android 自定义组件之URLImageView
最近想写一些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
文章来自
sheling
的博客园:
http://www.cnblogs.com/sheling
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,
且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 我的独立博客 http://blog.iyestin.com
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
![知识共享许可协议](http://i.creativecommons.org/l/by-nc-sa/2.5/cn/80x15.png)