Android使用Handler实现线程池的效果,实现照片墙应用
2015-08-05 13:52
477 查看
转载请注明 /article/1462867.html
在这里,我会尝试用handler、looper、thread、message来基本模拟线程池的效果,不用android、java自带的线程池,实现多线程并发下载图片。这里不涉及LruCache,每次都是重新去获取,为了呈现handler模拟多线程并发的极端用法。
这里补充一句,如果对上述handler、looper、thread、message这几个概念不清楚的话,可以查看我的上一篇Android异步消息之Looper、Handler、Message、HandlerThread的关系
看下效果图,模拟器有点卡,真机在网络好的情况下秒现:
从上一篇Android异步消息之Looper、Handler、Message、HandlerThread的关系可以得到一个关键点是,handler是用来唤醒线程给它办事情的。这个线程可以是子线程,也可以UI线程,就看你handler在哪里初始化。
既然如此,那么我们在程序一开始的时候,就开辟一些子线程,然后这些子线程都用handler关联起来,这样不就有了一个线程池了,因为handler可以唤醒这个线程干活。
至于怎么判断当前handler对应的线程是空闲的,可以通过一个信号量去控制。
大致是这样,具体我们直接看代码一点点分析。
直接用GridView填充屏幕
接着是每个item的布局:
每个item由一个ImageView组成。
然后是一堆的图片资源链接:
太多了,就贴个部分。这些是从百度图片搜“美女”搜出来的,然后从网页源码里抓取出来的链接。
然后是主代码部分了:
这里,定义了一些变量。可以看到,默认开辟10个子线程。
threadHandlers用于保存子线程环境下的handler,这些handler用于从网上下载图片资源。handlerStatus用于记录这些handler忙碌还是空闲的情况。
managerHandler用来管理当前哪个threadhandler可用,并发送消息给它。mSemaphore信号量用来确保没有可用threadhandler时,阻塞该线程。
uiHandler是UI线程下的handler,用来更新ui。
接着往下看:
onCreate是一些基本的操作,这里重点看
第2、3行是初始化两个list。上面代码有点多,我们按照流程顺序,一个个分开讲。
先从中间那个thread开始讲:
这里创建了一个子线程,然后在run()里我们看到,有初始化了managerHandler。这过程正如上一篇讲的,先是
managerHandler用来接收从gridview的adapter里每次getView时发出的消息。
然后看看它怎么处理消息,发现它复写了handleMessage(),里面先是判断信号量是否还有许可可以获取,如果有的话,就往下;没有了,就阻塞等待。
往下看到第15行调用了
这个是用来获取threadHandlers里哪个是空闲的位置。用handlerStatus存储handler空闲还是忙碌的标记,0代表空闲,1代表忙碌。
这里如果返回的是-1我抛个异常。因为先通过信号量判断了下,所以理论上,能走到
第22~24行标记找到的handler处于忙碌状态。注释也有。
然后25~30行就是继续创建一个message,并发送给空闲的那个handler。
这里,就类似线程池的分发了。先是从线程池里找到个空闲线程,然后分配它任务处理。这里分配任务是通过发送消息过去。
好,我们接着看这个发送的消息给谁处理:
可以看到,这里创建了10个子线程。每个子线程里都创建了一个handler,并通过looper和子线程关联。
这些handler都复写相同的handleMessage()。在里面,先是从message里获取图片url,然后调用
threadHandlers.add(handler);这一句,把这些创建的handler保存下来,用来在manageHandler里获取。
第32行是初始化这些handler的状态是空闲。
好了,继续看最后的uiHandler:
因为uiHandler是创建在UI主线程,所以它处理消息的地方是在UI主线程。代码很清晰,用来把网上下载下来的bitmap设置到imageview里。
这里要判断下imageview里保存的urlPath和传过来的urlPath是否一致,一致才能赋值给它。这是因为和listview一样,Gridview的每个item是可以复用的。
这里贴上每个消息的载体类
path即为图片的url。
再贴个downloadBitmap:
最后,我们来看下GridView的adapter:
主要来看下getView();
代码很简单,第30行是先给imageview附值默认的图片。然后把url通过view.settag,保存到imageview里。最后执行
这个loadImage干嘛呢?聪明的你应该已经猜到了吧。对,是给managerHandler发送消息:
这个消息发送过去,就会触发managerHandler去从threadHandlers找空闲的handler,然后把消息发给它,那个空闲的handler就变为忙碌状态,然后从网上下载图片,下载完后,再发送消息给uiHandler,让它去更新图片。
总的流程并不复杂,就是handler有点多。
我们再来理理,做为总结:
该应用涉及到三类handler
1. managerHandler
子线程里的handler,接收adapter里getview发出的消息,然后找出空闲handler,并发配任务处理。
2. threadHandlers
这是一个handler的集合,也是在子线程环境里的,都是接收managerHandler发出的消息(任务),然后从网上去下载图片,再发给uiHandler。
3. uiHandler
ui线程里的handler,用来更新图片。
这里,最初消息发起者是在getView的
我有加了log,可以看得更清晰点
这是对handler使用的极端案例,能看清楚handler的所有用法,希望能给大家起到启迪的作用。
源码入口,请轻轻的进
背景
照片墙应用常常被用来讲解Bitmap缓存机制,我觉得这个应用也适合讲多线程并发机制。在这里,我会尝试用handler、looper、thread、message来基本模拟线程池的效果,不用android、java自带的线程池,实现多线程并发下载图片。这里不涉及LruCache,每次都是重新去获取,为了呈现handler模拟多线程并发的极端用法。
这里补充一句,如果对上述handler、looper、thread、message这几个概念不清楚的话,可以查看我的上一篇Android异步消息之Looper、Handler、Message、HandlerThread的关系
看下效果图,模拟器有点卡,真机在网络好的情况下秒现:
实现
思路
我们先来理理思路:从上一篇Android异步消息之Looper、Handler、Message、HandlerThread的关系可以得到一个关键点是,handler是用来唤醒线程给它办事情的。这个线程可以是子线程,也可以UI线程,就看你handler在哪里初始化。
既然如此,那么我们在程序一开始的时候,就开辟一些子线程,然后这些子线程都用handler关联起来,这样不就有了一个线程池了,因为handler可以唤醒这个线程干活。
至于怎么判断当前handler对应的线程是空闲的,可以通过一个信号量去控制。
大致是这样,具体我们直接看代码一点点分析。
具体实现
先是主布局文件:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.office.steven.imageloadbyhandler.DownloadUpdateSimulated"> <GridView android:id="@+id/gridview" android:layout_width="match_parent" android:layout_height="wrap_content" android:columnWidth="90dp" android:stretchMode="columnWidth" android:numColumns="auto_fit" android:verticalSpacing="20dp" android:horizontalSpacing="20dp" android:gravity="center" > </GridView> </RelativeLayout>
直接用GridView填充屏幕
接着是每个item的布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/photo" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> </RelativeLayout>
每个item由一个ImageView组成。
然后是一堆的图片资源链接:
public class ImageUrl { public static final String[] IMAGE_URL = new String[] { ...... "http://c.hiphotos.baidu.com/image/h%3D360/sign=5f17bc3738f33a87816d061cf65c1018/8d5494eef01f3a29a7dbeac99b25bc315c607ca1.jpg", "http://a.hiphotos.baidu.com/image/h%3D360/sign=a2e4471513dfa9ece22e501152d0f754/342ac65c10385343abc5a2289113b07eca8088b4.jpg", "http://e.hiphotos.baidu.com/image/h%3D360/sign=3bdeafecd300baa1a52c41bd7711b9b1/0b55b319ebc4b745e74ad516cdfc1e178b8215d7.jpg", "http://c.hiphotos.baidu.com/image/h%3D360/sign=64668be4b11c8701c9b6b4e0177f9e6e/0d338744ebf81a4c1b1ad9c9d52a6059252da681.jpg", "http://f.hiphotos.baidu.com/image/h%3D360/sign=3c8e03e7b53533faeab6952898d2fdca/9f510fb30f2442a7fc65774cd543ad4bd01302eb.jpg", }; }
太多了,就贴个部分。这些是从百度图片搜“美女”搜出来的,然后从网页源码里抓取出来的链接。
然后是主代码部分了:
public class DownloadUpdateSimulated extends Activity { private static final String TAG = "Test"; /* 定义开辟10个子线程 */ private static final int THREAD_COUNT = 10; /* 用于保存在子线程处理的handler */ LinkedList<Handler> threadHandlers; List<Integer> handlerStatus; volatile Semaphore mSemaphore; Handler managerHandler; /* 在UI线程处理的handler */ Handler uiHandler;
这里,定义了一些变量。可以看到,默认开辟10个子线程。
threadHandlers用于保存子线程环境下的handler,这些handler用于从网上下载图片资源。handlerStatus用于记录这些handler忙碌还是空闲的情况。
managerHandler用来管理当前哪个threadhandler可用,并发送消息给它。mSemaphore信号量用来确保没有可用threadhandler时,阻塞该线程。
uiHandler是UI线程下的handler,用来更新ui。
接着往下看:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download_update_simulated); GridView gridView = (GridView) findViewById(R.id.gridview); initThreadsAndHandlers(); DownloadAdapter adapter = new DownloadAdapter(); gridView.setAdapter(adapter); }
onCreate是一些基本的操作,这里重点看
initThreadsAndHandlers();
private void initThreadsAndHandlers() { threadHandlers = new LinkedList<>(); handlerStatus = new ArrayList<>(); mSemaphore = new Semaphore(THREAD_COUNT); // 创建10个子线程,并通过looper和handler关联 for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; int position = msg.arg1; Log.i(TAG, "handler " +position +" 开始下载图片"); // 下载图片并处理 Bitmap bitmap = downloadBitmap(msgHolder.path); msgHolder.bitmap = bitmap; Message ui_msg = new Message(); ui_msg.obj = msgHolder; ui_msg.setTarget(uiHandler); ui_msg.sendToTarget(); // 标记这个handler已经完成任务,空了 synchronized (handlerStatus) { handlerStatus.set(position, 0); } mSemaphore.release(); Log.i(TAG, "handler " +position +" 任务完成, 空的状态"); } }; threadHandlers.add(handler); Looper.loop(); } }).start(); handlerStatus.add(0); } // 创建管理上面n个handler的handler new Thread(new Runnable() { @Override public void run() { Looper.prepare(); managerHandler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; try { mSemaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } int position = getEmptyHandlerPosition(); if (position == -1) { throw new IllegalStateException(); } Log.i(TAG, "获取到空的handler " + position); Log.i(TAG, "handler " +position + " 开始使用, 忙碌状态"); // 标记这个handler已经在使用了 synchronized (handlerStatus) { handlerStatus.set(position, 1); } Handler emptyHandler = threadHandlers.get(position); Message work_msg = emptyHandler.obtainMessage(); work_msg.obj = msgHolder; work_msg.arg1 = position; work_msg.sendToTarget(); } }; Looper.loop(); } }).start(); // 在主线程创建handler uiHandler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; ImageView imageView = msgHolder.imageView; String path = msgHolder.path; if (imageView.getTag().toString().equals(path)) { imageView.setImageBitmap(msgHolder.bitmap); } Log.i(TAG, "uiHandler更新ui"); } }; }
第2、3行是初始化两个list。上面代码有点多,我们按照流程顺序,一个个分开讲。
先从中间那个thread开始讲:
// 创建管理上面n个handler的handler new Thread(new Runnable() { @Override public void run() { Looper.prepare(); managerHandler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; try { mSemaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } int position = getEmptyHandlerPosition(); if (position == -1) { throw new IllegalStateException(); } Log.i(TAG, "获取到空的handler " + position); Log.i(TAG, "handler " +position + " 开始使用, 忙碌状态"); // 标记这个handler已经在使用了 synchronized (handlerStatus) { handlerStatus.set(position, 1); } Handler emptyHandler = threadHandlers.get(position); Message work_msg = emptyHandler.obtainMessage(); work_msg.obj = msgHolder; work_msg.arg1 = position; work_msg.sendToTarget(); } }; Looper.loop(); } }).start();
这里创建了一个子线程,然后在run()里我们看到,有初始化了managerHandler。这过程正如上一篇讲的,先是
Looper.prepare();,然后是
new Handler(),最后是
Looper.loop();。
managerHandler用来接收从gridview的adapter里每次getView时发出的消息。
然后看看它怎么处理消息,发现它复写了handleMessage(),里面先是判断信号量是否还有许可可以获取,如果有的话,就往下;没有了,就阻塞等待。
往下看到第15行调用了
getEmptyHandlerPosition();
这个是用来获取threadHandlers里哪个是空闲的位置。用handlerStatus存储handler空闲还是忙碌的标记,0代表空闲,1代表忙碌。
private int getEmptyHandlerPosition() { for (int i = 0; i < THREAD_COUNT; i++) { if (handlerStatus.get(i) == 0) { return i; } } return -1; }
这里如果返回的是-1我抛个异常。因为先通过信号量判断了下,所以理论上,能走到
getEmptyHandlerPosition();,说明已经有空闲的handler了。
第22~24行标记找到的handler处于忙碌状态。注释也有。
然后25~30行就是继续创建一个message,并发送给空闲的那个handler。
这里,就类似线程池的分发了。先是从线程池里找到个空闲线程,然后分配它任务处理。这里分配任务是通过发送消息过去。
好,我们接着看这个发送的消息给谁处理:
// 创建10个子线程,并通过looper和handler关联 for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; int position = msg.arg1; Log.i(TAG, "handler " +position +" 开始下载图片"); // 下载图片并处理 Bitmap bitmap = downloadBitmap(msgHolder.path); msgHolder.bitmap = bitmap; Message ui_msg = new Message(); ui_msg.obj = msgHolder; ui_msg.setTarget(uiHandler); ui_msg.sendToTarget(); // 标记这个handler已经完成任务,空了 synchronized (handlerStatus) { handlerStatus.set(position, 0); } mSemaphore.release(); Log.i(TAG, "handler " +position +" 任务完成, 空的状态"); } }; threadHandlers.add(handler); Looper.loop(); } }).start(); handlerStatus.add(0); }
可以看到,这里创建了10个子线程。每个子线程里都创建了一个handler,并通过looper和子线程关联。
这些handler都复写相同的handleMessage()。在里面,先是从message里获取图片url,然后调用
downloadBitmap(msgHolder.path);去网络端下载图片。最后把这个bitmap在新的message里捎上,发送给uiHandler。然后标记此handler已经空闲(通过handlerStatus),再释放信号量。这样一来,在managerHandler就可以继续分配这个空闲的handler了。
threadHandlers.add(handler);这一句,把这些创建的handler保存下来,用来在manageHandler里获取。
第32行是初始化这些handler的状态是空闲。
好了,继续看最后的uiHandler:
// 在主线程创建handler uiHandler = new Handler() { @Override public void handleMessage(Message msg) { MsgHolder msgHolder = (MsgHolder) msg.obj; ImageView imageView = msgHolder.imageView; String path = msgHolder.path; if (imageView.getTag().toString().equals(path)) { imageView.setImageBitmap(msgHolder.bitmap); } Log.i(TAG, "uiHandler更新ui"); } };
因为uiHandler是创建在UI主线程,所以它处理消息的地方是在UI主线程。代码很清晰,用来把网上下载下来的bitmap设置到imageview里。
这里要判断下imageview里保存的urlPath和传过来的urlPath是否一致,一致才能赋值给它。这是因为和listview一样,Gridview的每个item是可以复用的。
这里贴上每个消息的载体类
private final class MsgHolder { String path; Bitmap bitmap; ImageView imageView; }
path即为图片的url。
再贴个downloadBitmap:
private Bitmap downloadBitmap(String imageUrl) { Bitmap bitmap = null; HttpURLConnection con = null; try { URL url = new URL(imageUrl); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5 * 1000); con.setReadTimeout(10 * 1000); bitmap = BitmapFactory.decodeStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } return bitmap; }
最后,我们来看下GridView的adapter:
class DownloadAdapter extends BaseAdapter { @Override public int getCount() { return ImageUrl.IMAGE_URL.length; } @Override public Object getItem(int position) { return ImageUrl.IMAGE_URL[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { viewHolder = new ViewHolder(); convertView = View.inflate(DownloadUpdateSimulated.this, R.layout.image_item, null); viewHolder.imageView = (ImageView) convertView.findViewById(R.id.photo); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.imageView.setImageResource(R.drawable.default_pic); viewHolder.imageView.setTag(getItem(position)); loadImage(viewHolder.imageView); return convertView; } } private final class ViewHolder { ImageView imageView; }
主要来看下getView();
代码很简单,第30行是先给imageview附值默认的图片。然后把url通过view.settag,保存到imageview里。最后执行
loadImage(viewHolder.imageView);
这个loadImage干嘛呢?聪明的你应该已经猜到了吧。对,是给managerHandler发送消息:
private void loadImage(ImageView imageView) { Message msg = new Message(); MsgHolder msgHolder = new MsgHolder(); msgHolder.imageView = imageView; msgHolder.path = (String) imageView.getTag(); msg.obj = msgHolder; msg.setTarget(managerHandler); msg.sendToTarget(); }
这个消息发送过去,就会触发managerHandler去从threadHandlers找空闲的handler,然后把消息发给它,那个空闲的handler就变为忙碌状态,然后从网上下载图片,下载完后,再发送消息给uiHandler,让它去更新图片。
总的流程并不复杂,就是handler有点多。
我们再来理理,做为总结:
该应用涉及到三类handler
1. managerHandler
子线程里的handler,接收adapter里getview发出的消息,然后找出空闲handler,并发配任务处理。
2. threadHandlers
这是一个handler的集合,也是在子线程环境里的,都是接收managerHandler发出的消息(任务),然后从网上去下载图片,再发给uiHandler。
3. uiHandler
ui线程里的handler,用来更新图片。
这里,最初消息发起者是在getView的
loadImage(viewHolder.imageView);。getView是在不断被调用的,然后managerHandler就会不断找出空闲handler去分配任务。而任务执行者从网上下载完之后,又会从忙碌状态设为空闲状态,方便managerHandler继续分配给它任务,如此,就实现了类似线程池的效果。
我有加了log,可以看得更清晰点
这是对handler使用的极端案例,能看清楚handler的所有用法,希望能给大家起到启迪的作用。
源码入口,请轻轻的进
相关文章推荐
- android获取共享文件信息
- android模拟器访问本地ip
- Android studio gradle debug模式用Release签名
- Android 4.0 事件输入(Event Input)系统
- Android性能调优
- Android getevent/sendevent详解
- Android中添加布局和初始化布局总结
- 如何“任性”使用Android的drawText()
- Facebook工程师是如何改进他们Android客户端的
- Android中批处理drawable-xxx目录中图片资源的那些事儿
- Android Wifi锁之WifiLock
- Android中ListView包含CheckBox时滑动丢失选中状态的解决
- Android进程与内存及内存泄露
- android应用中去掉标题栏的方法
- android.os.NetworkOnMainThreadException 异常处理
- Android(java)学习笔记140:SpannableString类的使用
- Android 绘制计时器
- 界面布局(1)
- VS2015 VisualStudio Emulator for Android 模拟器目录
- 分享一些大牛的Android博客