Android播放webp和gif的一種方法(接上篇)

上一篇我們介紹了Android中播放Webp動畫的一種方法FrameSequenceDrawable的實現(xiàn)原理嗜傅,在上篇文章的最后我做了一個預(yù)告厨钻,本篇就是接上篇的內(nèi)容磷醋,抽象了FrameSequence思想實現(xiàn)了可以播放webp和gif的AnimationSequenceDrawable淤井,主要還是介紹實現(xiàn)的原理滚局,如果你沒有看上一篇的內(nèi)容史简,建議一定要看一下乃秀,了解整個播放的原理后在看本篇,這篇對播放原理不會在介紹圆兵,上篇的地址Android中播放webp動畫的一種方式:FrameSequenceDrawable

播放效果

在介紹之前跺讯,我們還是先看一下播放效果:


播放gif

播放webp

我想直接用

如果你想直接使用,本篇就不直接貼代碼了殉农,可以去github查看

沒有介紹的類:FrameSequence

上篇在介紹FrameSequenceDrawable時刀脏,并沒有詳細的查看FrameSequence的源碼,那么我們現(xiàn)在先來看看FrameSequence在FrameSequenceDrawable都有哪些地方使用到了

public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) {
    if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();
    mFrameSequence = frameSequence;
    mFrameSequenceState = frameSequence.createState();
    //...省略
    mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
    initializeDecodingThread();
}
@Override
protected void finalize() throws Throwable {
    try {
        mFrameSequenceState.destroy();
    } finally {
        super.finalize();
    }
}

/**
  * Runs on decoding thread, only modifies mBackBitmap's pixels
  */
 private Runnable mDecodeRunnable = new Runnable() {
     @Override
     public void run() {
        //...省略
         boolean exceptionDuringDecode = false;
         long invalidateTimeMs = 0;
         try {
             invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
         } catch (Exception e) {
             // Exception during decode: continue, but delay next frame indefinitely.
             Log.e(TAG, "exception during decode: " + e);
             exceptionDuringDecode = true;
         }
        //...省略
        
     }
 };

private void scheduleDecodeLocked() {
   mState = STATE_SCHEDULED;
   mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
   sDecodingThreadHandler.post(mDecodeRunnable);
}

@Override
public void draw(Canvas canvas) {
    //...省略
    if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
                mCurrentLoop++;
                if ((mLoopBehavior == LOOP_FINITE && mCurrentLoop == mLoopCount) ||
                        (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
                    continueLooping = false;
                }
    }
    //...省略
}
@Override
public int getIntrinsicWidth() {
    return mFrameSequence.getWidth();
}

@Override
public int getIntrinsicHeight() {
    return mFrameSequence.getHeight();
}

@Override
public int getOpacity() {
    return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
}

通過代碼和上篇介紹過的內(nèi)容來看统抬,F(xiàn)rameSequence主要就是封裝了幀序列的一些信息火本,長扰才、寬鲤嫡、幀數(shù)、循環(huán)次數(shù)仗岖、透明度等信息金麸,所以在FrameSequenceDrawable中擎析,主要就是使用FrameSequence獲取這些信息,mFrameSequenceState的類型是FrameSequence.State它主要的作用就是解析某一幀挥下,和釋放資源內(nèi)存揍魂,主要解析的一些方法都是在native層實現(xiàn)的,我們可以看一下FrameSequence的源碼中定義的一些方法

public class FrameSequence {
    static {
        System.loadLibrary("framesequence");
    }

    private final long mNativeFrameSequence;
    private final int mWidth;
    private final int mHeight;
    private final boolean mOpaque;
    private final int mFrameCount;
    private final int mDefaultLoopCount;

    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

    public boolean isOpaque() {
        return mOpaque;
    }

    public int getFrameCount() {
        return mFrameCount;
    }

    public int getDefaultLoopCount() {
        return mDefaultLoopCount;
    }

