4.1 问题
应用程序需要从Web或其他远程服务器下载一张图片并显示。
4.2 解决方案
(API Level 4)
使用AsyncTask在后台线程中下载数据。AsyncTask是封装类,它可以很方便地让需要长时间允许操作的线程在后台允许;同样,它通过一个内部线程池管理线程的并发。除了管理后台线程外,在操作执行前、中、后都会提供回调方法,让你可以做任何需要在主UI线程中进行的更新。
4.3 实现机制
在下载图片的环境中,我们会创建ImageView的一个子类,叫做WebImageView,它会从远程来源中延迟加载一张图片并且在该图片可用时就显示它。下载过程会在一个AsyncTask操作中执行(参见以下代码清单)。
WebImageView
public class WebImageView extends ImageView {
private Drawable mPlaceholder,mImage;
public WebImageView(Context context) {
super(context,null);
}
public WebImageView(Context context, AttributeSet attrs) {
super(context, attrs,0);
}
public WebImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setPlaceholder(Drawable drawable) {
this.mPlaceholder = drawable;
if (mImage == null){
setImageDrawable(mPlaceholder);
}
}
public void setPlaceholder(int resid) {
mPlaceholder = getResources().getDrawable(resid);
if (mImage == null){
setImageDrawable(mPlaceholder);
}
}
public void setImageURl(String url) {
DownloadTask task = new DownloadTask();
task.execute(url);
}
private class DownloadTask extends AsyncTask<String,Void,Bitmap> {
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
try {
URLConnection connection = (new URL(url)).openConnection();
InputStream is = connection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
while ((current = bis.read()) != -1) {
baf.append((byte) current);
}
byte[] imageData = baf.toByteArray();
return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
} catch (Exception exc) {
return null;
}
}
@Override
protected void onPostExecute(Bitmap result) {
mImage = new BitmapDrawable(getContext().getResources(),result);
if (mImage != null){
setImageDrawable(mImage);
}
}
}
}
正如你所看到的那样,WebImageView是Android的mageView小部件的一个简单扩展。在远程内容下载完成之前,setPlaceholderImage()会为要显示的图片设置一个本地Drawable。大多数有趣的工作都是从使用setImageUrl()向视图传入一个给定远程URL开始的,该方法表示自定义的AsyncTask开始工作了。
请注意,AsyncTask是强类型化的,它需要三个值:输入参数、进度值、结果值。在这种情况下,会传递给任务的execute()方法一个字符串,而后台操作应该返回一个Bitmap。对于中间的进度值,我们在本例中并不会使用,因此它被设置为Void。继承AsyncTask后,唯一需要实现的方法就是doInBackground(),它定义了后台线程中需要大量运行的操作。在前面的示例中,这里是与提供的远程URL进行连接以及下载图片的地方。完成后,我们会试图用下载的数据创建一个Bitmap。发生任何错误时,操作将中止并返回null。
要点:
Android UI类是线程不安全的。确保在更新UI时使用运行在主线程上的回调方法。不要在doInBackground()中更新视图。
以下两段代码清单展示了在一个Activity中使用这个类的示例。因为这个类不是android.widget或android.view包的一部分,所以在XML中使用它时必须先指定它的完全限定包名。
res/layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.jennyni.webimageviewdemo.MainActivity">
<com.jennyni.webimageviewdemo.WebImageView
android:id="@+id/webImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.jennyni.webimageviewdemo.WebImageView>
</LinearLayout>
示例Activity
public class WebImageActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebImageView imageView = (WebImageView)findViewById(R.id.webImage);
imageView.setPlaceholderImage(R.drawable.ic_launcher);
imageView.setImageUrl("http://lorempixel.com/400/200");
}
}
本例中,首先会设置一张本地图片(应用程序图标)作为WebImageView的占位图片,这张图片会立刻显示给用户。然后我们会告诉视图从Web上获取Apress的徽标图片。如前所述,这里会在后台下载图片,下载完成后会替换掉视图中的占位图片。正是因为创建后台操作的简单性,Android团队才会把AsyncTask叫作“无痛苦使用线程”。
网友评论