1. 程式人生 > >Android_效能優化之ViewPager載入成百上千高清大圖oom解決方案

Android_效能優化之ViewPager載入成百上千高清大圖oom解決方案

歡迎加入技術談論群:714476794

一、背景

最近做專案需要用到選擇圖片上傳,類似於微信、微博那樣的圖片選擇器,ContentResolver讀取本地圖片資源並用RecyclerView+Glide載入圖片顯示就搞定列表的顯示,這個沒什麼大問題,重點是,點選圖片進入大圖瀏覽,比如你相簿有幾百張圖片,也就意味著在ViewPager中需要載入幾百個view,況且手機拍出來的圖片都是1-2千萬左右畫素的高清大圖(筆者手機2千萬畫素 也就是拍照出來的照片3888*5152),大小也有5-7個兆,ViewPager滑動不了十幾張就oom了,即是對圖片做了壓縮處理,把圖片解析度降低至1366*960,大小壓縮至150k以下,並且在ViewPager的destroyItem方法做了bitmap資源的回收,雖然效果好了點,這也抵擋不了oom的降臨(網上查詢的方案都是壓縮、使用第三方控制元件、回收,其實這都沒用,可能他們沒有真正體驗過ViewPager載入幾百上千張大圖的感受),瀏覽到了第50-70張的時候就oom了 記憶體一直暴漲,根本回收不了的,不信你們試試,壓縮和回收根本不能根治問題,那麼怎麼解決呢?研究了微信和微博,他們怎麼也不會oom,最後我想到了一種解決方案。

二、方案實施

1、以往的普通做法

部分程式碼:

List<SubsamplingScaleImageView> mViews = new ArrayList<>();
        
        int size = mDatas.size();
        for (int i = 0; i < size; i++) {
            SubsamplingScaleImageView view = new SubsamplingScaleImageView(this);
            mViews.add(view);
        }

        mBinding.viewpager.setAdapter(new MyAdapter());

class MyAdapter extends PagerAdapter {

        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, final int position) {

            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                    ViewPager.LayoutParams.MATCH_PARENT,ViewPager.LayoutParams.MATCH_PARENT);
            final SubsamplingScaleImageView imageView = mViews.get(position);
            imageView.setLayoutParams(params);

            final String url = mDatas.get(position);
            String cacheExists = cacheExists(url);
            if(TextUtils.isEmpty(cacheExists)) {//沒快取 需要壓縮(壓縮耗時 非同步)
                new AsyncTask<Void, Void, String>() {
                    @Override
                    protected String doInBackground(Void... voids) {
                        String cacheNoExistsPath = getCacheNoExistsPath(url);
                        BitmapCompressUtils.compressBitmap(url, cacheNoExistsPath);
                        File file = new File(cacheNoExistsPath);
                        if (file.exists()) {//存在表示成功
                            return cacheNoExistsPath;
                        } else {
                            return url;
                        }
                    }

                    @Override
                    protected void onPostExecute(String s) {
                        imageView.setImage(ImageSource.uri(s));
                    }

                }.execute();


            } else {//有快取 直接顯示
                imageView.setImage(ImageSource.uri(cacheExists));
            }

            container.addView(imageView);
            return imageView;

        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {

            SubsamplingScaleImageView imageView = mViews.get(position);
            if(imageView != null) {
                imageView.recycle();
            }

            container.removeView(imageView);

        }
    }

/**
     * 判斷當前圖片url對應的壓縮過的快取是否存在 ""表示不存在
     *
     * @param url 圖片路徑
     * @return
     */
    private String cacheExists(String url) {
        try {
            File fileDir = new File(mCacheRootPath);
            if(!fileDir.exists()) {
                fileDir.mkdirs();
            }

            File file = new File(mCacheRootPath,new StringBuffer().append(MD5EncryptorUtils.md5Encryption(url)).toString());
            if(file.exists()) {
                return file.getAbsolutePath();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "";
    }

    public String getCacheNoExistsPath(String url) {
        File fileDir = new File(mCacheRootPath);
        if(!fileDir.exists()) {
            fileDir.mkdirs();
        }


        return new StringBuffer().append(mCacheRootPath)
                .append(MD5EncryptorUtils.md5Encryption(url)).toString();
    }

可以看到,這裡筆者通過自己的壓縮演算法(上一篇文章Android_NDK圖片壓縮之Libjpeg庫使用 )做了圖片壓縮,並快取,細心的朋友應該有發現mViews集合新增的view個數是mDatas的size大小個數,這樣就會導致一個問題ViewPager一直向下滑動的時候,記憶體一直是增加的,即是做了資源回收,也是不能解決問題(況且筆者這裡展示圖片的控制元件是SubsamplingScaleImageView 很不錯的大圖區域性載入控制元件 能有效防止oom),大家可以試試,大量圖片的時候還是會oom,這得歸根於viewpager載入的圖片數量問題。


2、解決方案:

圖片壓縮也做了,資源回收也做了,但是ViewPager載入越來越多圖片的時候就會oom 你避免不了,不信你試試;

這裡就要用到ViewPager的view的重用機制(自己理解的),也就是mViews我們固定給定個數量,如4,這樣ViewPager的i實際所需要的item也就只有4個。

修改後的部分程式碼:

 for (int i = 0; i < 4; i++) {
            SubsamplingScaleImageView view = new SubsamplingScaleImageView(this);
            mViews.add(view);
        }

        mBinding.viewpager.setAdapter(new MyAdapter());

class MyAdapter extends PagerAdapter {

        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, final int position) {

            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                    ViewPager.LayoutParams.MATCH_PARENT,ViewPager.LayoutParams.MATCH_PARENT);

            int i = position % 4;
            final SubsamplingScaleImageView imageView = mViews.get(i);
            imageView.setLayoutParams(params);

            final String url = mDatas.get(position);
            String cacheExists = cacheExists(url);
            if(TextUtils.isEmpty(cacheExists)) {//沒快取 需要壓縮(壓縮耗時 非同步)
                new AsyncTask<Void, Void, String>() {
                    @Override
                    protected String doInBackground(Void... voids) {
                        String cacheNoExistsPath = getCacheNoExistsPath(url);
                        BitmapCompressUtils.compressBitmap(url, cacheNoExistsPath);
                        File file = new File(cacheNoExistsPath);
                        if (file.exists()) {//存在表示成功
                            return cacheNoExistsPath;
                        } else {
                            return url;
                        }
                    }

                    @Override
                    protected void onPostExecute(String s) {
                        imageView.setImage(ImageSource.uri(s));
                    }

                }.execute();


            } else {//有快取 直接顯示
                imageView.setImage(ImageSource.uri(cacheExists));
            }

            container.addView(imageView);
            return imageView;

        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            int i = position % 4;
            SubsamplingScaleImageView imageView = mViews.get(i);
            if(imageView != null) {
                imageView.recycle();
            }

            container.removeView(imageView);

        }

很簡單的修改 就能有效防止oom  利用position%4拿到第幾個控制元件從mViews取值,保證了viewpager載入的mViews儲存的圖片為4個

看一直向下滑動的記憶體走勢圖



記憶體基本維持穩定

三、demo演示

因為要讀取相簿就沒再模擬器執行錄製gif ,直接截圖


四、總結

這個只是簡單的演示,實際專案中的相簿比這個複雜多了,簡單說就是要壓縮,要回收,View重用。

有什麼問題可以留言