Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android圖片處理神器BitmapFun源碼分析

Android圖片處理神器BitmapFun源碼分析

編輯:關於Android編程

 

 

作為一名Android開發人員,相信大家對圖片OOM的問題已經耳熟能詳了,關於圖片緩存和解決OOM的開源項目也是相當的多,被大家熟知的就是Universal_image_loader和Volley了,Volley在前面的文章中已經有介紹。Universal_image_loader在圖片緩存功能方面應該算功能最強的,但是感覺很多功能用不上,所以在項目中我一般不太喜歡使用Universal_image_loader(因為本身自己的App源碼非常多,加入這些開源庫就就更大了,容易出現無法編譯的問題,因為Android貌似對一個應用中的方法個數好像有限制,貌似是655**個吧,具體多少我也記不清)。

關於處理圖片緩存上,我接觸的兩個播放器項目中,使用的都是BitmapFun,BitmapFun 是Google為Android開發提供了一個培訓教程,既然是Google提供的,那麼我覺得作為一名合格的Android開發人員很有必要學習學習,而且BitmapFun非常簡單,基本可以滿足我們項目中對於圖片緩存處理需求了。

對於開源項目的學習,我通常很少在應用層面來學習的,因為如何使用一個開源項目的相關博客已經相當多了,而且寫得都非常詳細,對於大多數開源項目它都是自帶sample的,所以如果想學習如何使用某個開源項目,好好研究sample就行了,但是我始終認為,熟悉經典開源項目源碼才是王道。好了廢話不多說,我們開始學習BitmapFun源碼吧。

1、BitmapFun結構
BitmapFun和其他開源庫的結構稍有不同,因為它僅僅是Google的培訓教程,所以BitmapFun和它的sample放在了一個工程裡面,結構圖如下:上面部分是BitmapFun的應用,下面部分是BitmapFun的源碼。

/

 

 

 

2、相關類介紹
在BitmapFun中最重要的一個類就是ImageFetcher,請求圖片主要就是調用loadImage方法,但是這個類是繼承ImageResizer,而ImageResizser是繼承ImageWorker,所以我們就從ImageWorker開始學習吧

 

ImageWorker.java
/**
	這個類用來封裝一次圖片的加載過程,包括使用從緩存中加載
 */
public abstract class ImageWorker {
    private static final String TAG = ImageWorker;
	//這個變量用於動畫效果,沒有實際意義
    private static final int FADE_IN_TIME = 200;
	//緩存,包括磁盤緩存和內存緩存
    private ImageCache mImageCache;
	//創建緩存需要的參數
    private ImageCache.ImageCacheParams mImageCacheParams;
	//加載過程中,ImageView顯示的圖片
    private Bitmap mLoadingBitmap;
	//是否使用漸變效果
    private boolean mFadeInBitmap = true;
	//是否提前退出任務,如果true,那麼圖片請求回來後是不會顯示出來的
    private boolean mExitTasksEarly = false;
	//是否暫停任務
    protected boolean mPauseWork = false;
    private final Object mPauseWorkLock = new Object();

    protected Resources mResources;

    private static final int MESSAGE_CLEAR = 0;
    private static final int MESSAGE_INIT_DISK_CACHE = 1;
    private static final int MESSAGE_FLUSH = 2;
    private static final int MESSAGE_CLOSE = 3;

    protected ImageWorker(Context context) {
        mResources = context.getResources();
    }

