前言:
在多年的开发过程中,遇到的问题也是很多,有常见的、有不解的,所谓的开发经验正是与遇到的问题成正比。
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()
}
}
接下来分别创建AActivity
、BActivity
、CActivity
、DActivity
最后创建申请内存的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()
}
}
结果:
可以看到,当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
获取的是IActivityTaskManager
AIDL接口,对应的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
ActivityStack
管理着TaskRecord,内部维护Activity所有状态、特殊状态的Activity和Activity相关的列表数据。
|名称|类型|说明|
|:-:|:-:|:-:|
|mTaskHistory|ArrayList
ActivityStackSupervisor
管理所有的ActivityStack。
|名称|类型|说明|
|:-:|:-:|:-:|
|mHomeStack|ActivityStack|桌⾯的stack|
|mFocusedStack|ActivityStack|当前聚焦的stack|
|mLastFocusedStack|ActivityStack|正在切换到聚焦的stack|
|mActivityDisplays|SparseArray
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
发生GC就会回收。
- 软引用(SoftReference
当内存不足时会进行回收。
当被弱引用声明的对象,在被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)原理
总结
- 当应用内存不足时,如果当前应用的ActivityTask>=2并且使用内存超过了虚拟机分配内存的3/4时,会尝试对当前应用下不可见的ActivityTask进行回收,回收顺序是先入先出,每次回收ActivityTask的个数为
int n=taskCount/4
,回收时机为每次GC的时候。 - 当系统内存不足时候,会在kernal层查找进程并回收。
最后推荐Google官方给的内存管理优化建议。