什么情況下Activity會(huì)被殺掉呢松蒜?

首先一個(gè)報(bào)錯(cuò)來作為開篇:

Caused by androidx.fragment.app.Fragment$InstantiationException
Unable to instantiate fragment xxx: could not find Fragment constructor

這個(gè)報(bào)錯(cuò)原因就是Fragment如果重載了有參的構(gòu)造方法扔茅,沒有實(shí)現(xiàn)默認(rèn)無參構(gòu)造方法。Activity被回收又回來嘗試重新恢復(fù)Fragment的時(shí)候報(bào)錯(cuò)的秸苗。
那如何模擬Activity被回收呢召娜?
可能有人知道,一個(gè)方便快捷的方法就是:打開 開發(fā)者選項(xiàng) - 不保留活動(dòng)惊楼,這樣每次Activity回到后臺(tái)都會(huì)被回收玖瘸,也就可以很方便的測試這種case。

但拋開這種方式我怎么來復(fù)現(xiàn)這種情況呢檀咙?
這里我提出一種方式:我是不是可以打開我的App雅倒,按Home回到后臺(tái),然后瘋狂的打開手機(jī)里其他的大型應(yīng)用或者游戲這類的能占用大量手機(jī)內(nèi)存的App弧可,等手機(jī)內(nèi)存占用大的時(shí)候是不是可以復(fù)現(xiàn)這種情況呢蔑匣?

結(jié)論是不可以,不要混淆兩個(gè)概念棕诵,系統(tǒng)內(nèi)存不足App內(nèi)存不足裁良,兩者能引起的后果也是不同的

  • 系統(tǒng)內(nèi)存不足 -> 殺掉應(yīng)用進(jìn)程
  • App內(nèi)存不足 -> 殺掉后臺(tái)Activity

首先明確一點(diǎn),Android框架對(duì)進(jìn)程創(chuàng)建與管理進(jìn)行了封裝年鸳,對(duì)于APP開發(fā)者只需知道Android四大組件的使用趴久。當(dāng)Activity, Service, ContentProvider, BroadcastReceiver任一組件啟動(dòng)時(shí),當(dāng)其所承載的進(jìn)程存在則直接使用搔确,不存在則由框架代碼自動(dòng)調(diào)用startProcessLocked創(chuàng)建進(jìn)程祠饺。所以說對(duì)APP來說進(jìn)程幾乎是透明的乓旗,但了解進(jìn)程對(duì)于深刻理解Android系統(tǒng)是至關(guān)關(guān)鍵的。

1. 系統(tǒng)內(nèi)存不夠 -> 殺掉應(yīng)用進(jìn)程

1.1. LKM簡介

Android底層還是基于Linux,在Linux中低內(nèi)存是會(huì)有oom killer去殺掉一些進(jìn)程去釋放內(nèi)存辖试,而Android中的lowmemorykiller就是在此基礎(chǔ)上做了一些調(diào)整來的初肉。因?yàn)槭謾C(jī)上的內(nèi)存畢竟比較有限析校,而Android中APP在不使用之后并不是馬上被殺掉皿哨,雖然上層ActivityManagerService中也有很多關(guān)于進(jìn)程的調(diào)度以及殺進(jìn)程的手段,但是畢竟還需要考慮手機(jī)剩余內(nèi)存的實(shí)際情況机隙,lowmemorykiller的作用就是當(dāng)內(nèi)存比較緊張的時(shí)候去及時(shí)殺掉一些ActivityManagerService還沒來得及殺掉但是對(duì)用戶來說不那么重要的進(jìn)程蜘拉,回收一些內(nèi)存萨西,保證手機(jī)的正常運(yùn)行。

lowmemkiller中會(huì)涉及到幾個(gè)重要的概念:
/sys/module/lowmemorykiller/parameters/minfree:里面是以”,”分割的一組數(shù)旭旭,每個(gè)數(shù)字代表一個(gè)內(nèi)存級(jí)別
/sys/module/lowmemorykiller/parameters/adj: 對(duì)應(yīng)上面的一組數(shù)谎脯,每個(gè)數(shù)組代表一個(gè)進(jìn)程優(yōu)先級(jí)級(jí)別

比如:
/sys/module/lowmemorykiller/parameters/minfree:18432, 23040, 27648, 32256, 55296, 80640
/sys/module/lowmemorykiller/parameters/adj: 0, 100, 200, 300, 900, 906

