1. 程式人生 > >Android之打造自己載入高清大圖及瀑布流框架.解決錯位等問題.

Android之打造自己載入高清大圖及瀑布流框架.解決錯位等問題.

    首先看效果圖如下:

    本框架支援本地圖片和網路圖片的獲取.採用LruCache演算法,最少使用的最先釋放.有效的避免OOM,專案結構圖:

    

    核心載入類在於ImageLoader.採用了TreadPool去做併發請求.UI處理採用Handler去管理,實現的思路類似於AsnycTask類.該類採用單例模式:

    public static ImageLoader getInstance(Context context) {
        if (null == loader) {
            synchronized (ImageLoader.class) {
                if (null == loader) {
                    loader = new ImageLoader(context, defThreadCount, mType);
                }
            }
        }
        return loader;
    }

    public static ImageLoader getInstance(Context context, int threadCount, Type type) {
        if (null == loader) {
            synchronized (ImageLoader.class) {
                if (null == loader) {
                    loader = new ImageLoader(context, threadCount, type);
                }
            }
        }
        return loader;
    }

第一種類不需要配置執行緒池及載入方式.載入方式分為兩種:1.先進先載入,2.後進先載入.
    /**
     * 佇列排程模式
     */
    public enum Type {
        FIFO, LIFO
    }
工作執行緒中核心是用Loop去不斷的取訊息,取到訊息後就加入到執行緒池當中去執行,這樣減少了自己去維護輪訓,減少記憶體開銷.
        //工作執行緒
        mThread = new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                mPoolThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        mThreadPool.execute(getTask());
                        try {
                            mPoolSemaphore.acquire();//訊號量 + 1
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
                mSemapHore.release();//初始化完成後訊號量 -1
                Looper.loop();
            }
        };

從上面程式碼可以看出PoolTreadHandler收到一個訊息後會讓mThreadPool去執行一個任務,該任務通過getTask()方法獲得一個Runnable物件,並且讓訊號量增加表示,執行緒池中有一個任務了.

看看getTask()程式碼很簡單,僅僅是將任務按不同的方式取出來:

    /**
     * 獲取任務
     *
     * @return
     */
    private synchronized Runnable getTask() {
        if (0 < mTask.size()) {
            if (mType == Type.LIFO)
                return mTask.removeFirst();
            else
                return mTask.removeLast();
        }
        return null;
    }

真正的工作在於mTask去add,mTask是一個LinkedList<Runnable>型別的集合.所以核心在於方法Load()
    /**
     * 載入圖片
     *
     * @param path
     * @param imageview
     */
    public void load(final String path, final View view, final LoadListener<View> loadListener) {

        if (null == path)
            throw new RuntimeException("this path is null");

        if (null == loadListener)
            throw new RuntimeException("this loadListener is null");

        view.setTag(path);
        //1.從磁碟,2.從記憶體
        if (null == mDisPlayHandler)
            mDisPlayHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    int code = msg.what;
                    ViewBeanHolder holder = (ViewBeanHolder) msg.obj;
                    final View view = holder.view;
                    Bitmap bm = holder.bitmap;
                    String path = holder.path;
                    switch (code) {
                        case LOAD_SUCCESS://載入成功
                            if (view.getTag().toString().equals(path)) {
                                loadListener.LoadSuccess(view, bm, path);
                                if (isNeedAnim)
                                    new LoadAnimCore(view);
                            }
                            break;
                        case LOAD_ING://載入中
                            if (view.getTag().toString().equals(path)) {
                                loadListener.Loading(view, path);
                            }
                            break;
                        case LOAD_FAILE://載入失敗
                            if (view.getTag().toString().equals(path)) {
                                loadListener.LoadError(view, path, null);//暫時訊息為空
                            }
                            break;
                    }
                }
            };

        addTask(path, view);

    }

其中view.setTag是為了防止錯亂.上面程式碼可以看出來僅僅是用於callBack,核心的東西其實在addTask方法.我們看看addTask方法做了什麼事情:
    /**
     * 新增任務
     *
     * @param path
     * @param view
     */
    private synchronized void addTask(final String path, final View view) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                ViewBeanHolder holder = new ViewBeanHolder();
                holder.view = view;
                holder.path = path;
                sendMsg(LOAD_ING, holder);
                //TODO 從記憶體中獲取
                Bitmap bitmap = LruCacheUtils.getInstance().get(path);
                if (null == bitmap) {
                    //TODO 從磁碟中獲取
                    String tempPath = getImageFromDiskUrl(path);
                    if (null != tempPath) {
                        bitmap = decodeSampledBitmapFromResource(tempPath, (ImageView)view);
                    } else {
                        if (null == bitmap) {
                            // TODO 從網路中獲取
                            bitmap = decodeSampledBitmapFromNetWork(path, (ImageView)view);
                        } else {
                            // TODO 失敗
                            sendMsg(LOAD_FAILE, holder);
                        }
                    }
                }
                //載入成功
                if (null != bitmap) {
                    LruCacheUtils.getInstance().put(path, bitmap);
                    holder.bitmap = bitmap;//唯一的
                    sendMsg(LOAD_SUCCESS, holder);
                } else {
                    //載入失敗
                    sendMsg(LOAD_FAILE, holder);
                }
            }
        };

        if (null == mPoolThreadHandler) {
            try {
                mSemapHore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mTask.add(runnable);
        mPoolThreadHandler.sendEmptyMessage(0x1000);
        mPoolSemaphore.release();//訊號量 -1
    }

    快取策略:先從記憶體中獲取,如果沒有獲取到,就從磁盤獲取,磁碟也沒有獲取到,那就從網路獲取.最後並將該bitmap設定到記憶體快取,假象:如果設定非常多的bitmap到記憶體快取中肯定會讓記憶體佔滿導致OOM,所以便採用了google推薦使用的LruCache快取演算法.該演算法可以實現固定記憶體載入,並且最近少使用的會被記憶體回收掉.

    然後在MainActivity中可以使用如下:

  ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView);

上面載入方式是直接交給內部處理.圖片預設載入RGB_565.
    ImageLoader.getInstance(MainActivity.this, 3, ImageLoader.Type.LIFO).load(IMAGES[position], holder.imageView, new LoadListener<View>() {
                @Override
                public <T> void Loading(View view, String path) {

                }

                @Override
                public <T> void LoadSuccess(View view, Bitmap bitmap, String path) {
                        ((ImageView) view).setImageBitmap(bitmap);
                }

                @Override
                public <T> void LoadError(View view, String path, String errorMsg) {
                    Log.d("Tanck","載入失敗:"+path);
                    ((ImageView)view).setImageResource(R.mipmap.ic_launcher);
                }
            });


採用幾個載入配置方式記憶體對比:

RGB_565:


約11.31MB,效果如下:


ARGB_8888:


約12.86MB效果圖如下:


可以看出差別不是很大.

但是ARGB_4444使用記憶體和RGB_565相近,但是效果很差,效果圖如下: