Android開發者文檔筆記(三)

**在APP中使用圖片和動畫


*如何有效地加載容量大的位圖


圖片有各種大小和形狀,在很多情況下它們的大小超過了所要求的平臺的用戶界面的分辨率。例如,系統應用程序顯示圖片庫通常使用Android設備的相機分辨率遠高於你的設備的屏幕密度。鑑於手機的運行內存有限,理想情況下我們只需要加載一個適應UI組件大小的低分辨率版本的圖片到於內存中。




在各種資源中,Bitmapfactory類提供了許多加工方法(如加工字節數組,文件,資源等等)用來生成位圖Bitmap。這些方法很容就把內存分配出去,從而導致內存泄漏異常。爲此,在生成位圖之前,我們必須查看你所要生成的位圖大小。


BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;


在知道了大小之後,我們就可以決定以什麼樣的方式載入圖片,通過在Bitmapfactory.Options類中設置inSampleSize爲true來告訴編譯器,我們所要進行的改變。例如,一個分辨率爲2048x1536的圖像變成inSampleSize=4的分辨率爲512x384的圖像,此時圖片所佔用的內存就是0.75MB,而不是12MB的完整圖像(ARGB_8888的圖片配置)。有一個方法可以計算樣本大小值inSampleSzie=2的冪次方的目標的高度和寬度:

//計算縮小的比例值
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;


    if (height > reqHeight || width > reqWidth) {


        final int halfHeight = height / 2;
        final int halfWidth = width / 2;


        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }


    return inSampleSize;
}





//重繪縮小後的位圖
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {


    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);


    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);


    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}


//在UI組件中添加圖像
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));




這就是降分辨率的整個過程,計算完整圖像的大小,再算出降到目標分辨率所需要的參數值,通過調整圖像的邊界,重新生成Bitmap對象





*在AsyncTask中處理圖像




對於從外部內存獲取或者從網絡中下載下來的圖像的分辨率的降低處理不應該放在UI線程中進行,由於有網速,圖片大小以及處理能力的差異,降分辨率處理的時間機油可能變得很長,若在UI線程中進行就會導致線程阻塞而使app崩潰。

//下載
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;


    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }


    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }


    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}


//啓動
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}



在上述代碼中,使用的是AsyncTask這個方法,並且添加了垃圾回收機制用來回收不用的圖像資源。當然,如果加載量多的話,可以使用一些第三方的框架,如Volley。




















*併發處理:
例子下載


在例子中使用了WeakReference這個方法,弱引用的概念是:在Java中一個對象要被回收,需要有兩個條件:1、沒有任何引用指向它。2、能被Java中的垃圾回收機制運行。
而簡單的對象在被使用完後,是可以被直接回收,但是cache中緩存的數據是不會被GC運行的,所以也就不會被回收,需要手動清除,所以Java中引入了Weak Reference這個概念,
當一個對象僅僅被Weak Reference所指向時,必然可以被GC運行和回收


弱引用對象:WeakReference<BitmapWorkerTask> weakReferenceTask = new WeakReference(BitmapWorkerTask)(bitmapWorkerTask);
強引用:BitmapWorkerTask task = newBitmapWorkerTask();
判斷對象是否被回收:weakReference.get();
如果對象創建後被閒置,就會自動回收


Soft Reference比Weak Reference多一個條件被回收:當運行內存不足時


使用弱引用的三個前提:1、對象有需要被Cache的價值,2、對象不容易被回收。3.對象佔內存





*MemoryLruCache緩存圖片:



//create a LruCacahe 
private LruCache<String, Bitmap> mMemoryCache;


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);


    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;


    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}


public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}


public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}










//loading bitmap
public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);


    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}




//how to load bitmap from disk or internet
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}	









*DiskLruCache緩存圖片


private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}


class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);


        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);


        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }


        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);


        return bitmap;
    }
    ...
}


public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }


    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}


public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}


// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
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);
}



*其中使用到線程的同步控制,Disk的訪問和寫入都是不能同時寫入或讀取的,在這些地方需要多加註意。還有就是對於Disk緩存的讀寫,必須建立在Disk緩存初始化完成的前提下,因爲初始化操作不是放在MainThread中進行的。



*當手機設備的配置改變時,比如轉屏後,無論是Activity還是Fragment都需要重新創建。此時,如果你的Activity或者Fragment的UI中有大量數據,如有大量的圖片顯示,這樣重新創建就會消耗更多重複的資源,當然Activity中是自動進行緩存,提供現場恢復。而Fragment就需要我們用Memory Cache進行手動緩存。以往設置Fragment現場恢復的時候,都認爲這個方式有點雞肋,因爲並不能打到跟Activity一樣的效果,但是,看了文檔,才發現,Fragment必須手動申請Memoery Cache緩存纔可以。



private LruCache<String, Bitmap> mMemoryCache;


@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}


class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;


    public RetainFragment() {}


    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}




*管理位圖內存:
看不懂,實力不夠,沒明白意圖


*提示:一般的androdi設備分配給一個app的內存是32mb,而一個Cache比較合理的是使用其內存的八分之一,假如一張爲1024*843分辨率的32位真彩照片,那麼這張照片的大小就應該爲1024*843*4 bytes,而4MB/(1024*843*4 bytes)則是能緩存的張數。


  另外,8位的真彩照片一個像素點是8bit,1字節
16位的真彩照片一個像素點是16bit,2字節
24位的真彩照片一個像素點是24bit,3字節
32位的真彩照片一個像素點是32bit,4字節


黑白二值圖像,不壓縮的情況下一個像素點是1bit
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章