生產者-消費者模型在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開發中的應用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM