1. 程式人生 > >安卓專案實戰之Gif圖片載入的最佳實踐android-gif-drawable開源庫的使用

安卓專案實戰之Gif圖片載入的最佳實踐android-gif-drawable開源庫的使用

前言

在平時的專案開發中,我們或多或少會遇到載入gif圖片這樣的需求,但是Android的ImageView又無法直接載入Gif圖片,面對這樣的需求我們一般都會想到使用支援載入gif動圖的Glide第三方庫來進行實現,但是使用過程中發現Glide在載入大的gif圖片時會出現卡頓,而且載入速度很慢,這很影響使用者體驗,所以又從網上找到另一個專門應對gif圖片載入的另外一個開源庫GifView,但是使用中發現當頻繁的載入過大的圖片的時候,會很容易出現OOM,最後機緣巧合之下了解到了android-gif-drawable這個開源庫,它也是用來進行gif圖片的載入顯示的,底層解碼使用C實現,極大的提高了解碼效率,並且是通過JNI來渲染幀的,相比Glide等框架提高了gif圖片載入的速度,同時很大程度上避免了OOM。

android-gif-drawable的整合

在app的build.gradle檔案中新增如下依賴:

dependencies {
    compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
}

android-gif-drawable的使用

android-gif-drawable有四種控制元件:GifImageView、GifImageButton、GifTextView、GifTextureView。這裡以ImageView為例進行介紹。

載入本地GIF圖片

1.直接在xml佈局檔案中進行指定,如下:

<pl.droidsonroids.gif.GifImageView
    android:id="@+id/fragment_gif_local"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/dog"/>

GifImageView會自動識別”Android:src”或者”android:background”的內容是否Gif檔案,如果是Gif就播放Gif檔案,如果是普通的靜態圖片,例如是png,jpg的,這個時候,gifImageView等這些控制元件的效果和ImageView是一樣的。這樣其實Gif就已經可以播放了,就這麼簡單。

2.通過程式碼動態的指定要載入的gif圖,程式碼如下:

setImageResource(int resId)
setBackgroundResource(int resId)

除了上面這兩種方法以外,還支援多種來源,如下:

//1. 構建GifDrawable物件,根據來源不同選擇不同的構造方法進行建立
// 從Assets中獲取
GifDrawable gifFromAssets = new GifDrawable(getAssets(), "anim.gif");
// 從drawable或者raw中獲取
GifDrawable gifFromResource = new GifDrawable(getResources(), R.drawable.anim);
// 從檔案中獲取
File gifFile = new File(getFilesDir(), "anim.gif");
GifDrawable gifFromFile = new GifDrawable(gifFile);
//從輸入流中獲取,如果GifDrawable不再使用,輸入流會自動關閉。另外,你還可以通過呼叫recycle()關閉不再使用的輸入流
InputStream inputStream = new FileInputStream(gifFile);
BufferedInputStream bis = new BufferedInputStream(inputStream, 1024 * 1024);
GifDrawable gifFromStream = new GifDrawable(bis);
//2. 設定給GifImageView控制元件
gifImageView.setImageDrawable(gifFromResDrawable); 

GifDrawable是用於該開源庫的Drawable類。構造方法大致有9種:

//1. asset file
GifDrawable gifFromAssets = new GifDrawable( getAssets(), "anim.gif" );

//2. resource (drawable or raw)
GifDrawable gifFromResource = new GifDrawable( getResources(), R.drawable.anim );

//3. byte array
byte[] rawGifBytes = ...
GifDrawable gifFromBytes = new GifDrawable( rawGifBytes );

//4. FileDescriptor
FileDescriptor fd = new RandomAccessFile( "/path/anim.gif", "r" ).getFD();
GifDrawable gifFromFd = new GifDrawable( fd );

//5. file path
GifDrawable gifFromPath = new GifDrawable( "/path/anim.gif" );

//6. file
File gifFile = new File(getFilesDir(),"anim.gif");
GifDrawable gifFromFile = new GifDrawable(gifFile);

//7. AssetFileDescriptor
AssetFileDescriptor afd = getAssets().openFd( "anim.gif" );
GifDrawable gifFromAfd = new GifDrawable( afd );

//8. InputStream (it must support marking)
InputStream sourceIs = ...
BufferedInputStream bis = new BufferedInputStream( sourceIs, GIF_LENGTH );
GifDrawable gifFromStream = new GifDrawable( bis );

//9. direct ByteBuffer
ByteBuffer rawGifBytes = ...
GifDrawable gifFromBytes = new GifDrawable( rawGifBytes ); 

載入網路Gif圖片

如果gif是網路圖片,這個庫不支援直接載入一個url,但是提供了一個GifDrawable 類,可以通過傳入本地gif圖片的路徑,輸入流等方式構造建立GifDrawable物件(參見上面的9種構造方法),這裡我們採用的辦法是將Gif圖片下載到快取目錄中,然後從磁碟快取中獲取該Gif動圖進行顯示。 1、下載工具DownloadUtils.java

