您的位置:首页 > 移动开发 > Android开发

Android使用Handler实现线程池的效果,实现照片墙应用

2015-08-05 13:52 477 查看
转载请注明 /article/1462867.html

背景

照片墙应用常常被用来讲解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的所有用法,希望能给大家起到启迪的作用。

源码入口,请轻轻的进
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: