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

来源:互联网 发布:apache 配置php7 linux 编辑:程序博客网 时间:2024/04/29 15:04

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


这里写图片描述


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


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

这里写图片描述

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


2.实现模型

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

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

  • 其次,我们要在Adapter中启动一条工作线程,充当消费者。
<code class="hljs java has-numbering"><span class="hljs-keyword">if</span>(<span class="hljs-keyword">null</span> == workThread){                workThread = <span class="hljs-keyword">new</span> Thread(){                    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {                        <span class="hljs-keyword">while</span>(isLoad){                            <span class="hljs-comment">//这里停顿主要是为了达到瀑布流的效果</span>                            <span class="hljs-keyword">try</span> {                                Thread.sleep(<span class="hljs-number">100</span>);                            } <span class="hljs-keyword">catch</span> (InterruptedException e) {                                <span class="hljs-comment">// TODO Auto-generated catch block</span>                                e.printStackTrace();                            }                            <span class="hljs-keyword">if</span>(! vector.isEmpty() ){                            <span class="hljs-comment">//这里仿照微信里面的倒序加载图片</span>                                String url = vector.remove(vector.size()-<span class="hljs-number">1</span>);                                <span class="hljs-keyword">if</span>(mHandler != <span class="hljs-keyword">null</span>)                                    <span class="hljs-comment">//消耗任务并且更新UI</span>                                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));                            }                            <span class="hljs-keyword">else</span>{                                <span class="hljs-keyword">try</span> {                                    <span class="hljs-comment">//如果队列为空,则等待</span>                                    <span class="hljs-keyword">synchronized</span> (workThread) {                                        workThread.wait();                                    }                                } <span class="hljs-keyword">catch</span> (InterruptedException e) {                                    e.printStackTrace();                                }                            }                        }                    }                };                workThread.start();            }        }</code>

在Adapter的getView()方法,放入任务,生产者所做的事情。

<code class="hljs mel has-numbering"><span class="hljs-comment">//标记ImageView,为了后续执行完任务后,将图片放到这个ImageView上</span>iv.setTag(Images.imageThumbUrls[position]);            ImageContainer <span class="hljs-keyword">image</span> = ImageMaps.get(Images.imageThumbUrls[position]);            <span class="hljs-keyword">if</span>(<span class="hljs-keyword">image</span> != null){                <span class="hljs-comment">//这里防止内存中缓存的图片已回收</span>                <span class="hljs-keyword">if</span>(<span class="hljs-keyword">image</span>.getBitmap() != null){                    iv.setImageBitmap(<span class="hljs-keyword">image</span>.getBitmap());                }            } <span class="hljs-keyword">else</span> {                <span class="hljs-keyword">if</span>(!isFlying){                    <span class="hljs-comment">//这里防止getView()方法多次调用,导致放入重复的任务</span>                    <span class="hljs-comment">//或者可以使用Set来存放任务</span>                    <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">vector</span>.contains(Images.imageThumbUrls[position]))                    {                        <span class="hljs-comment">//放任务</span>                        <span class="hljs-keyword">vector</span>.add(Images.imageThumbUrls[position]);                    }                      <span class="hljs-comment">//通知消费者去取任务</span>                    synchronized (workThread) {                        workThread.notify();                    }                }            }</code>
在Handler中,将任务交给Volley处理,并且更新UI。

<code class="hljs java has-numbering"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Handler</span>{</span>        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> WeakReference<MainActivity> mActivity;        <span class="hljs-keyword">public</span> <span class="hljs-title">MyHandler</span>(MainActivity activity) {            mActivity = <span class="hljs-keyword">new</span> WeakReference<MainActivity>(activity);        }        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) {            <span class="hljs-keyword">if</span>(msg.what == UPDATE_IMAGE){                <span class="hljs-keyword">try</span> {                    String url = (String) msg.obj;                    <span class="hljs-comment">//找出我们标记的ImageView</span>                    SquareImageView iv = (SquareImageView)mActivity.get().mGridView.findViewWithTag(url);                     <span class="hljs-comment">//更新UI之后,我们再将从网络上获取的图片资源,再放到一层硬缓存中</span>                     <span class="hljs-comment">//目的是为了不再加载已经加载过的图片</span>                    mActivity.get().ImageMaps.put(url,                            mActivity.get().imageLoader.get(                                    url,                                    ImageLoader.getImageListener(iv, R.mipmap.aio_image_default, R.mipmap.aio_image_fail))                    );                       <span class="hljs-comment">//告诉我们的硬缓存,现在应用程序已经占有多少内存空间了</span>                       <span class="hljs-comment">//如果达到指定的空间,则清理部分图片</span>                    mActivity.get().ImageMaps.setHasHoldMemory(                            ((<span class="hljs-keyword">int</span>) Runtime.getRuntime().totalMemory())/<span class="hljs-number">1024</span>/<span class="hljs-number">1024</span>);                } <span class="hljs-keyword">catch</span> (Exception e) {                    e.printStackTrace();                }            }        }    }</code>
在此之前,指定一层硬缓存(不一定要加,可以选择加上)。选择继承的是LinkedHashMap,内部采用了LRU算法,即不常用的实体,最先被清理。

<code class="hljs axapta has-numbering"><span class="hljs-comment">/** * Created by Nipuream on 2016/5/19 0019. */</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LruHashMap</span><<span class="hljs-title">K</span>,<span class="hljs-title">V</span>> <span class="hljs-inheritance"><span class="hljs-keyword">extends</span></span> <span class="hljs-title">LinkedHashMap</span><<span class="hljs-title">K</span>,<span class="hljs-title">V</span>>{</span>    <span class="hljs-keyword">private</span>  <span class="hljs-keyword">int</span> maxMemory = <span class="hljs-number">1024</span>*<span class="hljs-number">10</span>;    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> totalMemory = <span class="hljs-number">0</span>;    <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> isRemoveOldest = <span class="hljs-keyword">true</span>;    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> setMaxMemory(<span class="hljs-keyword">int</span> max){        maxMemory = max;    }    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> setRemoveOldest(<span class="hljs-keyword">boolean</span> isRemoveOldest){        <span class="hljs-keyword">this</span>.isRemoveOldest = isRemoveOldest;    }    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> setHasHoldMemory(<span class="hljs-keyword">int</span> totalMemory){        <span class="hljs-keyword">this</span>.totalMemory = totalMemory;    }    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">boolean</span> removeEldestEntry(Map.Entry eldest) {        <span class="hljs-comment">//如果应用程序现在占据的内存空间加上10MB已经要大于系统指定给我们最大的内存空间</span>        <span class="hljs-comment">//那赶紧清理老的图片</span>        <span class="hljs-keyword">if</span>(isRemoveOldest){            <span class="hljs-keyword">if</span>((totalMemory + <span class="hljs-number">10</span>) > maxMemory){                <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;            }<span class="hljs-keyword">else</span>{                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;            }        }        <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;    }}</code>
到此我们的生产者-消费者模型就已经构建完了,这样一来,用户的体验就会提升一个档次,不相信的可以下载代码,运行下,哈哈。当然,如果你觉得代码繁琐,还有种更为简单的方式,我们可以采用java并发库里面的工具ConcurrentLinkedQueue来充当我们的队列,当然这个队列就是真的队列,采用的是先进先出的行为,我们就不能对任务的取出的顺序进行操作了,也就达不到倒序加载或者随机加载了的效果了。但是代码非常的简洁。

<code class="hljs cpp has-numbering"><span class="hljs-comment">//队列</span><span class="hljs-keyword">private</span> ConcurrentLinkedQueue<String> <span class="hljs-built_in">queue</span> = <span class="hljs-keyword">new</span> ConcurrentLinkedQueue<String>();<span class="hljs-comment">//生产者</span>    <span class="hljs-keyword">if</span>(!<span class="hljs-built_in">queue</span>.contains(Images.imageThumbUrls[position])){                        <span class="hljs-built_in">queue</span>.offer(Images.imageThumbUrls[position]);                    }<span class="hljs-comment">//消费者,这里虽然不是原子操作,但是考虑到只有一个线程对它操作,所以就没有同步了。</span>    <span class="hljs-keyword">if</span>(!<span class="hljs-built_in">queue</span>.isEmpty()){                                String url = <span class="hljs-built_in">queue</span>.poll();                                <span class="hljs-keyword">if</span>(mHandler != null){                                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));                                }                            }</code>

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


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

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

本文转载自:http://blog.csdn.net/YanghuiNipurean/article/details/51454344

0 0