    private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length);

    private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage);

    private static native FrameSequence nativeDecodeByteBuffer(ByteBuffer buffer, int offset, int capacity);

    private static native void nativeDestroyFrameSequence(long nativeFrameSequence);

    private static native long nativeCreateState(long nativeFrameSequence);

    private static native void nativeDestroyState(long nativeState);

    private static native long nativeGetFrame(long nativeState, int frameNr,
                                              Bitmap output, int previousFrameNr);

    @SuppressWarnings("unused") // called by native
    private FrameSequence(long nativeFrameSequence, int width, int height,
                          boolean opaque, int frameCount, int defaultLoopCount) {
        ...
    }

    public static FrameSequence decodeByteArray(byte[] data) {
        return decodeByteArray(data, 0, data.length);
    }

    public static FrameSequence decodeByteArray(byte[] data, int offset, int length) {
        ...
        return nativeDecodeByteArray(data, offset, length);
    }

    public static FrameSequence decodeByteBuffer(ByteBuffer buffer) {
        ...
        return nativeDecodeByteBuffer(buffer, buffer.position(), buffer.remaining());
    }

    public static FrameSequence decodeStream(InputStream stream) {
        ...
        return nativeDecodeStream(stream, tempStorage);
    }

    State createState() {
        ...
        return new State(nativeState);
    }

    @Override
    protected void finalize() throws Throwable {
        ...
    }

    static class State {
        private long mNativeState;

        public State(long nativeState) {}

        public void destroy() {}

        // TODO: consider adding alternate API for drawing into a SurfaceTexture
        public long getFrame(int frameNr, Bitmap output, int previousFrameNr) {
                    }
            return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr);
        }
    }
}

可以發(fā)現(xiàn)棚瘟,所以有和webp解析圖片相關(guān)的方法都被封裝到了FrameSequence中现斋,F(xiàn)rameSequenceDrawable中需要獲取某一張圖片時,只需要調(diào)用內(nèi)部FrameSequence.State對象去解析就好了

是否可以使用其它的解析so庫偎蘸?

如果理解了上面的設(shè)計方式的話庄蹋,那么使用其它解析庫來解析webp就是很容易的事情了瞬内,開篇提到的AnimationSequenceDrawable中,我們使用了Fresco的animated-webp和animated-gif來解析每一幀對象限书,實現(xiàn)了AnimationSequenceDrawable在使用了不同的Sequence時可以播放webp或者gif的圖片虫蝶,整體的類關(guān)系如下圖


uml.png

通過上面的uml圖,可以看到倦西,

  • AnimationSequenceDrawable就是FrameSequenceDrawable在這個項目中的名字能真,它和FrameSequenceDrawable的代碼幾乎時一摸一樣的(拷貝過來修改的 嘿嘿)
  • 抽象了BaseAnimationSequence它是AnimationSequenceDrawable唯一依賴的幀序列
  • FrescoSequence繼承BaseAnimationSequence實現(xiàn)的幀序列,里面的解析方法主要是用了Fresco的庫
  • BaseSequenceFactory是一個抽象的工廠扰柠,它的唯一作用就是根據(jù)傳入的InputStream生成一個BaseAnimationSequence粉铐,其中FrescoSequence的內(nèi)部類FrescoWebpSequenceFactory和FrescoGifSequenceFactory分別是根據(jù)傳入的InputStream生成Webp和GIF的FrescoSequence

在看這些類的源碼之前,看一下這個既可以播放webp又可以播放gif是如何使用的耻矮,這里主要是直接使用AnimationSequenceDrawable秦躯,如果你覺得麻煩可以看gifhub項目里封裝的AnimationImageView

