Activity怎么被回收了?

记录Android上的GC对于Activity的影响

Posted by XYH on May 4, 2022

前言:

在多年的开发过程中,遇到的问题也是很多,有常见的、有不解的,所谓的开发经验正是与遇到的问题成正比。

Activity什么时候会被回收

日常开发中,有时候会发现在开发过程中,存在多个ActivityTask的情况下返回到之前的页面会重新执行onCreate,为什么应用在前台,这些不在栈顶的Activity还会被回收呢?本文探讨该问题并从中总结关于Activity回收机制运用的部分知识。

大部分开发者看到这个问题,都会抛出一句,因为内存不够了,所以需要回收Activity释放内存啊。

这么回答并不算正确,因为内存不足同样分为两种情况。

  • APP内存不足
  • 系统内存不足

这两种情况下,反应出来的结果是完全不一样的,当系统内存不足的时候,不会是回收Activity那么人性化,出现系统内存不足的情况,系统会直接杀掉APP的进程。 只有当APP的内存不足时,才会去回收当前APP内不可见的Activity来释放内存。

APP内存不足

APP内存分配实际上也是对堆的分配跟释放,系统对APP的内存会分配一个堆的阈值,如果当前应用接近了这个阈值还再继续申请内存,此时就容易OOM了。

需要注意的是,Activity的回收只会发生在APP存在2个及以上ActivityTask并且app内存占用超过了虚拟机分配内存的3/4的情况下。如果ActivityTask只有1个并且APP内存申请超过了虚拟机允许分配的最大内存时会直接出现OOM。

下文会贴出具体判断源码。

本文标题出现的情况,APP内存不存,模拟复现场景:

创建一个BaseActivity,在对应的生命周期里打印log。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
open class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        "${javaClass.simpleName} onCreate".log()
    }

    override fun onStart() {
        super.onStart()
        "${javaClass.simpleName} onStart".log()
    }

    override fun onResume() {
        super.onResume()
        "${javaClass.simpleName} onResume".log()
    }

    override fun onRestart() {
        super.onRestart()
        "${javaClass.simpleName} onRestart".log()
    }

    override fun onPause() {
        super.onPause()
        "${javaClass.simpleName} onPause".log()
    }

    override fun onStop() {
        super.onStop()
        "${javaClass.simpleName} onStop".log()
    }

    override fun onDestroy() {
        super.onDestroy()
        "${javaClass.simpleName} onDestroy".log()
    }
}

接下来分别创建AActivityBActivityCActivityDActivity最后创建申请内存的RequestMemoryActivity

A、B、C、DActivity页面只有一个跳转到下一个页面的按钮。

manifest注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xxx>
    <application
        xxx>
        <activity
            android:name=".AActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".BActivity" android:launchMode="singleInstance"/>
        <activity
            android:name=".CActivity" android:launchMode="singleInstance"/>
        <activity
            android:name=".DActivity" android:launchMode="singleInstance"/>
        <activity
            android:name=".RequestMemoryActivity" />
    </application>

</manifest>

RequestMemoryActivity中申请内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class RequestMemoryActivity : BaseActivity() {

    private var mBytes = ByteArray(1024 * 1024 * 5)
    private var clickCount = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_request_memory)
    }

    fun requestMemory(v: View) {
        mBytes = ByteArray(1024 * 1024 * 10 * clickCount++)
        val runtime = Runtime.getRuntime()
        //虚拟机已使用的总内存
        val totalMemory = runtime.totalMemory()
        //虚拟机准备使用的最大内存
        val maxMemory = runtime.maxMemory()
        //虚拟机闲置内存
        val freeMemory = runtime.freeMemory()
        val usedMemory = totalMemory - freeMemory

        val isExceededRatio = usedMemory > (3 * maxMemory) / 4
        "maxMemory = ${maxMemory / 1024 / 1024}mb,usedMemory = ${usedMemory / 1024 / 1024}mb,内存占用是否大于3/4 ? $isExceededRatio".log()
    }
}

结果:

image

可以看到,当APP内存超过阈值(3/4最大分配内存)的时候,依次回收的Task顺序为:

  • BActivity (ActivityTaskB)
  • CActivity (ActivityTaskC)
  • DActivity (ActivityTaskD)

最后当只有一个ActivityTask的时候,应用发生了OOM。

源码分析

首先带着疑问去看源码(本文分析的源码对应的版本为Android10,SDK29)。

1,Activity回收的时机? 2,Activity回收的顺序?

Activity的回收监听是在每次GC的时候。

所涉及的源码如下:

ActivityThread类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        xxx
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        xxx
    }
    
    private void attach(boolean system, long startSeq){
        xxx
        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();
                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();
                    }
                }
            }
        });
        xxx
    }
}

这里看到了回收时候,对于内存的判断。

