生产者-消费者模型在Android开发中的应用

话说生产者-消费者模型可是并发模型中的一个经典案例了,你可能会问,这种并发情况下,一般服务端程序用的比较多把,Android端的应用程序哪有什么并发情况。虽然事实如此,但是构建生产者-消费者模型,是线程间协作的思想,工作线程的协助是为了让UI线程更好的完成工作,提高用户体验。比如,下面的一种情况:


这里写图片描述


这个是我们平常开发中很常见的一种情景,大量的图片资源的访问,因为图片访问是一个网络耗时的任务,如果完全交由UI线程去处理,显然用户体验不佳,只能在适配器(Adapter)中的getView()方法做网络异步请求。很多人,都通过第三方框架来实现异步的效果,虽然图片加载的处理要比我们好很多,但是用户体验的效果还是不佳。在比如说像图片的那样,如何做到异步加载,这都是由工作线程协助UI线程去完成的,使用生产者-消费者模型则有助于提高的用户体验。


1.生产者-消费者模型的构造

这里写图片描述

在这里,我可以提前准备一个队列或者集合,作为缓冲区,把用户拖动作为生产者,因为用户一拖动就会调度getView()方法,那么我们在getView()方法就像缓冲区存放网络请求的任务进去。那么,消费者就是我们的工作线程,我们在工作线程将任务取出,并且加载到内存中,由Hander来切换到UI线程中,完成更新。更为主要的是,如果将任务放入队列中或者什么时间取出任务;从队列中取出哪个任务;什么时候执行任务;怎样执行任务……这个决策权完全由我们掌握,这样,我们就把UI线程的压力给释放出来了。


2.实现模型

  • 用Vector来实现队列的效果,当然我们的选择也有很多。
  • 第二步我们要做的是,实现GridView/ListView的滚动监听
mGridView.setOnScrollListener(new OnScrollListener() {
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {
                    // TODO Auto-generated method stub
                    switch (scrollState) {
                        case OnScrollListener.SCROLL_STATE_FLING:
                            isTouch = false;
                            isFlying = true;
                            break;
                        case OnScrollListener.SCROLL_STATE_IDLE:
                            isTouch = false;
                            isFlying = false;
                            if (adapter != null) {
                                adapter.notifyDataSetChanged();
                            }
                            break;
                        case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                            isFlying = false;
                            isTouch = true;
                            break;
                    }
                }

                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    // TODO Auto-generated method stub
                }
            });

这样我们就知道,用户使用的状态了。

  • 其次,我们要在Adapter中启动一条工作线程,充当消费者。
        if(null == workThread){
                workThread = new Thread(){
                    public void run() {
                        while(isLoad){
                            //这里停顿主要是为了达到瀑布流的效果
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                            if(! vector.isEmpty() ){
                            //这里仿照微信里面的倒序加载图片
                                String url = vector.remove(vector.size()-1);
                                if(mHandler != null)
                                    //消耗任务并且更新UI
                                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
                            }

                            else{
                                try {

                                    //如果队列为空,则等待
                                    synchronized (workThread) {
                                        workThread.wait();
                                    }

                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                };
                workThread.start();
            }
        }
  • 在Adapter的getView()方法,放入任务,生产者所做的事情。
//标记ImageView,为了后续执行完任务后,将图片放到这个ImageView上
iv.setTag(Images.imageThumbUrls[position]);
            ImageContainer image = ImageMaps.get(Images.imageThumbUrls[position]);
            if(image != null){
                //这里防止内存中缓存的图片已回收
                if(image.getBitmap() != null){
                    iv.setImageBitmap(image.getBitmap());
                }
            } else {
                if(!isFlying){

                    //这里防止getView()方法多次调用,导致放入重复的任务
                    //或者可以使用Set来存放任务
                    if(!vector.contains(Images.imageThumbUrls[position]))
                    {
                        //放任务
                        vector.add(Images.imageThumbUrls[position]);
                    }

                      //通知消费者去取任务
                    synchronized (workThread) {
                        workThread.notify();
                    }
                }
            }
  • 在Handler中,将任务交给Volley处理,并且更新UI。
    private static class MyHandler extends Handler{

        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        public void handleMessage(Message msg) {
            if(msg.what == UPDATE_IMAGE){
                try {
                    String url = (String) msg.obj;
                    //找出我们标记的ImageView
                    SquareImageView iv = (SquareImageView)mActivity.get().mGridView.findViewWithTag(url);

                     //更新UI之后,我们再将从网络上获取的图片资源,再放到一层硬缓存中
                     //目的是为了不再加载已经加载过的图片
                    mActivity.get().ImageMaps.put(url,
                            mActivity.get().imageLoader.get(
                                    url,
                                    ImageLoader.getImageListener(iv, R.mipmap.aio_image_default, R.mipmap.aio_image_fail))
                    );

                       //告诉我们的硬缓存,现在应用程序已经占有多少内存空间了
                       //如果达到指定的空间,则清理部分图片
                    mActivity.get().ImageMaps.setHasHoldMemory(
                            ((int) Runtime.getRuntime().totalMemory())/1024/1024);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 在此之前,指定一层硬缓存(不一定要加,可以选择加上)。选择继承的是LinkedHashMap,内部采用了LRU算法,即不常用的实体,最先被清理。
/**
 * Created by Nipuream on 2016/5/19 0019.
 */
public class LruHashMap<K,V> extends LinkedHashMap<K,V>{


    private  int maxMemory = 1024*10;
    private int totalMemory = 0;

    private boolean isRemoveOldest = true;



    public void setMaxMemory(int max){
        maxMemory = max;
    }

    public void setRemoveOldest(boolean isRemoveOldest){
        this.isRemoveOldest = isRemoveOldest;
    }

    public void setHasHoldMemory(int totalMemory){
        this.totalMemory = totalMemory;
    }


    protected boolean removeEldestEntry(Map.Entry eldest) {

        //如果应用程序现在占据的内存空间加上10MB已经要大于系统指定给我们最大的内存空间
        //那赶紧清理老的图片
        if(isRemoveOldest){
            if((totalMemory + 10) > maxMemory){
                return true;
            }else{
                return false;
            }
        }

        return false;
    }
}



到此我们的生产者-消费者模型就已经构建完了,这样一来,用户的体验就会提升一个档次,不相信的可以下载代码,运行下,哈哈。当然,如果你觉得代码繁琐,还有种更为简单的方式,我们可以采用java并发库里面的工具ConcurrentLinkedQueue来充当我们的队列,当然这个队列就是真的队列,采用的是先进先出的行为,我们就不能对任务的取出的顺序进行操作了,也就达不到倒序加载或者随机加载了的效果了。但是代码非常的简洁。

//队列
private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();

//生产者
    if(!queue.contains(Images.imageThumbUrls[position])){
                        queue.offer(Images.imageThumbUrls[position]);
                    }
//消费者,这里虽然不是原子操作,但是考虑到只有一个线程对它操作,所以就没有同步了。
    if(!queue.isEmpty()){
                                String url = queue.poll();
                                if(mHandler != null){
                                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
                                }
                            }

ConcurrentLinkedQueue队列的特点就是内部采用了CAS原子操作,是一种非阻塞的同步队列,所以就没必要我们对其加锁了。所以对ConcurrentLinkedQueue不熟悉的话,可以看这篇文章并发编程网脑补下。


如果对文章代码逻辑不清楚的也可以下载我的源码参考下,代码地址链接如下:

生产者-消费者在Android开发中的应用

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值