一Handler+Runnable模式

我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。

这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程 这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。

我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。


package
ghj1976.AndroidTest;





import
java.io.IOException;


import
java.net.URL;


import
android.app.Activity;


import
android.graphics.drawable.Drawable;


import
android.os.Bundle;


import
android.os.Handler;


import
android.os.SystemClock;


import
android.util.Log;


import
android.widget.ImageView;





public
class
MainActivity
extends
Activity {



@Override



public
void
onCreate(Bundle savedInstanceState) {



super
.onCreate(savedInstanceState);



setContentView(R.layout.main);



loadImage(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.imageView1);



loadImage(<img id=
"\"aimg_tv9sr\""
onclick=
"\"zoom(this,"
this
.src,=
""
0
,=
""
0
)\
"="
" class="
\"zoom\"
" file="
\"http:
//www.chinatelecom.com.cn/images/logo_new.gif\"" onmouseover="\"img_onmouseoverfunc(this)\"" onload="\"thumbImg(this)\"" border="\"0\"" alt="\"\"">",



R.id.imageView2);



loadImage("http:
//cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);



loadImage(
"http://csdnimg.cn/www/images/csdnindex_logo.gif"
,



R.id.imageView4);



loadImage(
"http://images.cnblogs.com/logo_small.gif"
,



R.id.imageView5);



}






private
Handler handler =
new
Handler();






private
void
loadImage(
final
String url,
final
int
id) {



handler.post(
new
Runnable() {



public
void
run() {



Drawable drawable =
null
;



try
{



drawable = Drawable.createFromStream(



new
URL(url).openStream(),
"image.gif"
);



}
catch
(IOException e) {



Log.d(
"test"
, e.getMessage());



}



if
(drawable ==
null
) {



Log.d(
"test"
,
"null drawable"
);



}
else
{



Log.d(
"test"
,
"not null drawable"
);



}



// 为了测试缓存而模拟的网络延时



SystemClock.sleep(
2000
);



((ImageView) MainActivity.
this
.findViewById(id))



.setImageDrawable(drawable);



}



});



}


}


Handler+Thread+Message模式

这种模式使用了线程,所以可以看到异步加载的效果。


核心代码:

package
ghj1976.AndroidTest;





import
java.io.IOException;


import
java.net.URL;


import
android.app.Activity;


import
android.graphics.drawable.Drawable;


import
android.os.Bundle;


import
android.os.Handler;


import
android.os.Message;


import
android.os.SystemClock;


import
android.util.Log;


import
android.widget.ImageView;





public
class
MainActivity
extends
Activity {



@Override



public
void
onCreate(Bundle savedInstanceState) {



super
.onCreate(savedInstanceState);



setContentView(R.layout.main);



loadImage2(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.imageView1);



loadImage2(
"http://www.chinatelecom.com.cn/images/logo_new.gif"
,



R.id.imageView2);



loadImage2(
"http://cache.soso.com/30d/img/web/logo.gif"
, R.id.imageView3);



loadImage2(
"http://csdnimg.cn/www/images/csdnindex_logo.gif"
,



R.id.imageView4);



loadImage2(
"http://images.cnblogs.com/logo_small.gif"
,



R.id.imageView5);



}






final
Handler handler2 =
new
Handler() {



@Override



public
void
handleMessage(Message msg) {



((ImageView) MainActivity.
this
.findViewById(msg.arg1))



.setImageDrawable((Drawable) msg.obj);



}



};






// 采用handler+Thread模式实现多线程异步加载



private
void
loadImage2(
final
String url,
final
int
id) {



Thread thread =
new
Thread() {



@Override



public
void
run() {



Drawable drawable =
null
;



try
{



drawable = Drawable.createFromStream(



new
URL(url).openStream(),
"image.png"
);



}
catch
(IOException e) {



Log.d(
"test"
, e.getMessage());



}






// 模拟网络延时



SystemClock.sleep(
2000
);






Message message = handler2.obtainMessage();



message.arg1 = id;



message.obj = drawable;



handler2.sendMessage(message);



}



};



thread.start();



thread =
null
;



}





}