代表的意思是兩組數(shù)一一對(duì)應(yīng):

  • 當(dāng)手機(jī)內(nèi)存低于80640時(shí),就去殺掉優(yōu)先級(jí)906以及以上級(jí)別的進(jìn)程
  • 當(dāng)內(nèi)存低于55296時(shí)持寄,就去殺掉優(yōu)先級(jí)900以及以上的進(jìn)程

可能每個(gè)手機(jī)的配置是不一樣的源梭,可以查看一下手頭的手機(jī),需要root稍味。

1.2. 如何查看ADJ

如何查看進(jìn)程的ADJ呢废麻?比如我們想看QQ的adj

-> adb shell ps | grep "qq" 
UID            PID  PPID C STIME TTY          TIME CMD
u0_a140       9456   959 2 10:03:07 ?     00:00:22 com.tencent.mobileqq
u0_a140       9987   959 1 10:03:13 ?     00:00:07 com.tencent.mobileqq:mini3
u0_a140      16347   959 0 01:32:48 ?     00:01:12 com.tencent.mobileqq:MSF
u0_a140      21475   959 0 19:47:33 ?     00:01:25 com.tencent.mobileqq:qzone

# 看到QQ的PID為 9456,這個(gè)時(shí)候打開QQ模庐,讓QQ來到前臺(tái)
-> adb shell cat /proc/9456/oom_score_adj
0

# 隨便打開一個(gè)其他的App
-> adb shell cat /proc/9456/oom_score_adj
700

# 再隨便打開另外一個(gè)其他的App
-> adb shell cat /proc/9456/oom_score_adj
900

我們可以看到adj是在根據(jù)用戶的行為不斷變化的烛愧,前臺(tái)的時(shí)候是0,到后臺(tái)是700掂碱,回到后臺(tái)后再打開其他App后是900屑彻。
常見ADJ級(jí)別如下:

ADJ級(jí)別 取值 含義
NATIVE_ADJ -1000 native進(jìn)程
SYSTEM_ADJ -900 僅指system_server進(jìn)程
PERSISTENT_PROC_ADJ -800 系統(tǒng)persistent進(jìn)程
PERSISTENT_SERVICE_ADJ -700 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程
FOREGROUND_APP_ADJ 0 前臺(tái)進(jìn)程
VISIBLE_APP_ADJ 100 可見進(jìn)程
PERCEPTIBLE_APP_ADJ 200 可感知進(jìn)程,比如后臺(tái)音樂播放
BACKUP_APP_ADJ 300 備份進(jìn)程
HEAVY_WEIGHT_APP_ADJ 400 重量級(jí)進(jìn)程
SERVICE_ADJ 500 服務(wù)進(jìn)程
HOME_APP_ADJ 600 Home進(jìn)程
PREVIOUS_APP_ADJ 700 上一個(gè)進(jìn)程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可見進(jìn)程的adj最小值
CACHED_APP_MAX_ADJ 906 不可見進(jìn)程的adj最大值

So顶吮,當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候會(huì)kill掉整個(gè)進(jìn)程,皮之不存毛將焉附粪薛,Activity也就不在了悴了,當(dāng)然也不是開頭說的那個(gè)case。

2. App內(nèi)存不足 -> 殺掉后臺(tái)Activity

上面分析了是直接kill掉進(jìn)程的情況违寿,一旦出現(xiàn)進(jìn)程被kill掉湃交,說明內(nèi)存情況已經(jīng)到了萬劫不復(fù)的情況了,拋開內(nèi)存泄漏的情況下藤巢,framework也需要一些策略來避免無內(nèi)存可用的情況搞莺。下面我們來找一找fw里面回收Activity的邏輯(代碼Base Android-30)。

Android Studio查看源碼無法查看com.android.internal包名下的代碼掂咒,雙擊Shift才沧,勾選右上角Include non-prject Items.

入口定位到ActivityThreadattach方法,ActivityThread是App的入口程序绍刮,main方法中創(chuàng)建并調(diào)用atttach温圆。

// ActivityThread.java
    private void attach(boolean system, long startSeq) {
        ...
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    // mSomeActivitiesChanged在生命周期變化的時(shí)候會(huì)修改為true
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        mSomeActivitiesChanged = false;
                        try {
                            ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                }
            });
        ...
    }

這里關(guān)注BinderInternal.addGcWatcher, 下面有幾個(gè)點(diǎn)需要理清:

  1. addGcWatcher是干嘛的,這個(gè)Runnable什么時(shí)候會(huì)被執(zhí)行孩革。
  2. 這里的maxMemory() / totalMemory() / freeMemory()都怎么理解岁歉,值有什么意義
  3. releaseSomeActivities()做了什么事情,回收Activity的邏輯是什么膝蜈。

還有一個(gè)小的點(diǎn)是這里還用了mSomeActivitiesChanged這個(gè)標(biāo)記位來標(biāo)記讓檢測工作不會(huì)過于頻繁的執(zhí)行锅移,檢測到需要releaseSomeActivities后會(huì)有一個(gè)mSomeActivitiesChanged = false;賦值熔掺。而所有的mSomeActivitiesChanged = true操作都在handleStartActivity/handleResumeActivity...等等這些操作Activity聲明周期的地方》翘辏控制了只有Activity聲明周期變化了之后才會(huì)繼續(xù)去檢測是否需要回收置逻。

2.1. GcWatcher

BinderInternal.addGcWatcher是個(gè)靜態(tài)方法,相關(guān)代碼如下:

public class BinderInternal {
    private static final String TAG = "BinderInternal";
    static WeakReference<GcWatcher> sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
    static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
    static Runnable[] sTmpWatchers = new Runnable[1];

    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());
        }
    }

    public static void addGcWatcher(Runnable watcher) {
        synchronized (sGcWatchers) {
            sGcWatchers.add(watcher);
        }
    }
    ...
}

兩個(gè)重要的角色:sGcWatcherssGcWatcher

  • sGcWatchers保存了調(diào)用BinderInternal.addGcWatcher后需要執(zhí)行的Runnable(也就是檢測是否需要kill Activity的Runnable)努潘。
  • sGcWatcher是個(gè)裝了new GcWatcher()的弱引用诽偷。

弱引用的規(guī)則是如果一個(gè)對(duì)象只有一個(gè)弱引用來引用它,那GC的時(shí)候就會(huì)回收這個(gè)對(duì)象疯坤。那很明顯new出來的這個(gè)GcWatcher()只會(huì)有sGcWatcher這一個(gè)弱引用來引用它报慕,所以每次GC都會(huì)回收這個(gè)GcWatcher對(duì)象,而回收的時(shí)候會(huì)調(diào)用這個(gè)對(duì)象的finalize()方法压怠,finalize()方法中會(huì)將之前注冊(cè)的Runnable來執(zhí)行掉眠冈。
注意哈,這里并沒有移除sGcWatcher中的Runnable菌瘫,也就是一開始通過addGcWatcher(Runnable watcher)進(jìn)來的runnable一直都在蜗顽,不管執(zhí)行多少次run的都是它。

為什么整個(gè)系統(tǒng)中addGcWatcher只有一個(gè)調(diào)用的地方雨让,但是sGcWatchers確實(shí)一個(gè)List呢雇盖?我在自己寫了這么一段代碼并且想著怎么能反射搞到系統(tǒng)當(dāng)前的BinderInternal一探究竟的時(shí)候明白了一點(diǎn)點(diǎn),我覺著他們就是怕有人主動(dòng)調(diào)用了addGcWatcher給弄了好多個(gè)GcWatcher導(dǎo)致系統(tǒng)的失效了才搞了個(gè)List吧栖忠。崔挖。

2.2. App可用的內(nèi)存

上面的Runnable是如何檢測當(dāng)前的系統(tǒng)內(nèi)存不足的呢?通過以下的代碼

        Runtime runtime = Runtime.getRuntime();
        long dalvikMax = runtime.maxMemory();
        long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
        if (dalvikUsed > ((3*dalvikMax)/4)) { ... }

看變量名字就知道庵寞,在使用的內(nèi)存到達(dá)總內(nèi)存的3/4的時(shí)候去做一些事情狸相,這幾個(gè)方法的注釋如下:

    /**
     * Returns the amount of free memory in the Java Virtual Machine. 
     * Calling the gc method may result in increasing the value returned by freeMemory.
     * @return  an approximation to the total amount of memory currently  available for future allocated objects, measured in bytes.
     */
    public native long freeMemory();

    /**
     * Returns the total amount of memory in the Java virtual machine.
     * The value returned by this method may vary over time, depending on the host environment.
     * @return  the total amount of memory currently available for current and future objects, measured in bytes.
     */
    public native long totalMemory();

    /**
     * Returns the maximum amount of memory that the Java virtual machine will attempt to use.   
     * If there is no inherent limit then the value java.lang.Long#MAX_VALUE will be returned.
     * @return  the maximum amount of memory that the virtual machine will attempt to use, measured in bytes
     */
    public native long maxMemory();

