android 自定义组件之URLImageView
fmms
13年前
最近想写一些android小组件,因为最近各种事情也比较多,没时间也没精力再来自己单独写一个应用,写组件也是为了写应用更方便嘛^-^。这第 一个小组件是能够显示一个互联网的图片的ImageView,我把它叫做URLImageView,本来是觉得昨天一个晚上就可以搞定的,结果在写的过程 中遇到了各种各样的问题,这里就和大家分享一下。
URLImageView小组件的作用是显示互联网上的图片。并在加载过程中显示进度条。最终效果如下:
下载中(左):和下载完毕显示(右)
这里我的实现方式是继承自RelativeLayout。好了,废话不说,上代码。
package com.sheling.android.widget; /**@author sheling * 2012-4-2 * * */ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Environment; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; public class UrlImageView extends RelativeLayout { private static final String LOG_TAG = "URLImageView"; private static String TEMP_STORGE_PATH_DIR = "/tmp/"; private Bitmap bitmap; private String TEMP_STORGE_PATH_FILE ; private URL url; private Context context; private ImageView imageView; private ProgressBar progressBar; public UrlImageView(Context context) { super(context); this.context = context; init(); } public UrlImageView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); } public UrlImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; init(); } /**初始化布局信息*/ public void init(){ LayoutInflater.from(context).inflate(R.layout.url_image_view,this,true);; imageView = (ImageView) findViewById(R.id.imageView); progressBar = (ProgressBar) findViewById(R.id.processBar); } /**bind ImageUrl * @throws MalformedURLException */ public void bindUrl(String urlString) throws MalformedURLException,IOException{ bindUrl(new URL(urlString)); } /**bind ImageURL * @throws IOException */ public void bindUrl(URL url) throws IOException{ Log.v(LOG_TAG, "bindURL..."); this.url = url; String[] urlArr = url.toString().split("/"); TEMP_STORGE_PATH_FILE = TEMP_STORGE_PATH_DIR +urlArr[urlArr.length -1]; DownloadTask dTask = new DownloadTask(); dTask.execute(url); } class DownloadTask extends AsyncTask<URL, Long, String>{ @Override protected String doInBackground(URL... params) { // TODO get bitmap and set update process signal /* 取得连接 */ HttpURLConnection conn; File tmpFile = null; try { conn = (HttpURLConnection) url.openConnection(); conn.connect(); /* 取得返回的InputStream */ InputStream is = conn.getInputStream(); //得到网络文件大小 long size = conn.getContentLength(); Log.v(LOG_TAG,"文件大小:"+size); //下载存储的文件路径 tmpFile = new File(Environment.getExternalStorageDirectory() + TEMP_STORGE_PATH_FILE); boolean needDownload = true; if(tmpFile.exists()){ //若存在同名文件,【判断大小再操作 FileInputStream fips = new FileInputStream(tmpFile); long tmpSize = fips.available(); Log.v(LOG_TAG,"已存在文件大小:"+tmpSize); fips.close(); Log.v(LOG_TAG, (tmpSize == size)+""); Log.v(LOG_TAG, (Long.valueOf(tmpSize)==Long.valueOf(size))+""); if(tmpSize == size){ Log.v(LOG_TAG, "already downloaded"); needDownload = false; }else{ tmpFile.delete(); Log.e(LOG_TAG, "deleted the same file"); } } //需要下载再下载 if(needDownload){ tmpFile.createNewFile(); FileOutputStream fops = new FileOutputStream(tmpFile); byte[] buffer = new byte[1024 * 8]; int length = 0; int readed = 0; while((length=is.read(buffer))!=-1){ fops.write(buffer); fops.flush(); readed += length; publishProgress((readed*100)/size); Log.v(LOG_TAG,"readed"); } /* 关闭InputStream */ fops.close(); is.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return tmpFile.toString(); } @Override protected void onProgressUpdate(Long... values) { // TODO update ProgressBar progressBar.setProgress(values[0].intValue()); super.onProgressUpdate(values); } @Override protected void onPostExecute(String tmpFileStr) { // TODO show bitmap super.onPostExecute(tmpFileStr); //读取文件 File tmpFile = new File(tmpFileStr); try { bitmap = BitmapFactory.decodeStream(new FileInputStream(tmpFile)); Log.v(LOG_TAG, "bitmapPath:"+tmpFileStr); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } //更新显示 progressBar.setVisibility(View.GONE); imageView.setVisibility(View.VISIBLE); imageView.setImageBitmap(bitmap); } } }
上述代码在执行过程中会发现,每次运行都要重新去下载图片。这段代码并没有按我们预期的目标执行(为了用户考虑,能不要重复下载的资源就不用下)。然后去仔细对比一下两个文件的大小,发现下载下来的文件总是要比原文件大。这问题出在哪呢。
计算一下,会发现我们下载下来的文件总是缓冲区的大小的倍数。说明这种下载文件的方式是错误的。这下问题便迎刃而解了。为了得到和原文件一样大小的文件,就应该先用一定大小的缓冲区读完之后,把剩下的不足一次缓冲区大小的一次读完,且不加入其他新字节。
修改后doInBackground方法的代码如下
@Override protected String doInBackground(URL... params) { // TODO get bitmap and set update process signal /* 取得连接 */ HttpURLConnection conn; File tmpFile = null; try { conn = (HttpURLConnection) url.openConnection(); conn.connect(); /* 取得返回的InputStream */ InputStream is = conn.getInputStream(); long size = conn.getContentLength(); Log.v(LOG_TAG,"文件大小:"+size); tmpFile = new File(Environment.getExternalStorageDirectory() + TEMP_STORGE_PATH_FILE); boolean needDownload = true; //存在,判断大小是否一致 if(tmpFile.exists()){ FileInputStream fips = new FileInputStream(tmpFile); long tmpSize = fips.available(); Log.v(LOG_TAG,"已存在文件大小:"+tmpSize); fips.close(); //一致,跳过下载 if(tmpSize == size){ Log.v(LOG_TAG, "already downloaded"); needDownload = false; }else{ tmpFile.delete(); Log.e(LOG_TAG, "deleted the same file"); } } if(needDownload){ tmpFile.createNewFile(); FileOutputStream fops = new FileOutputStream(tmpFile); int onceSize = 1024 * 4; byte[] buffer = new byte[onceSize]; //计算固定大小的缓冲区要读多少次 int readNum = (int) Math.floor(size/onceSize); //得到剩余的字节长度 int leave = (int) (size - readNum * onceSize); int length = 0; int readed = 0; for(int i=readNum;i>0;i--){ length=is.read(buffer); fops.write(buffer); fops.flush(); readed += length; publishProgress((readed*100)/size); Log.v(LOG_TAG,"readed"); } buffer = new byte[leave]; length=is.read(buffer); fops.write(buffer); fops.flush(); readed += length; publishProgress((readed*100)/size); Log.v(LOG_TAG,"readed"); fops.close(); is.close(); }else{ //固定的显示一个过程 publishProgress(80L); } } catch (IOException e) { e.printStackTrace(); } return tmpFile.toString(); }
这样,一个简单的读取,下载,并显示互联网图片的android小组件便完成了。当然,还可以给它美化,增加更多的功能。
附源码:
http://code.google.com/p/sheling-android-urlimageview/downloads/list