Android 当App内存不足或在后台运行时回收部分activity的流程分析。

以前写的文章,放到博客上当记录吧。

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; iif (sTmpWatchers[i] != null) {
                    sTmpWatchers[i].run();
                }
            }
            sGcWatcher = new WeakReference(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// Ask SQLite to free up as much memory as it can, mostly from its page caches.
        if (Process.myUid() != Process.SYSTEM_UID) {
            int sqliteReleased = SQLiteDatabase.releaseMemory();
            EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
        }

        // Ask graphics to free up as much as possible (font/image caches)
        Canvas.freeCaches();

        // Ask text layout engine to free also as much as possible
        Canvas.freeTextLayoutCaches();

        BinderInternal.forceGc("mem");
    }
  • 分析源码发现,这里有两条线,一条是 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// 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.
                  if (r.finishing || r.state == ActivityState.DESTROYING
                          || r.state == ActivityState.DESTROYED) {
                      if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r);
                      return;
                  }
                  // Don't consider any activies that are currently not in a state where they
                  // can be destroyed.
                  if (r.visible || !r.stopped || !r.haveState
                          || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING
                          || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) {
                      if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r);
                      continue;
                  }
                  if (r.task != null) {
                      if (DEBUG_RELEASE) Slog.d(TAG, "Collecting release task " + r.task
                              + " from " + r);
                      if (firstTask == null) {
                          firstTask = r.task;
                      } else if (firstTask != r.task) {
                          if (tasks == null) {
                              tasks = new ArraySet<>();
                              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开发,源码分析)