    /**
     * 請求一張圖片的接口
     * @param 圖片url
     * @param 要顯示這種圖片的ImageView
     */
    public void loadImage(Object data, ImageView imageView) {
        if (data == null) {
            return;
        }

        BitmapDrawable value = null;
		//如果緩存對象不為空,那麼從內存緩存中讀取對象
        if (mImageCache != null) {
            value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
        }

        if (value != null) {
            // 內存緩存命中,那麼直接顯示
            imageView.setImageDrawable(value);
        } else if (cancelPotentialWork(data, imageView)) {
			//內存緩存沒有命中,那麼創建一個圖片請求Task,將imageView作為參數
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
			//AsyncDrawable 是BitmapDrawable子類,主要用來存放當前任務的弱應用
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(mResources, mLoadingBitmap, task);
			//將asyncDrawable設置到imageView中,這樣imageView和當前任務就一一對應了
            imageView.setImageDrawable(asyncDrawable);

            //調用AsyncTask的executeOnExecutor方法,這個AsyncTask和Android系統中的AsyncTask有些區別,但是使用上一樣的
            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR, data);
        }
    }

    /**
     * 設置加載過程中的默認圖片
     *
     * @param bitmap
     */
    public void setLoadingImage(Bitmap bitmap) {
        mLoadingBitmap = bitmap;
    }

    /**
     * 將本地圖片設置為默認圖片
     *
     * @param resId
     */
    public void setLoadingImage(int resId) {
        mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
    }

    /**
     * 添加一個緩沖對象,創建磁盤緩存時需要子線程中完成
     * @param fragmentManager
     * @param cacheParams The cache parameters to use for the image cache.
     */
    public void addImageCache(FragmentManager fragmentManager,
            ImageCache.ImageCacheParams cacheParams) {
        mImageCacheParams = cacheParams;
        mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
		//完成磁盤緩存初始化
        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
    }

    /**
     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
     * caching.
     * @param activity
     * @param diskCacheDirectoryName See
     * {@link ImageCache.ImageCacheParams#ImageCacheParams(Context, String)}.
     */
    public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
        mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
        mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
    }

    /**
     * 設置是否使用漸變效果
     */
    public void setImageFadeIn(boolean fadeIn) {
        mFadeInBitmap = fadeIn;
    }

	//是否提前退出任務
    public void setExitTasksEarly(boolean exitTasksEarly) {
        mExitTasksEarly = exitTasksEarly;
        setPauseWork(false);
    }

    /**
     * Subclasses should override this to define any processing or work that must happen to produce
     * the final bitmap. This will be executed in a background thread and be long running. For
     * example, you could resize a large bitmap here, or pull down an image from the network.
     *
     * @param data The data to identify which image to process, as provided by
     *            {@link ImageWorker#loadImage(Object, ImageView)}
     * @return The processed bitmap
     */
    protected abstract Bitmap processBitmap(Object data);

    /**
     * @return The {@link ImageCache} object currently being used by this ImageWorker.
     */
    protected ImageCache getImageCache() {
        return mImageCache;
    }

    /**
     * Cancels any pending work attached to the provided ImageView.
     * @param imageView
     */
    public static void cancelWork(ImageView imageView) {
		//通過ImageView找到task,為什麼可以找到?因為imageView和task一一對應
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
		//如果task不為空,那麼取消
        if (bitmapWorkerTask != null) {
            bitmapWorkerTask.cancel(true);
            if (BuildConfig.DEBUG) {
                final Object bitmapData = bitmapWorkerTask.data;
                Log.d(TAG, cancelWork - cancelled work for  + bitmapData);
            }
        }
    }

    /**
     * Returns true if the current work has been canceled or if there was no work in
     * progress on this image view.
     * Returns false if the work in progress deals with the same data. The work is not
     * stopped in that case.
     */
    public static boolean cancelPotentialWork(Object data, ImageView imageView) {
		//通過imageView找到task
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

        if (bitmapWorkerTask != null) {
			//如果找到的task不為null,並且task的url和給定的url相同,那麼取消任務
            final Object bitmapData = bitmapWorkerTask.data;
            if (bitmapData == null || !bitmapData.equals(data)) {
                bitmapWorkerTask.cancel(true);
                if (BuildConfig.DEBUG) {
                    Log.d(TAG, cancelPotentialWork - cancelled work for  + data);
                }
            } else {
                // The same work is already in progress.
                return false;
            }
        }
        return true;
    }

    /**
     * 通過iamgeView找到對應的Task
     */
    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
        if (imageView != null) {
            final Drawable drawable = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable) {
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getBitmapWorkerTask();
            }
        }
        return null;
    }

    /**
     * 一個請求圖片的異步任務,
     */
    private class BitmapWorkerTask extends AsyncTask

分析完ImageWorker之後,我們發現在ImageWorker中已經提供了獲取網絡圖片的方法loadImage,當我調用了此方法後,首先會試圖從內存緩存獲取圖片,如果獲取成功,直接返回,如果沒有獲取成功,則啟動一個BitmapWorkerTask,使用異步線程獲取圖片,在異步線程中,首先到磁盤中獲取,如果磁盤沒有獲取,最後才從網絡獲取,我們發現在BitmapWorkerTask中是通過調用processBitmap方法完成圖片獲取的,但是這個方法是一個抽象方法,需要子類去實現,那我們到它的子類ImageResizer中

 

 

 

@Override
    protected Bitmap processBitmap(Object data) {
        return processBitmap(Integer.parseInt(String.valueOf(data)));
    }