public class DownloadUtils {
    private final int DOWN_START = 1; // Handler訊息型別(開始下載)
    private final int DOWN_POSITION = 2; // Handler訊息型別(下載位置)
    private final int DOWN_COMPLETE = 3; // Handler訊息型別(下載完成)
    private final int DOWN_ERROR = 4; // Handler訊息型別(下載失敗)
    private OnDownloadListener onDownloadListener;

    public void setOnDownloadListener(OnDownloadListener onDownloadListener) {
        this.onDownloadListener = onDownloadListener;
    }

    /**
     * 下載檔案
     *
     * @param url      檔案路徑
     * @param filepath 儲存地址
     */
    public void download(String url, String filepath) {
        MyRunnable mr = new MyRunnable();
        mr.url = url;
        mr.filepath = filepath;
        new Thread(mr).start();
    }

    @SuppressWarnings("unused")
    private void sendMsg(int what) {
        sendMsg(what, null);
    }

    private void sendMsg(int what, Object mess) {
        Message m = myHandler.obtainMessage();
        m.what = what;
        m.obj = mess;
        m.sendToTarget();
    }

    Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DOWN_START: // 開始下載
                    int filesize = (Integer) msg.obj;
                    onDownloadListener.onDownloadConnect(filesize);
                    break;
                case DOWN_POSITION: // 下載位置
                    int pos = (Integer) msg.obj;
                    onDownloadListener.onDownloadUpdate(pos);
                    break;
                case DOWN_COMPLETE: // 下載完成
                    String url = (String) msg.obj;
                    onDownloadListener.onDownloadComplete(url);
                    break;
                case DOWN_ERROR: // 下載失敗
                    Exception e = (Exception) msg.obj;
                    e.printStackTrace();
                    onDownloadListener.onDownloadError(e);
                    break;
            }
            super.handleMessage(msg);
        }
    };

    class MyRunnable implements Runnable {
        private String url = "";
        private String filepath = "";

        @Override
        public void run() {
            try {
                doDownloadTheFile(url, filepath);
            } catch (Exception e) {
                sendMsg(DOWN_ERROR, e);
            }
        }
    }

    /**
     * 下載檔案
     *
     * @param url      下載路勁
     * @param filepath 儲存路徑
     * @throws Exception
     */
    private void doDownloadTheFile(String url, String filepath) throws Exception {
        if (!URLUtil.isNetworkUrl(url)) {
            sendMsg(DOWN_ERROR, new Exception("不是有效的下載地址:" + url));
            return;
        }
        URL myUrl = new URL(url);
        URLConnection conn = myUrl.openConnection();
        conn.connect();
        InputStream is = null;
        int filesize = 0;
        try {
            is = conn.getInputStream();
            filesize = conn.getContentLength();// 根據響應獲取檔案大小
            sendMsg(DOWN_START, filesize);
        } catch (Exception e) {
            sendMsg(DOWN_ERROR, new Exception(new Exception("無法獲取檔案")));
            return;
        }
        FileOutputStream fos = new FileOutputStream(filepath); // 建立寫入檔案記憶體流,
        // 通過此流向目標寫檔案
        byte buf[] = new byte[1024];
        int numread = 0;
        int temp = 0;
        while ((numread = is.read(buf)) != -1) {
            fos.write(buf, 0, numread);
            fos.flush();
            temp += numread;
            sendMsg(DOWN_POSITION, temp);
        }
        is.close();
        fos.close();
        sendMsg(DOWN_COMPLETE, filepath);
    }

    interface OnDownloadListener{
        public void onDownloadUpdate(int percent);

        public void onDownloadError(Exception e);

        public void onDownloadConnect(int filesize);

        public void onDownloadComplete(Object result);
    }
} 

2、呼叫DonwloadUtils進行下載,下載完成後載入本地圖片

//1. 下載gif圖片(檔名自定義可以通過Hash值作為key)
DownloadUtils downloadUtils = new DownloadUtils();
downloadUtils.download(gifUrlArray[0],
                getDiskCacheDir(getContext())+"/0.gif");
//2. 下載完畢後通過“GifDrawable”進行顯示
downloadUtils.setOnDownloadListener(new DownloadUtils.OnDownloadListener() {
            @Override
            public void onDownloadUpdate(int percent) {
            }
            @Override
            public void onDownloadError(Exception e) {
            }
            @Override
            public void onDownloadConnect(int filesize) {
            }
            //下載完畢後進行顯示
            @Override
            public void onDownloadComplete(Object result) {
                try {
                    GifDrawable gifDrawable = new GifDrawable(getDiskCacheDir(getContext())+"/0.gif");
                    mGifOnlineImageView.setImageDrawable(gifDrawable);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }); 
//獲取快取的路徑
public String getDiskCacheDir(Context context) {
    String cachePath = null;
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
            || !Environment.isExternalStorageRemovable()) {
        // 路徑:/storage/emulated/0/Android/data/<application package>/cache
        cachePath = context.getExternalCacheDir().getPath();
    } else {
        // 路徑:/data/data/<application package>/cache
        cachePath = context.getCacheDir().getPath();
    }
    return cachePath;
}