美文网首页
内存不足时系统回收Activity的流程解析

内存不足时系统回收Activity的流程解析

作者: BlueSocks | 来源:发表于2022-11-10 22:22 被阅读0次

    前言
    android开发中,activity我们会经常遇到,作为view的容器,activity天然就具备了生命周期的特点,当然这篇不是讲生命周期,而是关于系统不足时回收的动作,有可能导致app运行时会出现一些不可预料的“逻辑”异常行为。

    Activity回收

    那么问题来了,这个不可见的activity(这里activity1跟activity2属于同一个任务栈),有没有可能会被系统所回收,如果有可能会被回收,那么什么情况下才会出现,如果会出现,有没有手段可以避免?

    我们带着这三个问题,去继续我们探索

    ActivityThread的内存回收机制

    我们都知道,Activity创建过程中,会通过ActivityThread进行各种初始化,其中我们特别关注一下attach函数。

    ActivityThread.java
    
    private void attach(boolean system, long startSeq) {
            ...
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    // 当内存大于3/4的时候,启动回收策略
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                        // 释放逻辑
                            ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                }
            });
    

    通过上面源码我们可以看到,attach中通过BinderInternal.addGcWatcher进行了一个gc的监听,如果此时已用内存大于runtime.maxMemory()即当前进程最大可用内存的3/4的时候,就会进入一个释放逻辑,我们继续看ActivityTaskManager.getService().releaseSomeActivities中releaseSomeActivities函数的实现

    @Override
    public void releaseSomeActivities(IApplicationThread appInt) {
        synchronized (mGlobalLock) {
            final long origId = Binder.clearCallingIdentity();
            try {
                // 真正的释放,通过WindowProcessController,原因是low-mem
                final WindowProcessController app = getProcessController(appInt);
                app.releaseSomeActivities("low-mem");
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }
    

    这个比较简单,就是直接包了一层,真正处理的是通过WindowProcessController的releaseSomeActivities方法,这个releaseSomeActivities非常重要,是我们上面三个问题的答案

    void releaseSomeActivities(String reason) {
        // Examine all activities currently running in the process.
        // Candidate activities that can be destroyed.
        ArrayList<ActivityRecord> candidates = null;
        if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + this);
        for (int i = 0; i < mActivities.size(); i++) {
            遍历所有的ActivityRecord
            final ActivityRecord r = mActivities.get(i);
            如果当前activity本来就处于finishing或者DESTROYING/DESTROYED状态,continue,即不加入activity的释放列表
            if (r.finishing || r.isState(DESTROYING, DESTROYED)) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
                return;
            }
            // 如果处于以下状态,则该activity也不会被回收
            if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
                    || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
                continue;
            }
    
            // 稍后我们会讲到,这里其实就是说明当前window是不是合法的window
            if (r.getParent() != null) {
                if (candidates == null) {
                    candidates = new ArrayList<>();
                }
                candidates.add(r);
            }
        }
    
        // 上面所以要释放的activityRecord信息都存在了candidates中
        if (candidates != null) {
            // Sort based on z-order in hierarchy.
            candidates.sort(WindowContainer::compareTo);
            // Release some older activities
            int maxRelease = Math.max(candidates.size(), 1);
            do {
                final ActivityRecord r = candidates.remove(0);
                if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + r
                        + " in state " + r.getState() + " for reason " + reason);
                // 回收
                r.destroyImmediately(reason);
                --maxRelease;
            } while (maxRelease > 0);
        }
    }
    

    我们一步步解释一下上面的关键方法,上面ArrayList candidates 就是一个即将被释放的ActivityRecord列表,那么ActivityRecord是什么呢?相关的解释已经有很多了,这里我们其实简单理解ActivityRecord其实 是Activity的标识,与每个Activity是一一对应,只不过在ActivityThread中我们操作的对象是ActviityRecord而不是Activity罢了。
    总之,candidates 就包含了系统即将回收的activity,这里就回答了我们第一个问题,activity是有可能被回收的

    接着我们继续看

    if (r.finishing || r.isState(DESTROYING, DESTROYED)) {
      if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
        return;
    }
    

    如果当前的activity的finishing 为true 或者 当前状态处于DESTROYING, DESTROYED,那么这个activity就不会再被加入回收列表了,因为本来已经要被回收

    接着,处于以下状态的ActivityRecord,也不会被回收

    r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
    

    这几个判断条件非常有意思

    • mVisibleRequested 当前activity虽然处于onstop,但是已经被要求可见,比如后台播放activity,不过现在大部分不支持了,还有就是壁纸类应用,也可以设置mVisibleRequested == true
    • stopped 处于非stopped状态,就是当前可见activity
    • !r.hasSavedState(),这个并非只activity没有重载onSaveInstanceState,没有重载onSaveInstanceState也有可能回收,可看源码
    boolean hasSavedState() {
        return mHaveState;
    }
    
    void setSavedState(@Nullable Bundle savedState) {
       mIcicle = savedState;
       mHaveState = mIcicle != null;
    }
    

    setSavedState 中savedState为null的时候基本是activity已经被回收的情况,比如activity处于不在历史任务里面,此时savedState就为null(但是这种activity不可见时就会被回收,可以尝试一下)

    • !r.isDestroyable isDestroyable == false的activity,处于前台可见时,就是isDestroyable == false

    这里就回答了我们的第二个问题,回收条件是当已用内存超过3/4且activity不可见时,且不满足上诉条件的activity就会被加入回收列表中。

    验证

    到了验证环节,我们可以通过创建三个activity。
    在可见的MyActivity3中,通过以下代码模拟内存分配

    companion object{
        @JvmStatic
        var list = ArrayList<ByteArray>()
    }
    val runtime = Runtime.getRuntime()
    val byteArray = ByteArray(1024*10000)
    list.add(byteArray)
    Log.e("hello","${runtime.maxMemory()} ${runtime.totalMemory()} ${runtime.freeMemory()}")
    

    多次分配后,就能看到处于非任务栈顶的MyActivity1跟MyActivity2就被回收掉了。
    总结
    从一个小小的activity回收,我们能看到系统做了很多很多的内部处理,保证了app运行时内存的充足,同时回归本文一开始提到的架构问题,我们尽量不要采取这种方式去传递信息,相反的,如果需要中转处理,我们完全可以依赖一个静态的全局类去处理即可,而不是把处理依赖于具有生命周期的activity,大家也可以检查一下自己的项目中有没有这种写法,有的话要尽量改掉噢!

    来自:https://juejin.cn/post/7162521119004557348

    相关文章

      网友评论

          本文标题:内存不足时系统回收Activity的流程解析

          本文链接:https://www.haomeiwen.com/subject/egwatdtx.html