上一篇我們介紹了Android中播放Webp動畫的一種方法FrameSequenceDrawable的實現(xiàn)原理嗜傅,在上篇文章的最后我做了一個預(yù)告厨钻,本篇就是接上篇的內(nèi)容磷醋,抽象了FrameSequence思想實現(xiàn)了可以播放webp和gif的AnimationSequenceDrawable淤井,主要還是介紹實現(xiàn)的原理滚局,如果你沒有看上一篇的內(nèi)容史简,建議一定要看一下乃秀,了解整個播放的原理后在看本篇,這篇對播放原理不會在介紹圆兵,上篇的地址Android中播放webp動畫的一種方式:FrameSequenceDrawable
播放效果
在介紹之前跺讯,我們還是先看一下播放效果:
我想直接用
如果你想直接使用,本篇就不直接貼代碼了殉农,可以去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圖,可以看到倦西,
- 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)代碼地址