欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AnimationDrawable

程序员文章站 2024-03-24 14:52:22
...

尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错

说明:
Android 的帧动画可以使用 AnimationDrawable 实现,但是如果你的帧动画中如果
包含过多帧图片,一次性加载所有帧图片所导致的内存消耗会使低端机发生 OOM 异常。帧动画所使用的图片要注意降低内存消耗,当图片比较大时,容易出现 OOM。

下面简单分析AnimationDrawable的原理:
java
//demo
testView = findViewById(R.id.testView);
final AnimationDrawable animationDrawable = (AnimationDrawable).getResources().getDrawable(R.drawable.animation_test);
testView.setBackgroundDrawable(animationDrawable);
animationDrawable.start();

1.生成AnimationDrawable的过程。

//我把log去掉只展示调用逻辑 Resource#getDrawable方法
public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
        final Drawable d = getDrawable(id, null);
        return d;
    }
    
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
            throws NotFoundException {
        return getDrawableForDensity(id, 0, theme);
    }
    
    
 public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
        final TypedValue value = obtainTempTypedValue();
        try {
        //最终调用到ResourceImpl的 loadDrawable方法
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValueForDensity(id, density, value, true);
            return impl.loadDrawable(this, value, id, density, theme);
        } finally {
            releaseTempTypedValue(value);
        }
    }
ResourcesImpl # loadDrawable方法
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
            int density, @Nullable Resources.Theme theme)
            throws NotFoundException {
            //省略的检查cache check preloaded的逻辑,现在不是关注的点
            //First, check whether we have a cached version of this drawable....
            // Next, check preloaded drawables. Preloaded drawables may contain..
            if (cs != null) {
                dr = cs.newDrawable(wrapper);
            } else if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            } else {
                //最初cs为null,同时不是color drawable所以走到该逻辑
                dr = loadDrawableForCookie(wrapper, value, id, density, null);
            }
            if (dr instanceof DrawableContainer)  {
                needsNewDrawableAfterCache = true;
            }
    }
//ResourcesImpl#loadDrawableForCookie方法逻辑

try {
            if (file.endsWith(".xml")) {
            //我们是从xml中加载所以走一下逻辑
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
            //调用drawable的方法
                dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
                rp.close();
            } else {
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
        }
Drawable的createFromXmlForDensity又会调用
Drawable#createFromXmlInnerForDensity方法
static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
            @Nullable Theme theme) throws XmlPullParserException, IOException {
        return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
                density, theme);
    }

最终是DrawableInflater来生成Drawable实例

  Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
            @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
            throws XmlPullParserException, IOException {
            1.根据节点生成相应的Drawable。到这里AnimationDrawable的实例构建成功
            /**
            private Drawable inflateFromTag(@NonNull String name) {
        switch (name) {
        对应着我们xml animation-list
            case "animation-list":
                return new AnimationDrawable();
                }
            **/
            Drawable drawable = inflateFromTag(name);
        if (drawable == null) {
            drawable = inflateFromClass(name);
        }
        drawable.setSrcDensityOverride(density);
        2.调用inflate方法
        drawable.inflate(mRes, parser, attrs, theme);
        return drawable;
    }
生成Drawable实例后接着调用其inflate方法
我们查看AnimationDrawable的inflate方法
 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
            throws XmlPullParserException, IOException {
            //inflate 子节点
        inflateChildElements(r, parser, attrs, theme);
        setFrame(0, true, false);
    }
inflate item节点
 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
            Theme theme) throws XmlPullParserException, IOException {
        int type;
        final int innerDepth = parser.getDepth()+1;
        int depth;
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
                //1.从xml中解析item对应的drawable
                dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
                //2.drawable 和 对应的duration展示时间存放在数组中
            mAnimationState.addFrame(dr, duration);
            if (dr != null) {
            //3.设置Drawable的CallBack。这里CallBack是DrawableContainer实现。DrawableContainer也继承了Drawable类。这里有点类似View和ViewGroup的感觉。AnimationDrawable继承了该类。
                dr.setCallback(this);
            }
        }
    }

接着我们看AnimationDrawable的start方法

 public void start() {
        mAnimating = true;
        if (!isRunning()) {
            // Start from 0th frame.
            //从第一帧开始
            setFrame(0, false, mAnimationState.getChildCount() > 1
                    || !mAnimationState.mOneShot);
        }
    }
接着看setFrame函数
  private void setFrame(int frame, boolean unschedule, boolean animate) {
        mAnimating = animate;
        mCurFrame = frame;
        //根据index来确定当前Drawable
        selectDrawable(frame);
        if (unschedule || animate) {
            unscheduleSelf(this);
        }
        if (animate) {
            // Unscheduling may have clobbered these values; restore them
            mCurFrame = frame;
            //Running状态值改变
            mRunning = true;
            //调度每一个item drawable,传递的时间是当前时间加上duration
            //这里的this代表一个runnable实例,最后传递到Choreographer中
            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        }
    }