当APP的使用内存超过了虚拟机分配内存3/4时,会执行 ActivityTaskManager.getService().releaseSomeActivities(mAppThread);

跟踪该方法:

ActivityTaskManager类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ActivityTaskManager { 
    public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }
    
    @UnsupportedAppUsage(trackingBug = 129726065)
    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
                }
            };
}        

IActivityTaskManagerSingleton获取的是IActivityTaskManagerAIDL接口,对应的impl类为ActivityTaskManagerService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    @Override
    public void releaseSomeActivities(IApplicationThread appInt) {
        synchronized (mGlobalLock) {
            final long origId = Binder.clearCallingIdentity();
            try {
                final WindowProcessController app = getProcessController(appInt);
                mRootActivityContainer.releaseSomeActivitiesLocked(app, "low-mem");
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }   
}

调用来到了mRootActivityContainer.releaseSomeActivitiesLocked(app, "low-mem")对应的类为RootActivityContainer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class RootActivityContainer extends ConfigurationContainer
        implements DisplayManager.DisplayListener {
    void releaseSomeActivitiesLocked(WindowProcessController app, String reason) {
        // Tasks is non-null only if two or more tasks are found.
        //tasks不为null则代表tasks里面的task数量>=2,看下文分析。
        ArraySet<TaskRecord> tasks = app.getReleaseSomeActivitiesTasks();
        if (tasks == null) {
            if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Didn't find two or more tasks to release");
            return;
        }
        // If we have activities in multiple tasks that are in a position to be destroyed,
        // let's iterate through the tasks and release the oldest one.
        final int numDisplays = mActivityDisplays.size();
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
            final int stackCount = display.getChildCount();
            // Step through all stacks starting from behind, to hit the oldest things first.
           //移除的stack的策略为先进先出
            for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) {
                final ActivityStack stack = display.getChildAt(stackNdx);
                // Try to release activities in this stack; if we manage to, we are done.
                if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
                    return;
                }
            }
        }
    }    
}

跟踪 ArraySet<TaskRecord> tasks = app.getReleaseSomeActivitiesTasks();此方法在WindowProcessController中,会返回满足条件的ActivityTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class WindowProcessController extends ConfigurationContainer<ConfigurationContainer>
        implements ConfigurationContainerListener {
    ArraySet<TaskRecord> getReleaseSomeActivitiesTasks() {
        // Examine all activities currently running in the process.
        TaskRecord firstTask = null;
        // Tasks is non-null only if two or more tasks are found.
        ArraySet<TaskRecord> tasks = null;
        if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + this);
        for (int i = 0; i < mActivities.size(); i++) {
            final ActivityRecord r = mActivities.get(i);
            // First, if we find an activity that is in the process of being destroyed,
            // then we just aren't going to do anything for now; we want things to settle
            // down before we try to prune more activities.
            //如果activity正在销毁或者已销毁,直接返回null,不作下一步处理。
            if (r.finishing || r.isState(DESTROYING, DESTROYED)) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
                return null;
            }
            // Don't consider any activies that are currently not in a state where they
            // can be destroyed.
            //如果activity可见,或者没有处于已经stop状态下,contine loop。这也是前文提到的,只会回收不可见的Activity的依据。
            if (r.visible || !r.stopped || !r.haveState
                    || r.isState(RESUMED, PAUSING, PAUSED, STOPPING)) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
                continue;
            }

            final TaskRecord task = r.getTaskRecord();
            if (task != null) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Collecting release task " + task
                        + " from " + r);
                //要想保证tasks不为null,tasks里的数量肯定是>=2
                if (firstTask == null) {
                    firstTask = task;
                } else if (firstTask != task) {
                    if (tasks == null) {
                        tasks = new ArraySet<>();
                        tasks.add(firstTask);
                    }
                    tasks.add(task);
                }
            }
        }
        return tasks;
    }            
}

最后会执行ActivityStack#releaseSomeActivitiesLocked,跟踪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class ActivityStack extends ConfigurationContainer {
    final int releaseSomeActivitiesLocked(WindowProcessController app, ArraySet<TaskRecord> tasks,
            String reason) {
        // Iterate over tasks starting at the back (oldest) first.
        if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + app);
        //数量为4及以下,每次回收1个task直到成功为止,4-8每次回收2个task依此类推。
        int maxTasks = tasks.size() / 4;
        if (maxTasks < 1) {
            maxTasks = 1;
        }
        int numReleased = 0;
        for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) {
            final TaskRecord task = mTaskHistory.get(taskNdx);
            if (!tasks.contains(task)) {
                continue;
            }
            
            int curNum = 0;
            final ArrayList<ActivityRecord> activities = task.mActivities;
            for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
                final ActivityRecord activity = activities.get(actNdx);
                if (activity.app == app && activity.isDestroyable()) {
                    if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity
                            + " in state " + activity.getState() + " resumed=" + mResumedActivity
                            + " pausing=" + mPausingActivity + " for reason " + reason);
                    destroyActivityLocked(activity, true, reason);
                    if (activities.get(actNdx) != activity) {
                        // Was removed from list, back up so we don't miss the next one.
                        actNdx--;
                    }
                    curNum++;
                }
            }
            if (curNum > 0) {
                numReleased += curNum;
                maxTasks--;
                if (mTaskHistory.get(taskNdx) != task) {
                    // The entire task got removed, back up so we don't miss the next one.
                    taskNdx--;
                }
            }
        }
        if (DEBUG_RELEASE) Slog.d(TAG_RELEASE,
                "Done releasing: did " + numReleased + " activities");
        return numReleased;
    }    
}