public void playGif(){
    InputStream in = null;
    try {
        in = getResources().getAssets().open("lion.gif");
        BaseSequenceFactory factory = FrescoSequence.getSequenceFactory(FrescoSequence.GIF);
        final AnimationSequenceDrawable drawable = new AnimationSequenceDrawable(factory.createSequence(in));
        drawable.setLoopCount(1);
        drawable.setLoopBehavior(AnimationSequenceDrawable.LOOP_FINITE);
        drawable.setOnFinishedListener(new AnimationSequenceDrawable.OnFinishedListener() {
            @Override
            public void onFinished(AnimationSequenceDrawable frameSequenceDrawable) {

            }
        });
        mGifImage.setImageDrawable(drawable);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void playWebp(){
    InputStream in = null;
    try {
        in = getResources().getAssets().open("rmb.webp");
        BaseSequenceFactory factory = FrescoSequence.getSequenceFactory(FrescoSequence.WEBP);
        final AnimationSequenceDrawable drawable = new AnimationSequenceDrawable(factory.createSequence(in));
        drawable.setLoopCount(1);
        drawable.setLoopBehavior(AnimationSequenceDrawable.LOOP_FINITE);
        drawable.setOnFinishedListener(new AnimationSequenceDrawable.OnFinishedListener() {
            @Override
            public void onFinished(AnimationSequenceDrawable frameSequenceDrawable) {

            }
        });
        mFrescoImage.setImageDrawable(drawable);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

可以看到,這里AnimationSequenceDrawable并不會依賴具體的某一種Sequence, BaseSequenceFactory的createSequence返回值類型為BaseAnimationSequence裆装,這樣設(shè)計的好處就是,如果有一天有需要播放別的類型的幀序列動畫或者發(fā)現(xiàn)了效率更牛逼的解析庫時倡缠,可以直接實現(xiàn)對應(yīng)繼承的BaseSequenceFactory和BaseAnimationSequence即可哨免,AnimationSequenceDrawable代碼不需要一點的改動,下面我們來看一下這幾個主要的類的代碼
AnimationSequenceDrawable和FrameSequenceDrawable幾乎就是一樣的昙沦,不同的地方就是所有依賴FrameSequence和FrameSequence.State對象的地方被替換成了BaseAnimationSequence對象琢唾,感興趣的朋友可以自行查看代碼這里就不貼了,我們先看看BaseAnimationSequence代碼

abstract public class BaseAnimationSequence {
    private final int mWidth;
    private final int mHeight;
    private final int mFrameCount;
    private final int mDefaultLoopCount;
    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

    public int getFrameCount() {
        return mFrameCount;
    }

    public int getDefaultLoopCount() {
        return mDefaultLoopCount;
    }

    public BaseAnimationSequence(int width, int height, int frameCount, int defaultLoopCount){
        mWidth = width;
        mHeight = height;
        mFrameCount = frameCount;
        mDefaultLoopCount = defaultLoopCount;
    }

    /**
     * getFrame
     * @param frameNr
     * @param output
     * @param previousFrameNr
     * @return previousFrameNr duration time
     */
    abstract public long getFrame(int frameNr, Bitmap output, int previousFrameNr);

    /**
     * isOpaque
     * @return
     */
    abstract public boolean isOpaque();
}

可以看到盾饮,BaseAnimationSequence和FrameSequence的方法基本都是一樣的采桃,只不過我把State這個內(nèi)部類去掉了,getFrame直接變成了抽象的方法丘损,然后我們看一下BaseSequenceFactory

abstract public class BaseSequenceFactory {
    /**
     * create Sequence
     * @param inputStream
     * @return
     */
     abstract public BaseAnimationSequence createSequence(InputStream inputStream);
}

BaseSequenceFactory目前只有一個方法普办,那就createSequence根據(jù)傳入的InputStream生成BaseAnimationSequence,為什么BaseAnimationSequence和BaseSequenceFactory我用了抽象類而沒用接口徘钥?我主要的考慮就是衔蹲,如果某天需要增加某些通用方法時(比如圖片是否透明),如果我使用的解析庫不支持呈础,或者有些方法是統(tǒng)一的實現(xiàn)舆驶,那么我可以直寫一個默認的方法,如果要是用接口的話可能就不是那么方便了而钞,下面我看最后一個類FrescoSequence它里面還包含了兩個BaseSequenceFactory的子類

public class FrescoSequence extends BaseAnimationSequence {

    public static final int WEBP = 1;
    public static final int GIF = 2;

    @IntDef({WEBP, GIF})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ImageType {}

    //fresco包中的類沙廉,抽象了動畫圖片
    private AnimatedImage mWebpImage;

    public FrescoSequence(AnimatedImage image){
        this(image.getWidth(),image.getHeight(),image.getFrameCount(),image.getLoopCount());
        mWebpImage = image;
    }

    private FrescoSequence(int width, int height, int frameCount, int defaultLoopCount) {
        super(width, height, frameCount, defaultLoopCount);
    }

    @Override
    public long getFrame(int frameNr, Bitmap output, int previousFrameNr) {
        //將frameNr的幀繪制到output,返回frameNr前一張的duration時間
        AnimatedImageFrame frame =  mWebpImage.getFrame(frameNr);
        frame.renderFrame(mWebpImage.getWidth(),mWebpImage.getHeight(),output);
        int lastFrame = (frameNr + mWebpImage.getFrameCount() - 1) % mWebpImage.getFrameCount();
        return mWebpImage.getFrame(lastFrame).getDurationMs();
    }

    @Override
    public boolean isOpaque() {
        return false;
    }

    public static FrescoSequence decodeStream(InputStream in,@ImageType int type){
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int rc;
        try {
            while ((rc = in.read(buff,0,buff.length)) > 0){
                out.write(buff,0,rc);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        byte[] bytes = out.toByteArray();
        switch (type){
            case GIF :
                return decodeGifPByteArray(bytes);
            default:
                return decodeWebPByteArray(bytes);
        }
    }

    public static FrescoSequence decodeWebPByteArray(byte[] data){
        return new FrescoSequence(WebPImage.create(data));
    }

    public static FrescoSequence decodeGifPByteArray(byte[] data){
        return new FrescoSequence(GifImage.create(data));
    }

    public static BaseSequenceFactory getSequenceFactory(int srcType ){
        if(srcType == GIF) {
            return new FrescoGifSequenceFactory();
        }else {
            return new FrescoWebpSequenceFactory();
        }
    }

    public static class FrescoWebpSequenceFactory extends BaseSequenceFactory {
        @Override
        public BaseAnimationSequence createSequence(InputStream inputStream) {
            return decodeStream(inputStream,WEBP);
        }
    }

    public static class FrescoGifSequenceFactory extends BaseSequenceFactory {
        @Override
        public BaseAnimationSequence createSequence(InputStream inputStream) {
            return decodeStream(inputStream,GIF);
        }
    }
}

FrescoSequence類里面主要就是實現(xiàn)了BaseAnimationSequence和BaseSequenceFactory的相關(guān)方法臼节,主要就是實用了Fresco對每一幀圖片進行了解析撬陵,熟悉Fresco的朋友應(yīng)該對AnimatedImage珊皿、WebPImage、GifImage并不陌生袱结,還有一個值得注意的地方就是getFrame方法的返回值的問題亮隙,在上篇文章里我也有解釋為什么這里的返回值是frameNr的前一張

到此為止,整個Android端播放WebP和Gif方法的介紹就結(jié)束了,這里的介紹也只是根據(jù)我的理解把我認為需要介紹的東西寫出來了垢夹,希望感興趣的朋友可以查看源碼并且可以一起討論或者提供更好的思路??

相關(guān)代碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末溢吻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子果元,更是在濱河造成了極大的恐慌促王,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件而晒,死亡現(xiàn)場離奇詭異蝇狼,居然都是意外死亡,警方通過查閱死者的電腦和手機倡怎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門迅耘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人监署,你說我怎么就攤上這事颤专。” “怎么了钠乏?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵栖秕,是天一觀的道長。 經(jīng)常有香客問我晓避,道長簇捍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任俏拱,我火速辦了婚禮暑塑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彰触。我一直安慰自己梯投,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布况毅。 她就那樣靜靜地躺著分蓖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尔许。 梳的紋絲不亂的頭發(fā)上么鹤,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音味廊,去河邊找鬼蒸甜。 笑死棠耕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的柠新。 我是一名探鬼主播窍荧,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恨憎!你這毒婦竟也來了蕊退?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤憔恳,失蹤者是張志新(化名)和其女友劉穎瓤荔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钥组,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡输硝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了程梦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片点把。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屿附,靈堂內(nèi)的尸體忽然破棺而出愉粤,到底是詐尸還是另有隱情,我是刑警寧澤拿撩,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站如蚜,受9級特大地震影響压恒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜错邦,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一探赫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撬呢,春花似錦伦吠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芯勘,卻和暖如春箱靴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荷愕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工衡怀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棍矛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓抛杨,卻偏偏與公主長得像够委,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怖现,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容