首先確認(rèn)每個(gè)App到底有多少內(nèi)存可以用,這些Runtime的值都是誰來控制的呢捐川?
可以使用adb shell getprop | grep "dalvik.vm.heap"命令來查看手機(jī)給每個(gè)虛擬機(jī)進(jìn)程所分配的堆配置信息:

yocn@yocn ~ % adb shell getprop | grep "dalvik.vm.heap"
[dalvik.vm.heapgrowthlimit]: [256m]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[dalvik.vm.heapsize]: [512m]
[dalvik.vm.heapstartsize]: [8m]
[dalvik.vm.heaptargetutilization]: [0.75]

這些值分別是什么意思呢脓鹃?

  • [dalvik.vm.heapgrowthlimit]和[dalvik.vm.heapsize]都是當(dāng)前應(yīng)用進(jìn)程可分配內(nèi)存的最大限制,一般heapgrowthlimit < heapsize古沥,如果在Manifest中的application標(biāo)簽中聲明android:largeHeap=“true”瘸右,APP直到heapsize才OOM,否則達(dá)到heapgrowthlimit就OOM
  • [dalvik.vm.heapstartsize] Java堆的起始大小渐白,指定了Davlik虛擬機(jī)在啟動(dòng)的時(shí)候向系統(tǒng)申請(qǐng)的物理內(nèi)存的大小尊浓,后面再根據(jù)需要逐漸向系統(tǒng)申請(qǐng)更多的物理內(nèi)存,直到達(dá)到MAX
  • [dalvik.vm.heapminfree] 堆最小空閑值纯衍,GC后
  • [dalvik.vm.heapmaxfree] 堆最大空閑值
  • [dalvik.vm.heaptargetutilization] 堆目標(biāo)利用率

比較難理解的就是heapminfree栋齿、heapmaxfree和heaptargetutilization了,按照上面的方法來說:
在滿足 heapminfree < freeMemory() < heapmaxfree的情況下使得(totalMemory() - freeMemory()) / totalMemory()接近heaptargetutilization

所以一開始的代碼就是當(dāng)前使用的內(nèi)存到達(dá)分配的內(nèi)存的3/4的時(shí)候會(huì)調(diào)用releaseSomeActivities去kill掉某些Activity.

2.3. releaseSomeActivities

releaseSomeActivities在API 29前后差別很大,我們來分別看一下瓦堵。

2.3.1. 基于API 28的版本的releaseSomeActivities實(shí)現(xiàn)如下:
// step①:ActivityManagerService.java
@Override
public void releaseSomeActivities(IApplicationThread appInt) {
    synchronized(this) {
        final long origId = Binder.clearCallingIdentity();
        try {
            ProcessRecord app = getRecordForAppLocked(appInt);
            mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }
}

// step②:ActivityStackSupervisor.java
void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
    TaskRecord firstTask = null;
    ArraySet<TaskRecord> tasks = null;
    for (int i = 0; i < app.activities.size(); i++) {
        ActivityRecord r = app.activities.get(i);
        // 如果當(dāng)前有正在銷毀狀態(tài)的Activity基协,Do Nothing
        if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
            return;
        }
        // 只有Activity在可以銷毀狀態(tài)的時(shí)候才繼續(xù)往下走
        if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
                || r.state == PAUSED || r.state == STOPPING) {
            continue;
        }
        if (r.task != null) {
            if (firstTask == null) {
                firstTask = r.task;
            } else if (firstTask != r.task) {
                // 2.1 只有存在兩個(gè)以上的Task的時(shí)候才會(huì)到這里
                if (tasks == null) {
                    tasks = new ArraySet<>();
                    tasks.add(firstTask);
                }
                tasks.add(r.task);
            }
        }
    }
    // 2.2 只有存在兩個(gè)以上的Task的時(shí)候才不為空
    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.
    // 2.3 遍歷找到ActivityStack釋放最舊的那個(gè)
    final int numDisplays = mActivityDisplays.size();
    for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
        final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
        // Step through all stacks starting from behind, to hit the oldest things first.
        // 從后面開始遍歷,從最舊的開始匹配
        for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
            final ActivityStack stack = stacks.get(stackNdx);
            // Try to release activities in this stack; if we manage to, we are done.
            // 嘗試在這個(gè)stack里面銷毀這些Activities菇用,如果成功就返回澜驮。
            if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
                return;
            }
        }
    }
}