回收方法调用的是destroyActivityLocked(activity, true, reason);,点进去看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
    xxx
    boolean removedFromHistory = false;

    cleanUpActivityLocked(r, false, false);

    final boolean hadApp = r.hasProcess();

    if (hadApp) {
        if (removeFromApp) {
            r.app.removeActivity(r);
            if (!r.app.hasActivities()) {
                mService.clearHeavyWeightProcessIfEquals(r.app);
                }
            if (!r.app.hasActivities()) {
                // Update any services we are bound to that might care about whether
                // their client may have activities.
                // No longer have activities, so update LRU list and oom adj.
                r.app.updateProcessInfo(true /* updateServiceConnectionActivities */,
                        false /* activityChange */, true /* updateOomAdj */);
            }
        }

        boolean skipDestroy = false;

        try {
           mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken,
                    DestroyActivityItem.obtain(r.finishing, r.configChangeFlags));
        } catch (Exception e) {
            // We can just ignore exceptions here...  if the process
            // has crashed, our death notification will clean things
            // up.
            //Slog.w(TAG, "Exception thrown during finish", e);
            if (r.finishing) {
                removeActivityFromHistoryLocked(r, reason + " exceptionInScheduleDestroy");
                removedFromHistory = true;
                skipDestroy = true;
            }
        }

        r.nowVisible = false;

       
        if (r.finishing && !skipDestroy) {
            Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
            mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
        } else {
            r.setState(DESTROYED,
                    "destroyActivityLocked. not finishing or skipping destroy");
            r.app = null;
        }
    } else {
        // remove this record from the history.
        if (r.finishing) {
            removeActivityFromHistoryLocked(r, reason + " hadNoApp");
            removedFromHistory = true;
        } else {
            r.setState(DESTROYED, "destroyActivityLocked. not finishing and had no app");
            r.app = null;
        }
    }

    r.configChangeFlags = 0;

    if (!mLRUActivities.remove(r) && hadApp) {
        Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list");
    }

    return removedFromHistory;
}

再往下就是一些移除的操作了,分析到这一步已经明了Activity的回收策略。

其中涉及到了关于AMS栈管理的一些类,其具体的作用这里专门列出来来一个表格,数据来源

ActivityRecord

记录Activity的信息,并通过成员变量task指向TaskRecord。

名称 类型 说明
app ProcessRecord Activity运行在哪个进程
task TaskRecord Activity运行在哪个task
info ActivityInfo Activity信息
mActivityType int Activity类型
state ActivityState Activity状态
appInfo ApplicationInfo Activity运行在哪个APP中
realActivity ComponentName 组件名
packageName String 包名
processName String 进程名
launchMode int 启动模式
userId int 该Activity运⾏在哪个⽤户Id

TaskRecord 描述Activity的Affinity所属的栈。 |名称|类型|说明| |:-:|:-:|:-:| |stack|ActivityStack|当前所属的stack| |mActivities|ArrayList|当前task的所有Activity列表| |taskId|int|TaskRecord的Id| |affinity|String|root activity的affinity,即该Task中第⼀个Activity| |mCallingUid|int|调⽤者的UserId| |mCallingPackage|String|调⽤者的包名|

ActivityStack 管理着TaskRecord,内部维护Activity所有状态、特殊状态的Activity和Activity相关的列表数据。 |名称|类型|说明| |:-:|:-:|:-:| |mTaskHistory|ArrayList|保存所有的Task列表| |mStacks|ArrayList|所有的stack列表| |mStackId|int|ActivityStackvisor的mActivityContainers的key值Id| |mDisplayId|int|ActivityStackSupervisor的mActivityDisplays的key值Id| |mPauseingActivity|ActivityRecord|正在暂停的Activity⽤者的UserId| |mLastPausedActivity|ActivityRecord|上⼀个已暂停的Activity⽤者的包名| |mResumedActivity|ActivityRecord|已经Resumed的Activity| |mLastStartedActivity|ActivityRecord|最近⼀次启动的Activity|