//这个scheduleSelf函数 是Drawable类实现的。主要是回到drawable
//CallBack接口的scheduleDrawable函数。接下来的任务是看该CallBack的具体
//实现,即setCallBack的调用处。这里用debug查看调用栈。在给view设置background drawable时
public void scheduleSelf(@NonNull Runnable what, long when) {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.scheduleDrawable(this, what, when);
        }
    }
    
    public void setBackgroundDrawable(Drawable background) {
      background.setCallback(this);//此处设置Callback。this是View实例。
      //接下来查看view如何实现该接口
    }
  @Override
    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
        if (verifyDrawable(who) && what != null) {
            final long delay = when - SystemClock.uptimeMillis();
            if (mAttachInfo != null) {
            //view attach到window上时该mAttachInfo不为空。
            //这里的callback类型为animation,之后会分析到
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                        Choreographer.CALLBACK_ANIMATION, what, who,
                        Choreographer.subtractFrameDelay(delay));
            } else {
                // Postpone the runnable until we know
                // on which thread it needs to run.
                //没attach的时候,先暂存推迟执行
                getRunQueue().postDelayed(what, delay);
            }
        }
    }
接下来去看Choreographer类的postCallbackDelayed方法调用了私有postCallbackDelayedInternal方法
   private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            //根据callback类型存储CallbackRecord.内部实现是按时间
            //将CallbackRecord插入到链表中
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        //如果当前时间大于应该执行的时间,直接调用scheduleFrameLocked

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
            //否则延迟发送消息,接收消息后也是调用上面的函数进行处理
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
接下来查看scheduleFrameLocked的逻辑
private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
            //默认USE_VSYNC为true
                if (isRunningOnLooperThreadLocked()) {/判断是否是跟Choreographer的Looper相同
                //这里最终还是会调用doFrame()函数
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
            //为false的话,直接发消息调用doFrame()函数
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
接着再看Choreographer的doFrame
 void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
        try {
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            //这里先后执行四种类型的callback
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            //这里执行我们关心的Animation的callback
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
 void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
         //这里将执行时间小于当前时间的特定类型的callback都取出来
         mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

        try {
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
               //这里调用CallbackRecord的run函数,执行构造callbackrecord是传递的Action。针对我们要分析的情况,该callback的action是AnimationDrawable schedule函数传递进来的,AnimationDrawable实现的Runnable接口。
               /**
               public void run() {
               //其实现就是调用下一帧
               nextFrame(false);
    }
               **/
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

总结:到这里我们可以总结一下,AnimationDrawable调用start后首先set第一帧,接着通过Choreographer机制,不断的切换下一个drawable的drawable。
思考:AnimationDrawable只是控制当前的drawable序列。那画面如何更改的。
我们查看View的onDraw方法

 /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
         注释中提到,先绘制background。
         我们查看其具体实现。是调用Drawable的draw方法。
         我们知道我们使用AnimationDrawable作为background drawable。
           private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
//接着查看AnimationDrawable的draw方法。其中并没有draw方法,这里去查看其父
//类的draw方法,发现他继承的DrawableContainer中有draw方法的实现
@Override
    public void draw(Canvas canvas) {
        if (mCurrDrawable != null) {
            mCurrDrawable.draw(canvas);
        }
        if (mLastDrawable != null) {//为什么要调用last的draw方法?
            mLastDrawable.draw(canvas);
        }
    }
    具体实现是调用currentdrawable 和lastdrawable的draw方法

思考:如何触发draw操作?

//回过头看AnimationDrawable中setFrame()方法切换每一个drawable的时候
//除了调用上面分析过的scheduleSelf方法,还调用了 selectDrawable()方法
 private void setFrame(int frame, boolean unschedule, boolean animate) {
        selectDrawable(frame);
        scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        //看selectDrawable方法实现最后会调用
        //invalidateSelf();来通知重绘
    }

**所以,总体来看AnimationDrawable根据每个item drawable的duration时间
来不断调整当前的drawable。切换drawable的同时出发Choregorapher 的doFrame函数。doFrame函数依次处理 CALLBACK_INPUT ,CALLBACK_ANIMATION,CALLBACK_TRAVERSAL,CALLBACK_COMMIT类型的callback。处理CALLBACK_ANIMATION 类型的callback时触发
**
实验
现在使用16张图
每张图320*320 按4byte一位来算400k 16张差不多6m
有两个页面 第一个页面A只有一个按钮启动第二个页面B B页面播放该帧动画。
刚进入A页面内存稳定35M

实验 进入B内存情况 退出B内存情况 退出B后手动gc 退出A
1.退出B后不处理 118到122间波动 稳定112M 112M 77M
2.B的onStop中 stop animation 同上 稳定112M 76M 20M
3.B的onDestroy中view的background设为null 同上 稳定112M 76M 20M
4.同时stop animation onDestroy中view的background设为null 同上 稳定112M 76M 20M

疑问:为什么渲染这些图需要100多M内存