Android App及Activity回收流程分析

  • 首先我们要找到整个内存不足然后回调回收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.java
static 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.java
public static void forceGc(String reason) {
        EventLog.writeEvent(2741, reason);
        Runtime.getRuntime().gc();
    }
  • Ok, 我们看到这个方法一定会去触发运行时GC,那这个方法在何处调用呢?我们倒着走回去看看:
//路径:ActivityThread.java
void 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.java
final 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,android)