ActivityStackSupervisor 管理所有的ActivityStack。 |名称|类型|说明| |:-:|:-:|:-:| |mHomeStack|ActivityStack|桌⾯的stack| |mFocusedStack|ActivityStack|当前聚焦的stack| |mLastFocusedStack|ActivityStack|正在切换到聚焦的stack| |mActivityDisplays|SparseArray|displayId为key| |mActivityContainers|SparseArray |mStackId为key|

ActivityDisplay 表示⼀个屏幕,Android⽀持三种屏幕:主屏幕,外接屏幕(HDMI等),虚拟屏幕(投屏)⼀般地,对于没有 分屏功能以及虚拟屏的情况下,ActivityStackSupervisor与ActivityDisplay都是系统唯⼀;ActivityDisplay主要 有Home Stack和App Stack这两个栈。

题外话:

Activity是如何监听系统GC的?

在文中,监听GC的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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();
        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();
            }
        }
    }

BinderInternal类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BinderInternal {

    static WeakReference<GcWatcher> sGcWatcher
            = new WeakReference<GcWatcher>(new GcWatcher());
            
    static final class GcWatcher {
        @Override
        protected void finalize() throws Throwable {
            handleGc();
            sLastGcTime = SystemClock.uptimeMillis();
            synchronized (sGcWatchers) {
                sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
            }
            for (int i=0; i<sTmpWatchers.length; i++) {
                if (sTmpWatchers[i] != null) {
                    sTmpWatchers[i].run();
                }
            }
            sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
        }
    }    
}

Google采用的方式是弱引用。 引用的分类也顺便记录一下:

  • 强引用(Object obj = new Object())

    只要某个对象有强引用的关联,JVM不会回收该对象,哪怕是OOM。

  • 弱引用(WeakReference rf = new WeakReference<>(new Objectr()))

    发生GC就会回收。

  • 软引用(SoftReference rf2 = new SoftReference<>(new Object()))

    当内存不足时会进行回收。

当被弱引用声明的对象,在被GC回收时,会触发其finalize()方法,这时Google在GcWatcher被回收之后,重新声明了

sGcWatcher = new WeakReference(new GcWatcher());

所以下次GC的时候,还会监听到,这可比循环、递归高效多了。

分析完了APP内存不足的情况,接下来便是系统内存不足时对于应用的影响。

系统内存不足

当系统内存不足的时候,会对后台进程进行查杀,以保证内存是可用的,这就是在安卓系统上使用的Android的LMK(LowMemoryKiller)机制。

启动进程分为热启动、冷启动。

  • 热启动 当用户退出进程后,系统并不会立即杀掉该进程,而是将其置于后台运行,下次再启动该进程的时候,直接从后台将该进程拉起来使用。
  • 冷启动 重新为应用分配进程。

当启动程序、关闭程序这种操作越来越多的时候,后台会留下很多空进程,这种内存是会占用内存空间,Android当内存剩余的空间满足一定的条件时,会对后台的进程进行查杀,以保证内存是可用的,简称LMK(LowMemoryKiller机制)。 LMK在查杀过程中会按照进程的等级来查杀,先对等级低的下手。 按照进程的等级可以分为:

  • 前台进程
    • 正在进行交互的Activity所在的进程。
    • 拥有某个Service,后台绑定到用户正在交互的进程。
    • 拥有正在“前”台运行的Service(服务已经调用startForeground())。
    • 正在执行一个生命周期回调的Service(onCreate()、onStart()、onDestroy())。
    • 正在执行onReceive()方法的BroadCastReceiver 此进程Android系统一般是不会对其进行回收。
  • 可见进程
    • 绑定到可见前台的Service。
    • 托管不在前台,但仍对用户可见的Activity(已调用其onPause()方法)。
  • 服务进程
    • 正在执行startService()方法开启服务的进程
  • 后台进程
    • Activity已经调用onStop()方法,表示已经不可见的进程 后台会存在很多这种进程,当内存不足时,系统会终止他们,这些后台进程会被系统保存在LRU列表中,以保证用户最先访问的进程最后一个被终止。
  • 空进程
    • 此种进程就是某一个进程退出后,缓存在后台的进程,只是当做缓存进程,加速下一次的启动,可能随时被回收。

LMK(LowMemoryKiller)原理

LMKD

总结

  • 当应用内存不足时,如果当前应用的ActivityTask>=2并且使用内存超过了虚拟机分配内存的3/4时,会尝试对当前应用下不可见的ActivityTask进行回收,回收顺序是先入先出,每次回收ActivityTask的个数为int n=taskCount/4,回收时机为每次GC的时候。
  • 当系统内存不足时候,会在kernal层查找进程并回收。

最后推荐Google官方给的内存管理优化建议