AnimationDrawable

尽量不要使用 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间波动稳定112M112M77M
2.B的onStop中 stop animation同上稳定112M76M20M
3.B的onDestroy中view的background设为null同上稳定112M76M20M
4.同时stop animation onDestroy中view的background设为null同上稳定112M76M20M

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值