这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。


Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。


这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。


线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。


线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService 下面的演示例子是创建一个可重用固定线程数的线程池。


核心代码


这里我们象第一步一样使用了 handler.post(new Runnable() {  更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { 来确保下载是在线程池的线程中。

package
ghj1976.AndroidTest;





import
java.io.IOException;


import
java.net.URL;


import
java.util.concurrent.ExecutorService;


import
java.util.concurrent.Executors;





import
android.app.Activity;


import
android.graphics.drawable.Drawable;


import
android.os.Bundle;


import
android.os.Handler;


import
android.os.Message;


import
android.os.SystemClock;


import
android.util.Log;


import
android.widget.ImageView;





public
class
MainActivity
extends
Activity {



@Override



public
void
onCreate(Bundle savedInstanceState) {



super
.onCreate(savedInstanceState);



setContentView(R.layout.main);



loadImage3(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.imageView1);



loadImage3(
"http://www.chinatelecom.com.cn/images/logo_new.gif"
,



R.id.imageView2);



loadImage3(
"http://cache.soso.com/30d/img/web/logo.gif"
,



R.id.imageView3);



loadImage3(
"http://csdnimg.cn/www/images/csdnindex_logo.gif"
,



R.id.imageView4);



loadImage3(
"http://images.cnblogs.com/logo_small.gif"
,



R.id.imageView5);



}






private
Handler handler =
new
Handler();






private
ExecutorService executorService = Executors.newFixedThreadPool(
5
);






// 引入线程池来管理多线程



private
void
loadImage3(
final
String url,
final
int
id) {



executorService.submit(
new
Runnable() {



public
void
run() {



try
{



final
Drawable drawable = Drawable.createFromStream(



new
URL(url).openStream(),
"image.png"
);



// 模拟网络延时



SystemClock.sleep(
2000
);



handler.post(
new
Runnable() {



public
void
run() {



((ImageView) MainActivity.
this
.findViewById(id))



.setImageDrawable(drawable);



}



});



}
catch
(Exception e) {



throw
new
RuntimeException(e);



}



}



});



}


}


Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:


把整个代码封装在一个类中

为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

封装的类:


   

package
ghj1976.AndroidTest;





import
java.lang.ref.SoftReference;


import
java.net.URL;


import
java.util.HashMap;


import
java.util.Map;


import
java.util.concurrent.ExecutorService;


import
java.util.concurrent.Executors;





import
android.graphics.drawable.Drawable;


import
android.os.Handler;


import
android.os.SystemClock;





public
class
AsyncImageLoader3 {



// 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)



public
Map<String, SoftReference<Drawable>> imageCache =
new
HashMap<String, SoftReference<Drawable>>();






private
ExecutorService executorService = Executors.newFixedThreadPool(
5
);
// 固定五个线程来执行任务



private
final
Handler handler =
new
Handler();






/**



*



* @param imageUrl



* 图像url地址



* @param callback



* 回调接口



* <a href="\"http://www.eoeandroid.com/home.php?mod=space&uid=7300\"" target="\"_blank\"">@return</a> 返回内存中缓存的图像,第一次加载返回null



*/



public
Drawable loadDrawable(
final
String imageUrl,



final
ImageCallback callback) {



// 如果缓存过就从缓存中取出数据



if
(imageCache.containsKey(imageUrl)) {



SoftReference<Drawable> softReference = imageCache.get(imageUrl);



if
(softReference.get() !=
null
) {



return
softReference.get();



}



}



// 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中



executorService.submit(
new
Runnable() {



public
void
run() {



try
{



final
Drawable drawable = loadImageFromUrl(imageUrl);






imageCache.put(imageUrl,
new
SoftReference<Drawable>(



drawable));






handler.post(
new
Runnable() {



public
void
run() {



callback.imageLoaded(drawable);



}



});



}
catch
(Exception e) {



throw
new
RuntimeException(e);



}



}



});



return
null
;



}






// 从网络上取数据方法



protected
Drawable loadImageFromUrl(String imageUrl) {



try
{



// 测试时,模拟网络延时,实际时这行代码不能有



SystemClock.sleep(
2000
);






return
Drawable.createFromStream(
new
URL(imageUrl).openStream(),



"image.png"
);






}
catch
(Exception e) {



throw
new
RuntimeException(e);



}



}






// 对外界开放的回调接口



public
interface
ImageCallback {



// 注意 此方法是用来设置目标对象的图像资源



public
void
imageLoaded(Drawable imageDrawable);



}


}

说明:


final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。参看:Java关键字final、static使用总结

这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:内存优化的两个类:SoftReference 和 WeakReference


前段调用:


   

package
ghj1976.AndroidTest;





import
android.app.Activity;


import
android.graphics.drawable.Drawable;


import
android.os.Bundle;





import
android.widget.ImageView;





public
class
MainActivity
extends
Activity {



@Override



public
void
onCreate(Bundle savedInstanceState) {



super
.onCreate(savedInstanceState);



setContentView(R.layout.main);



loadImage4(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.imageView1);



loadImage4(
"http://www.chinatelecom.com.cn/images/logo_new.gif"
,



R.id.imageView2);



loadImage4(
"http://cache.soso.com/30d/img/web/logo.gif"
,



R.id.imageView3);



loadImage4(
"http://csdnimg.cn/www/images/csdnindex_logo.gif"
,



R.id.imageView4);



loadImage4(
"http://images.cnblogs.com/logo_small.gif"
,



R.id.imageView5);



}






private
AsyncImageLoader3 asyncImageLoader3 =
new
AsyncImageLoader3();






// 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程



private
void
loadImage4(
final
String url,
final
int
id) {



// 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行



Drawable cacheImage = asyncImageLoader3.loadDrawable(url,



new
AsyncImageLoader3.ImageCallback() {



// 请参见实现:如果第一次加载url时下面方法会执行



public
void
imageLoaded(Drawable imageDrawable) {



((ImageView) findViewById(id))



.setImageDrawable(imageDrawable);



}



});



if
(cacheImage !=
null
) {



((ImageView) findViewById(id)).setImageDrawable(cacheImage);



}



}





}
package
ghj1976.AndroidTest;





import
android.app.Activity;


import
android.graphics.drawable.Drawable;


import
android.os.Bundle;





import
android.widget.ImageView;





public
class
MainActivity
extends
Activity {



@Override



public
void
onCreate(Bundle savedInstanceState) {



super
.onCreate(savedInstanceState);



setContentView(R.layout.main);



loadImage4(
"http://www.baidu.com/img/baidu_logo.gif"
, R.id.imageView1);



loadImage4(
"http://www.chinatelecom.com.cn/images/logo_new.gif"
,



R.id.imageView2);



loadImage4(
"http://cache.soso.com/30d/img/web/logo.gif"
,



R.id.imageView3);



loadImage4(
"http://csdnimg.cn/www/images/csdnindex_logo.gif"
,



R.id.imageView4);



loadImage4(
"http://images.cnblogs.com/logo_small.gif"
,



R.id.imageView5);



}






private
AsyncImageLoader3 asyncImageLoader3 =
new
AsyncImageLoader3();






// 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程



private
void
loadImage4(
final
String url,
final
int
id) {



// 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行



Drawable cacheImage = asyncImageLoader3.loadDrawable(url,



new
AsyncImageLoader3.ImageCallback() {



// 请参见实现:如果第一次加载url时下面方法会执行



public
void
imageLoaded(Drawable imageDrawable) {



((ImageView) findViewById(id))



.setImageDrawable(imageDrawable);



}



});



if
(cacheImage !=
null
) {



((ImageView) findViewById(id)).setImageDrawable(cacheImage);



}



}





}