上面代碼都加了注釋,我們來理一理重點(diǎn)需要關(guān)注的點(diǎn)惋鸥。整個(gè)流程可以觀察tasks的走向

  • 2.1 & 2.2: 第一次循環(huán)會(huì)給firstTask賦值杂穷,當(dāng)firstTask != r.task的時(shí)候才會(huì)給tasks賦值,后續(xù)會(huì)繼續(xù)對(duì)tasks操作卦绣。所以單棧的應(yīng)用不會(huì)回收耐量,如果tasks為null,就直接return了滤港,什么都不做
  • 2.3: 這一大段的雙重for循環(huán)其實(shí)都沒有第一步遍歷出來的tasks參與廊蜒,真正釋放Activity的操作在ActivityStack中,所以嘗試找到這些tasks對(duì)應(yīng)的ActivityStack溅漾,讓ActivityStack去銷毀tasks山叮,直到成功銷毀。

繼續(xù)查看releaseSomeActivitiesLocked:

// step③ ActivityStack.java
final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks, String reason) {
    // Iterate over tasks starting at the back (oldest) first.
    int maxTasks = tasks.size() / 4;
    if (maxTasks < 1) {
        maxTasks = 1;
    }
    // 3.1 maxTasks至少為1添履,至少清理一個(gè)
    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()) {
                destroyActivityLocked(activity, true, reason);
                if (activities.get(actNdx) != activity) {
                    // Was removed from list, back up so we don't miss the next one.
                    // 3.2 destroyActivityLocked后續(xù)會(huì)調(diào)用TaskRecord.removeActivity()屁倔,所以這里需要將index--
                    actNdx--;
                }
                curNum++;
            }
        }
        if (curNum > 0) {
            numReleased += curNum;
            // 移除一個(gè),繼續(xù)循環(huán)需要判斷 maxTasks > 0
            maxTasks--;
            if (mTaskHistory.get(taskNdx) != task) {
                // The entire task got removed, back up so we don't miss the next one.
                // 3.3 如果整個(gè)task都被移除了暮胧,這里同樣需要將獲取Task的index--汰现。移除操作在上面3.1的destroyActivityLocked,移除Activity過程中叔壤,如果task為空了,會(huì)將task移除
                taskNdx--;
            }
        }
    }
    return numReleased;
}
  • 3.1: ActivityStack利用maxTasks 保證口叙,最多清理tasks.size() / 4炼绘,最少清理1個(gè)TaskRecord,同時(shí)妄田,至少要保證保留一個(gè)前臺(tái)可見TaskRecord俺亮,比如如果有兩個(gè)TaskRecord,則清理先前的一個(gè)疟呐,保留前臺(tái)顯示的這個(gè)脚曾,如果三個(gè),則還要看看最老的是否被有效清理启具,也就是是否有Activity被清理本讥,如果有則只清理一個(gè),保留兩個(gè),如果沒有拷沸,則繼續(xù)清理次老的色查,保留一個(gè)前臺(tái)展示的,如果有四個(gè)撞芍,類似秧了,如果有5個(gè),則至少兩個(gè)清理序无。一般APP中验毡,很少有超過兩個(gè)TaskRecord的。

  • 3.2: 這里清理的邏輯很清楚帝嗡,for循環(huán)晶通,如果定位到了期望的activity就清理掉,但這里這個(gè)actNdx--是為什么呢丈探?注釋說activity從list中移除了录择,為了能繼續(xù)往下走,需要index--碗降,但在這個(gè)方法中并沒有將activity從lsit中移除的操作隘竭,那肯定是在destroyActivityLocked方法中。繼續(xù)追進(jìn)去可以一直追到TaskRecord.java#removeActivity()讼渊,從當(dāng)前的TaskRecord的mActivities中移除了动看,所以需要index--。

  • 3.3: 我們弄懂了上面的actNdx--之后也就知道這里為什么要index--了爪幻,在ActivityStack.java#removeActivityFromHistoryLocked()中有

    if (lastActivity) {
        removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
    }

如果task中沒有activity了菱皆,需要將這個(gè)task移除掉。

以上就是基于API 28的releaseSomeActivities分析挨稿。

