android圖片獲取加載小結

獲取方式

1.內存

內存緩存主要使用LRU緩存算法,引用support-v4中的LruCache,
通過鍵值對的形式獲取到相應的bitmap,配置如下:

      //初始化緩存策略
        int maxMem = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMem / 8;

        mMemCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {

               //計算緩存對象大小
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };

2.磁盤緩存

磁盤緩存也是採用LRU緩存算法,一般從網上直接獲取DiskLruCache源碼,配置好,通過鍵值對的形式獲取。
DiskLruCache的入口函數如下:

 /*
  *  執行初始化磁盤緩存操作,
  *  注意directory要先mkdir
  */
 public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);

    /**
     * 獲取磁盤緩存路徑
     * @param context
     * @param uniqueName
     * @return 
     */
    public static File getDiskCacheDir(Context context, String uniqueName) {
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir
        // otherwise use internal cache dir
        final String cachePath =
                Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                        !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                                context.getCacheDir().getPath();

        return new File(cachePath + File.separator + uniqueName);
    }

directory:爲緩存存儲路徑,
appVersion: 版本號,改變後緩存會改變,一般設爲1
valueCount: 單節點對應數據個數,一般爲1
maxSize: 最大緩存容量,快滿會自動清理,單位爲Byte

注意的是這裏的key值不能直接存圖片url,而是需要將其轉化爲md5值,避免特殊字符干擾。


3.磁盤I/O

通過ContentResolver直接遍歷獲取磁盤上的圖片即可。

//獲取圖片存儲路徑
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver mContentResolver = getContentResolver();
//獲得遊標並執行相關數據庫表的查詢
Cursor mCursor = mContentResolver.query(mImageUri,null,                   MediaStore.Images.Media.MIME_TYPE + "=? or "+                       MediaStore.Images.Media.MIME_TYPE +"=?",new String[]{                              "image/jpeg","image/png"},                      MediaStore.Images.Media.DATE_MODIFIED);
if(mCursor==null){
  return;
}                  
while(mCursor.moveToNext()){
  //獲取圖片的路徑
String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
  //TODO 儲存path
}

4.網絡

直接通過HttpURLConnection獲取到IO流便可

                //網絡直接獲取到stream,注意getInputStream操作才執行網絡請求
                HttpURLConnection connect = null;
                InputStream in = null;

                try {
                    final URL urlString = new URL(url);

                    connect = (HttpURLConnection) urlString.openConnection();
                    connect.setDoInput(true);
                    connect.connect();
                    in = connect.getInputStream();
                    }...

使用事項

1.bitmap佔用內存過大問題

android裏bitmap是個大胖子,加載大量圖片時需要對其進行縮放處理,而加載單張全圖時,則注意將對應的imageView設置爲弱引用,這樣gc會更快回收。
bitmap縮放使用以下方法:

      /**
     * 根據文件描述符來傳入圖片流並壓縮成制定的bitmap
     * @param fd
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeByFD(FileDescriptor fd, int reqWidth, int reqHeight) {

        BitmapFactory.Options options  = new BitmapFactory.Options();

        //爲true時是隻取樣,幾乎不佔內存
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFileDescriptor(fd,null,options);
        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);

        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd,null,options);

    }


    /**
     * 計算壓縮比,一般是2的倍數,爲1時保留原來的格式
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        if (reqWidth == 0 && reqHeight == 0) {
            return 1;
        }

        int realWidth = options.outWidth;
        int realHeight = options.outHeight;
        int inSampleSize = 1;

        if (reqWidth < realWidth || reqHeight < realHeight) {

            realWidth /= 2;
            realHeight /= 2;
            while((realWidth/inSampleSize)>=reqHeight&&(realHeight/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }

            //這裏是考慮到極端情況,寬度比高度大很多的圖片需要繼續縮放
            //總的圖片大小
            long totalPixels = realWidth * realHeight / inSampleSize;

            //imageView提供的大小
            final long totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels > totalReqPixelsCap) {
                inSampleSize *= 2;
                totalPixels /= 2;
            }
        }

        return inSampleSize;
    }

不一定是decodeByFile,可以利用BitmapFactory提供的其他decode方法,但注意的是,使用decodeStream時,兩次操作會改變fileInputStream的位置信息,使得第二次decode獲取到的爲null,根本原因是fileInputStream爲有序的。

另外MedioStore.Images.Thumbnails中提供了本地圖片縮略圖的獲取方式,如下:

                String[] projection = new String[]{MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
                String[] condition = new String[]{url};
                Cursor cursor = mContext.getContentResolver().query(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, MediaStore.Images.Media.DATA + " =? ",
                        condition, null);
                if (cursor == null) {
                    return;
                }
                if (cursor.moveToNext()) {
                    long imageId = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));


                    String data = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

                    bitmap = MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(), imageId,
                            MediaStore.Images.Thumbnails.MICRO_KIND, null);
                }

getThumbnail可以獲取到本地圖片的縮略圖,且縮略圖有兩種,分別是MINI_KIND 512 x 384和MICRO_KIND 96 x 96 此方法與bitmapFactory的decode的效率還有待深入比較。
另外,這裏有個小bug,就是使用getContentResolver查找不到MICRO_KIND類型圖片的路徑,MINI_KIND是可以獲取的,歡迎有興趣的朋友一起討論。


2線程安全問題

大量的圖片操作伴隨高耗時,所以除了內存緩存中的獲取,一般採用線程池來異步加載圖片,保證不阻礙UI主線程。
而ImageView的setBitmap操作要在主線程中操作,所以完成圖片處理需要及時把圖片信息通過主線程的handler傳遞message出去。

通過AsyncTask的executeOnExecutor方法可以自己配置併發使用的線程池,AsyncTask自帶的線程池是串行的。


3界面卡頓問題

1)listview和gridview滑動過快,導致頻繁的圖片加載
2)listview和gridview的imageView控件複用,導致bitmap在未被加載完全時,顯示被複用的bitmap

解決方案:
1 ) 監聽滑動狀態,通過OnScrollStateChanged監聽滑動操作,如果使快速滑動時,不執行i/o和網絡請求,代碼如下:

        mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {

                    mPhotoAdapter.setmIsScroll(false);
                    mPhotoAdapter.notifyDataSetChanged();
                } else {
                    mPhotoAdapter.setmIsScroll(true);
                }
            }
        });

2) getView中對imageView的bitmap的url進行記錄,即設置完bitmap後,將圖片的url通過imageView.setTag保存起來,在getView開始時就用實際的url與getTag裏對比,如果不一致,則設置bitmap爲默認圖像,避免圖片錯位。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章