它調用的是另外一個重載的processBitmap方法,我們看看另外一個方法吧

 

 

private Bitmap processBitmap(int resId) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, processBitmap -  + resId);
        }
        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
                mImageHeight, getImageCache());
    }

我們發現這個方法僅僅是用來加載本地圖片的,那它是如何實現網絡圖片的加載的呢,如果你把ImageResizer源碼通讀一邊,你會發現ImageResizer這個類的主要功能如下:
1、設置顯示圖片的sizse
2、從磁盤緩存中加載圖片

所以從網絡加載圖片根本不是這個類的功能,聰明的同學馬上就應該想到了ImageFetcher這個類,對!,我們就直接看看ImageFetcher這個類吧

 

 

private Bitmap processBitmap(String data) {

        final String key = ImageCache.hashKeyForDisk(data);
        FileDescriptor fileDescriptor = null;
        FileInputStream fileInputStream = null;
        DiskLruCache.Snapshot snapshot;
		//檢查mHttpDiskCache是否已經初始化,這裡一定要注意,mHttpDiskCache這個磁盤緩存是在ImageFetcher調用addImageCache時初始化的,如果你沒有調用addImageCache
		//那麼這裡就會阻塞,從而無法獲取圖片,具體情況還請大家自己分析代碼吧
        synchronized (mHttpDiskCacheLock) {
            // Wait for disk cache to initialize
            while (mHttpDiskCacheStarting) {
                try {
                    mHttpDiskCacheLock.wait();
                } catch (InterruptedException e) {}
            }
			//下面這段代碼就是從mHttpDiskCache裡面寫入圖片
            if (mHttpDiskCache != null) {
                try {
                    snapshot = mHttpDiskCache.get(key);
                    if (snapshot == null) {
                        if (BuildConfig.DEBUG) {
                            Log.d(TAG, processBitmap, not found in http cache, downloading...);
                        }
                        DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
                        if (editor != null) {
							//下載圖片邏輯在這裡
                            if (downloadUrlToStream(data,
                                    editor.newOutputStream(DISK_CACHE_INDEX))) {
                                editor.commit();
                            } else {
                                editor.abort();
                            }
                        }
                        snapshot = mHttpDiskCache.get(key);
                    }
                    if (snapshot != null) {
                        fileInputStream =
                                (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
                        fileDescriptor = fileInputStream.getFD();
                    }
                } catch (IOException e) {
                    Log.e(TAG, processBitmap -  + e);
                } catch (IllegalStateException e) {
                    Log.e(TAG, processBitmap -  + e);
                } finally {
                    if (fileDescriptor == null && fileInputStream != null) {
                        try {
                            fileInputStream.close();
                        } catch (IOException e) {}
                    }
                }
            }
        }

        Bitmap bitmap = null;
        if (fileDescriptor != null) {
			//調用ImageResizer中的方法來將mHttpDiskCache中的緩存生成指定大小的圖片
            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
                    mImageHeight, getImageCache());
        }
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {}
        }
        return bitmap;
    }



    /**
     * 從網絡通過HttpURLConnection下載圖片,並寫入到磁盤緩存
     *
     * @param urlString The URL to fetch
     * @return true if successful, false otherwise
     */
    public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        disableConnectionReuseIfNecessary();
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            Log.e(TAG, Error in downloadBitmap -  + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {}
        }
        return false;
    }

好了,對於Bitmapfun的整個代碼邏輯我就簡單的分析到這裡吧,其實了解了Bitmapfun的代碼邏輯後,我們完全可以對其進行優化,我在這裡僅僅提出一點可以優化的地方,優化的方法就交給大家完成吧

比如BitmapWorkerTask在獲取圖片的時候先是讀取磁盤緩存,然後從網絡獲取,也就是說如果讀取本地和讀取網絡圖片時在同一條線程中完成的,這個時候就有可能出現一個問題,本地圖片存在卻無法加載出來:例如:在網絡條件不好的情況下,前面的五個圖片請求剛好用完了所有的線程,由於網絡條件不好,一直沒有返回,而第六個圖片剛好有緩存,那麼它是無法加載出來的,因為沒有線程了,所以解決方案就是學習Volley(我前面的文章對於Volley已經介紹了)中的解決方案,讓一條線程專門處理本地圖片,其他線程用於處理網絡圖片。

就寫到這裡吧,如果大家有什麼沒看明白或者我寫錯了的,歡迎留言.....

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved