Android App及Activity回收流程分析
16lz
2021-01-23
- 首先我们要找到整个内存不足然后回调回收activity的入口,在哪里呢?在ActivityThread里,通过之前的源码阅读我们发现,当一个新的app启动的时候,系统将从Zygote进程fork一个子进程出来,当然我们知道App不一定只存在一个进程,只要定义了Activity进程要游离,我们也可以将其游离,但本质上都是由核心Android 进程 Zygote 进程去启动,但app进程启动之后,ActivityThread将被初始化,代码如下:
//路径:android/app/AppThread.java public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0(""); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
- 可以看到这个方法将AppThread初始化,形成一个贯穿整个app的主线程,那么初始化完成将会调用attach()方法,这个方法里有什么 内容呢?来看代码:
/**AppThread attch 方法入口*/private void attach(boolean system) { sCurrentActivityThread = this; mSystemThread = system; if (!system) { ViewRootImpl.addFirstDrawHandler(new Runnable() { @Override public void run() { ensureJitEnabled(); } }); android.ddm.DdmHandleAppName.setAppName("", UserHandle.myUserId()); RuntimeInit.setApplicationObject(mAppThread.asBinder()); final IActivityManager mgr = ActivityManagerNative.getDefault(); try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { // Ignore } // Watch for getting close to heap limit. /** 这里添加Gc的监听,如果超过虚拟机分配最大内存的 3/4,那么触发mgr.releaseSomeActivities */ 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 { mgr.releaseSomeActivities(mAppThread); } catch (RemoteException e) { } } } }); } else { // Don't set application object here -- if the system crashes, // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); ContextImpl context = ContextImpl.createAppContext( this, getSystemContext().mPackageInfo); mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate Application():" + e.toString(), e); } } // add dropbox logging to libcore DropBox.setReporter(new DropBoxReporter()); ViewRootImpl.addConfigCallback(new ComponentCallbacks2() { @Override public void onConfigurationChanged(Configuration newConfig) { synchronized (mResourcesManager) { // We need to apply this change to the resources // immediately, because upon returning the view // hierarchy will be informed about it. if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) { // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(newConfig)) { mPendingConfiguration = newConfig; sendMessage(H.CONFIGURATION_CHANGED, newConfig); } } } } @Override public void onLowMemory() { } @Override public void onTrimMemory(int level) { } }); }
- oK,我们很明显的看到了一个 BinderInternal.addGcWatcher()这个方法,我们可以看字面意思就知道,这个是一个Gc 回收的监听器,作用是在AppThread 作用域下的GcRoots Gc开始的时候,我们去监听这个GC回收,具体如何做到呢?我们量看一下GcWatcher 的实现思路:
//路径 BinderInternal.java -> 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(new GcWatcher()); } }
- 哈哈,看到没,这里重写了finallize()方法,从JVM原理我们就知道,第一次进行GC回收的时候,GC线程将会回调finallize()方法,注意注意!!!敲黑板!这里有个sGcWatcher, 这玩意儿是干嘛的?来来来,我们看看sGcWatcher 的定义,
//路径 BinderInternal.javastatic WeakReference sGcWatcher = new WeakReference(new GcWatcher()); static ArrayList sGcWatchers = new ArrayList<>(); static Runnable[] sTmpWatchers = new Runnable[1]; static long sLastGcTime;
- Ok,我们看到了这是一个静态弱引用GcWatcher, 目的是啥?因为因为,如果静态强引用,这个引用就存在静态引用方法区,这时这个强引用GC线程无法回收!!所以这里当然要使用弱引用,当Gc 回收之后触发弱引用的scGcWather 的finallize()方法,但是此时只会触发一次啊!当然没关系,因为在方法最后我们又重新开辟了一个新的弱引用对象,所以,这个引用逃逸了,逃过一劫。原来如此
- OK,既然都讲到这里了,我们就展开讲一下,为何这里一定会保证gc回收呢?那我们需要看一下BinderInternal 里的内容了,接下来看这一段BinderInternal里的方法:
//路径 BinderInternal.javapublic static void forceGc(String reason) { EventLog.writeEvent(2741, reason); Runtime.getRuntime().gc(); }
- Ok, 我们看到这个方法一定会去触发运行时GC,那这个方法在何处调用呢?我们倒着走回去看看:
//路径:ActivityThread.javavoid doGcIfNeeded() { mGcIdlerScheduled = false; final long now = SystemClock.uptimeMillis(); //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime() // + "m now=" + now); if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!"); BinderInternal.forceGc("bg"); } }//路径:ActivityThread.javafinal void handleLowMemory() { ArrayList callbacks = collectComponentCallbacks(true, null); final int N = callbacks.size(); for (int i=0; i
- 分析源码发现,这里有两条线,一条是 doGcIfNeeded()方法,一条是handleLowMemory(),我们之后再来分析第二条线,这条线是系统检测内存不足的情况出发的,现在先分析第一条线,doGcIfNeeded()方法,我们继续往上走,看看,是谁来调用了我们的doGcIfNeeded()方法:
//路径 : ActivityThread.java -> GcIdler final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); return false; } }
- OK, 原来是您啊,GcIdler, 我们 GcIdler 是一个扩展了IdleHandler的类,我们翻一下这个类的注释描述会发现有这么一段:/**
- Callback interface for discovering when a thread is going to block
- waiting for more messages. */ public static interface IdleHandler { /**
- Called when the message queue has run out of messages and will now
- wait for more. Return true to keep your idle handler active, false
- to have it removed. This may be called if there are still messages
- pending in the queue, but they are all scheduled to be dispatched
- after the current time. */ boolean queueIdle(); } 大概意思就说这个接口是在Messagequeue队列里的所有message都已经处理完了之后想等待更多的handler,如果queueIdle()返回true,这个handler就会一直保持存活,false执行完就丢弃,如果有持续的消息从IdleHandler进入,那么将在队列里等待。 - 这个GcIdler 事在ActivityThread 里实例化的,终于又回到了ActivityThread,我们来看看,这个方法是哪里调用的:
//路径: ActivityThread.java void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); }
- Ok,经过上面的分析我们就明白了, Looper.myQueue() ,也就是主线程里的handler线程队列内容全部处理 结束,这个GcIdler 的 queueIdle() 就会被触发,那么GC就会被触发。已经到这,大概我们就明白GC调用的时机,我们接下来继续往上走,看看,何时我们会把这个GcIdler 加入执行队列,一直往上走我们来到了ActivityManagerService, 看里面的这段代码:
//路径ActivityManagerService.java /** * Perform GCs on all processes that are waiting for it, but only * if things are idle. */ final void performAppGcsLocked() { final int N = mProcessesToGc.size(); if (N <= 0) { return; } if (canGcNowLocked()) { while (mProcessesToGc.size() > 0) { ProcessRecord proc = mProcessesToGc.remove(0); if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) { if ((proc.lastRequestedGc+GC_MIN_INTERVAL) <= SystemClock.uptimeMillis()) { // To avoid spamming the system, we will GC processes one // at a time, waiting a few seconds between each. performAppGcLocked(proc); scheduleAppGcsLocked(); return; } else { // It hasn't been long enough since we last GCed this // process... put it in the list to wait for its time. addProcessToGcListLocked(proc); break; } } } scheduleAppGcsLocked(); } }
- 上面的代码,我们看这一段, if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory),这个从字面理解就是,如果目前的oom_adj 比 ProcessList.PERCEPTIBLE_APP_ADJ 级别要高,或者进程在低内存环境下运行,就会触发这个方法,我们来看看oom_adj 是如何定义的:
//路径:ProcessList.java // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. static final int MIN_CRASH_INTERVAL = 60*1000; // OOM adjustments for processes in various states: // Adjustment used in certain places where we don't know it yet. // (Generally this is something that is going to be cached, but we // don't know the exact value in the cached range to assign yet.) static final int UNKNOWN_ADJ = 16; // This is a process only hosting activities that are not visible, // so it can be killed without any disruption. static final int CACHED_APP_MAX_ADJ = 15; static final int CACHED_APP_MIN_ADJ = 9; // The B list of SERVICE_ADJ -- these are the old and decrepit // services that aren't as shiny and interesting as the ones in the A list. static final int SERVICE_B_ADJ = 8; // This is the process of the previous application that the user was in. // This process is kept above other things, because it is very common to // switch back to the previous app. This is important both for recent // task switch (toggling between the two top recent apps) as well as normal // UI flow such as clicking on a URI in the e-mail app to view in the browser, // and then pressing back to return to e-mail. static final int PREVIOUS_APP_ADJ = 7; // This is a process holding the home application -- we want to try // avoiding killing it, even if it would normally be in the background, // because the user interacts with it so much. static final int HOME_APP_ADJ = 6; // This is a process holding an application service -- killing it will not // have much of an impact as far as the user is concerned. static final int SERVICE_ADJ = 5; // This is a process with a heavy-weight application. It is in the // background, but we want to try to avoid killing it. Value set in // system/rootdir/init.rc on startup. static final int HEAVY_WEIGHT_APP_ADJ = 4; // This is a process currently hosting a backup operation. Killing it // is not entirely fatal but is generally a bad idea. static final int BACKUP_APP_ADJ = 3; // This is a process only hosting components that are perceptible to the // user, and we really want to avoid killing them, but they are not // immediately visible. An example is background music playback. static final int PERCEPTIBLE_APP_ADJ = 2; // This is a process only hosting activities that are visible to the // user, so we'd prefer they don't disappear. static final int VISIBLE_APP_ADJ = 1; // This is the process running the current foreground app. We'd really // rather not kill it! static final int FOREGROUND_APP_ADJ = 0; // This is a process that the system or a persistent process has bound to, // and indicated it is important. static final int PERSISTENT_SERVICE_ADJ = -11; // This is a system persistent process, such as telephony. Definitely // don't want to kill it, but doing so is not completely fatal. static final int PERSISTENT_PROC_ADJ = -12; // The system process runs at the default adjustment. static final int SYSTEM_ADJ = -16; // Special code for native processes that are not being managed by the system (so // don't have an oom adj assigned by the system). static final int NATIVE_ADJ = -17; // Memory pages are 4K. static final int PAGE_SIZE = 4*1024; // The minimum number of cached apps we want to be able to keep around, // without empty apps being able to push them out of memory. static final int MIN_CACHED_APPS = 2;
- 以上可以得出结论,就是如果此时App正在前台显示运行,并且不是低内存状态,那么进程全局的GC就不会被触发!
好了,到这里我们大概已经知道这个Activity的触发时机了,接下来,我们来分析第二条线,就是ActivityThread.java 里的 final void handleLowMemory() 方法,这个又是什么时候会调用呢?我们也从这条线往上走,我们发现一样也是走到了上面ActivityManagerService 里的 performAppGcsLocked()方法,把代码复制下来我们继续分析!:
final void performAppGcsLocked() { final int N = mProcessesToGc.size(); if (N <= 0) { return; } if (canGcNowLocked()) { while (mProcessesToGc.size() > 0) { ProcessRecord proc = mProcessesToGc.remove(0); if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) { if ((proc.lastRequestedGc+GC_MIN_INTERVAL) <= SystemClock.uptimeMillis()) { // To avoid spamming the system, we will GC processes one // at a time, waiting a few seconds between each. performAppGcLocked(proc); scheduleAppGcsLocked(); return; } else { // It hasn't been long enough since we last GCed this // process... put it in the list to wait for its time. addProcessToGcListLocked(proc); break; } } } scheduleAppGcsLocked(); } }
- 看到了吗? 实际上触发GC的方法在 performAppGcLocked(proc) 这个方法里,下面scheduleAppGcsLocked() 这个方法是用来通知Activity, Service, Application onLowMemory() 回调的,那么接下来我们来看看 performAppGcLocked(proc) 方法里有什么玄机:
/** * Ask a given process to GC right now. */ final void performAppGcLocked(ProcessRecord app) { try { app.lastRequestedGc = SystemClock.uptimeMillis(); if (app.thread != null) { if (app.reportLowMemory) { app.reportLowMemory = false; app.thread.scheduleLowMemory(); } else { app.thread.processInBackground(); } } } catch (Exception e) { // whatever. } }
- 我去,这么一看,没什么玄机啊?这个代码我们发现就是说,如果是低内存情况下执行scheduleLowMemory()方法,最终由 handleLowMemory() 实现GC回收;如果说不是的话,那么只有一种情况就是App是在后台运行的情况会进行GC回收,也就是没有前台展示界面的情况,这种情况走 processInBackground() 方法,最终由 doGcIfNeeded() 方法去实现回收。
到这,我们就整明白了App回收是何时触发的,那么接下来我们来看看,Activity会被释放部分Activity这种情况,前面已经分析过了是在ActivityThread attach() 方法里进行的回调,我们再回到attach() 方法,继续看以下代码:
//路径:ActivityThread.java 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 { mgr.releaseSomeActivities(mAppThread); } catch (RemoteException e) { } } } });
- 以上代码我们可以得出结论:Activity如果要被回收,那么要在虚拟机内存使用超过系统分配的最大内存的 3 / 4, 这时将会触发一次大规模GC 释放Activity(这个不管是app是在后台运行还是app低内存状态下运行都是如此),其余情况只有Activity 在onStop() 之后才会触发。接下来看看mgr.releaseSomeActivities(mAppThread);这个方法,我们先看下mgr是个什么鬼,翻翻之前代码,你看到了这句代码final IActivityManager mgr = ActivityManagerNative.getDefault(); 哦吼,你发现了, 这玩意儿其实就是ActivityManagerService, 那么接下来我们回到AMS中,看看,这个releaseSomeActivities()函数:
//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); } } }
- 以上代码发现,实际上走到了StackSupervisor 里的 releaseSomeActivitiesLocked 方法,StackSupervisor 是什么?可以理解为activity任务栈的管理中心,系统所有应用的activity任务都在此管理:
void releaseSomeActivitiesLocked(ProcessRecord app, String reason) { // Examine all activities currently running in the process. TaskRecord firstTask = null; // Tasks is non-null only if two or more tasks are found. ArraySet tasks = null; if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app); for (int i=0; i(); tasks.add(firstTask); } tasks.add(r.task); } } } if (tasks == null) { if (DEBUG_RELEASE) Slog.d(TAG, "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 ArrayList 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. if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) { return; } } } }
if (r.finishing || r.state == ActivityState.DESTROYING || r.state == ActivityState.DESTROYED) { if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r); return; } 和 if (r.visible || !r.stopped || !r.haveState || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING)
- 从以上的这几个条件判断可以发现,只有在activity 执行到onStop并且没有被finish 或者destroy 的情况下,才会进行Activity的回收。至此之后就会执行到Activity 的 performDestroy方法进行ondestroy,然后就等待GC回收的处理了。
好了,到此我们已经知道Activity在低内存或者后台运行的时候何时回收,回收哪几种activity,怎么样回收了,我们学习了这个流程之后,将会使我们更好地去实现自己的Activity,实现更加健壮的代码
更多相关文章
- Android adb功能使用方法
- Android 学习日记(二)android studio运行github代码
- Android Apk反编译得到Java源代码
- Android 4.4 全套源码及子模块源码的下载方法
- Android 通过java代码实现EditText输入限制
- android解析XML文件的三方法之SAX
- 傻瓜式建立数据库,高效数据库操作代码的编写--android
- 15个开发者最亲睐的Android代码编辑器