2.3.2. 基于29+的版本的releaseSomeActivities實(shí)現(xiàn)如下:
// ActivityTaskManagerService.java
    @Override
    public void releaseSomeActivities(IApplicationThread appInt) {
        synchronized (mGlobalLock) {
            final long origId = Binder.clearCallingIdentity();
            try {
                final WindowProcessController app = getProcessController(appInt);
                app.releaseSomeActivities("low-mem");
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

// WindowProcessController.java
    void releaseSomeActivities(String reason) {
        // Examine all activities currently running in the process. Candidate activities that can be destroyed.
        // 檢查進(jìn)程里所有的activity仇轻,看哪些可以被關(guān)掉
        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++) {
            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.
            // 首先,如果我們發(fā)現(xiàn)一個(gè)activity正在執(zhí)行關(guān)閉中奶甘,在關(guān)掉這個(gè)activity之前什么都不做
            if (r.finishing || r.isState(DESTROYING, DESTROYED)) {
                if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
                return;
            }
            // Don't consider any activities that are currently not in a state where they can be destroyed.
            // 如果當(dāng)前activity不在可關(guān)閉的state的時(shí)候篷店,不做處理
            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;
            }

            if (r.getParent() != null) {
                if (candidates == null) {
                    candidates = new ArrayList<>();
                }
                candidates.add(r);
            }
        }

        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);
                r.destroyImmediately(true /*removeFromApp*/, reason);
                --maxRelease;
            } while (maxRelease > 0);
        }
    }

新版本的releaseSomeActivities放到了ActivityTaskManagerService.java這個(gè)類中,這個(gè)類是API 29新添加的臭家,承載部分AMS的工作疲陕。
相比API 28基于Task棧的回收Activity策略,新版本策略簡單清晰钉赁, 也激進(jìn)了很多蹄殃。

遍歷所有Activity,刨掉那些不在可銷毀狀態(tài)的Activity你踩,按照Activity堆疊的順序诅岩,也就是Z軸的順序讳苦,從老到新銷毀activity。

有興趣的讀者可以自行編寫測試代碼按厘,分別在API 28和API 28+的手機(jī)上測試看一下回收策略是否跟上面分析的一致医吊。
也可以參考我寫的TestKillActivity,單棧和多棧的情況下在高于API 28和低于API 28的手機(jī)上的表現(xiàn)逮京。

總結(jié):

  1. 系統(tǒng)內(nèi)存不足時(shí)LMK會(huì)根據(jù)內(nèi)存配置項(xiàng)來kill掉進(jìn)程釋放內(nèi)存
  2. kill時(shí)會(huì)按照進(jìn)程的ADJ規(guī)則來kill
  3. App內(nèi)存不足時(shí)由GcWatcher來決定回收Activity的時(shí)機(jī)
  4. 可以使用getprop命令來查看當(dāng)前手機(jī)的JVM內(nèi)存分配和OOM配置
  5. releaseSomeActivities在API 28和API 28+的差別很大卿堂,低版本會(huì)根據(jù)Task數(shù)量來決定清理哪個(gè)task的。高版本簡單粗暴懒棉,遍歷activity草描,按照z order排序,優(yōu)先release掉更老的activity策严。

參考資料:
Android lowmemorykiller分析
解讀Android進(jìn)程優(yōu)先級(jí)ADJ算法
http://www.reibang.com/p/3233c33f6a79
https://juejin.cn/post/7063068797304832030
Android可見APP的不可見任務(wù)棧(TaskRecord)銷毀分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穗慕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妻导,更是在濱河造成了極大的恐慌逛绵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倔韭,死亡現(xiàn)場離奇詭異术浪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寿酌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門胰苏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人醇疼,你說我怎么就攤上這事硕并。” “怎么了秧荆?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵倔毙,是天一觀的道長。 經(jīng)常有香客問我乙濒,道長普监,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任琉兜,我火速辦了婚禮,結(jié)果婚禮上毙玻,老公的妹妹穿的比我還像新娘豌蟋。我一直安慰自己,他們只是感情好桑滩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布梧疲。 她就那樣靜靜地躺著允睹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幌氮。 梳的紋絲不亂的頭發(fā)上缭受,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音该互,去河邊找鬼米者。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宇智,可吹牛的內(nèi)容都是我干的蔓搞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼随橘,長吁一口氣:“原來是場噩夢啊……” “哼喂分!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起机蔗,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤蒲祈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后萝嘁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梆掸,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年酿愧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沥潭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嬉挡,死狀恐怖钝鸽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庞钢,我是刑警寧澤拔恰,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站基括,受9級(jí)特大地震影響颜懊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜风皿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一河爹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桐款,春花似錦咸这、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酿雪。三九已至,卻和暖如春侄刽,著一層夾襖步出監(jiān)牢的瞬間指黎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工州丹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醋安,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓当叭,卻偏偏與公主長得像茬故,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚁鳖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容