前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Android][Framework]关于Activity回收你要知道的事情

[Android][Framework]关于Activity回收你要知道的事情

作者头像
wOw
发布2020-01-20 17:11:44
8080
发布2020-01-20 17:11:44
举报
文章被收录于专栏:wOw的Android小站wOw的Android小站

之前分析过一篇:ActivityThread流程wossoneri.github.io,简单了解了从ActivityThread创建到Application的启动流程。

但毕竟Android源码是个大工程,分析流程的时候很多地方不会特别去关注,后来在解决问题的时候又发现一些很有意思的东西,所以拎出来整理一下。

ActivityThread创建之后首先创建Handler和获取Looper,之后就调用了非常重要的attach方法。

ActivityThread.attach()

代码语言:javascript
复制
private void attach(boolean system) {
  ...
  final IActivityManager mgr = ActivityManagerNative.getDefault();
  try {
    mgr.attachApplication(mAppThread);
  } catch (RemoteException ex) {
    throw ex.rethrowFromSystemServer();
  }
  // Watch for getting close to heap limit.
  // 这里有一个watcher,上次分析被我忽略了...
  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) {
          throw e.rethrowFromSystemServer();
        }
      }
    }
  });
}

方法里有一段曾经被我忽略的代码,从字面知道,这里添加了一个GC的watcher,里面的线程运行条件是当虚拟机内存占用超过虚拟机分配的最大内存的3/4时,对一些Activity进行释放。

BinderInternal.addGcWatcher()

代码语言:javascript
复制
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();

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

BinderInternel维护了一个Runnable列表。

BinderInternal.GcWatcher

代码语言:javascript
复制
static WeakReference<GcWatcher> sGcWatcher
            = new WeakReference<GcWatcher>(new GcWatcher());
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;

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

这里重写了finalize()方法,根据JVM的原理,JVM垃圾回收器准备释放内存前,会先调用该对象finalize

重写的内容是拿到Runnable列表,依次执行每个Runnable。也就是说当执行到GC的时候,会调用到这里,然后执行Runnable的时候调用到虚拟机3/4内存的计算。

finallize方法最后重新创建了一个GcWatcher的弱引用。sGcWatcher是一个静态对象,如果它是一个强引用,那么他就会存在静态引用方法区,就会导致这个强引用的GC线程无法回收。所以作为弱引用,引用对象在被回收时就会触发sGcWatcher的finalize方法,执行结束时仔new一个弱引用出来,以保证下次的调用。

那么这里如何保证GC回收呢?

BinderInternal.forceGc()

代码语言:javascript
复制
public static void forceGc(String reason) {
  EventLog.writeEvent(2741, reason);
  VMRuntime.getRuntime().requestConcurrentGC();
}

通过查询代码,发现一共有两处调用这个方法。我们分两条调用线去看

第一种GC条件

ActivityThread.doGcIfNeeded()
代码语言:javascript
复制
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
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");
  }
}

这段代码就是根据上次GC的时间加上两次GC间隔的最小时间5s,判断当前是否要GC。再查找其调用。

ActivityThread.GcIdler
代码语言:javascript
复制
final class GcIdler implements MessageQueue.IdleHandler {
  @Override
  public final boolean queueIdle() {
    doGcIfNeeded();
    return false;
  }
}

是GcIdler类,在实现MessageQueue.IdleHandler的queueIdle方法时做的处理。

MessageQueue.IdleHandler
代码语言:javascript
复制
// 检测当一个线程将要阻塞以等待更多的message时的状态的接口
public static interface IdleHandler {
  // 当消息队列处理完消息后,等待新消息时调用。
  // 如果返回true  则保持idle handler活跃
  // 如果返回false 则代表handler被移除
  // 如果队列里仍然有消息等待,但是这些消息不会立即发送,而是计划在之后一段时间发送,也会触发该回调。
  boolean queueIdle();
}
MessageQueue.next()
代码语言:javascript
复制
Message next() {
	...
    // Run the idle handlers.
    // We only ever reach this code block during the first iteration.
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; // release the reference to the handler

      boolean keep = false;
      try {
        keep = idler.queueIdle();
      } catch (Throwable t) {
        Log.wtf(TAG, "IdleHandler threw exception", t);
      }

      if (!keep) {
        synchronized (this) {
          mIdleHandlers.remove(idler);
        }
      }
    }
  ...
}

回调在这里触发的。知道回调后再回去看回调的实现是怎么调用的。

ActivityThread
代码语言:javascript
复制
final GcIdler mGcIdler = new GcIdler();
boolean mGcIdlerScheduled = false;

public void handleMessage(Message msg) {
  case GC_WHEN_IDLE:
    scheduleGcIdler();
    break; 
}
ActivityThread.unscheduleGcIdler()
代码语言:javascript
复制
void unscheduleGcIdler() {
  if (mGcIdlerScheduled) {
    mGcIdlerScheduled = false;
    Looper.myQueue().removeIdleHandler(mGcIdler);
  }
  mH.removeMessages(H.GC_WHEN_IDLE);
}

这里主要是提一下:在所有的unscheduleGcIdler调用前都有一段注释:

代码语言:javascript
复制
private void handleLaunchActivity(ActivityClientRecord r, 
                                  Intent customIntent, String  reason) {
  // If we are getting ready to gc after going to the background, well
  // we are back active so skip it.
  unscheduleGcIdler();
  ...

调用unscheduleGcIdler的方法如下:

代码语言:javascript
复制
private void handleLaunchActivity(ActivityClientRecord r, 
                                  Intent customIntent, String reason)
private void handleReceiver(ReceiverData data)
private void handleCreateBackupAgent(CreateBackupAgentData data)
private void handleCreateService(CreateServiceData data)
final void handleResumeActivity(IBinder token, boolean clearHide, 
             boolean isForward, boolean reallyResume, int seq, String reason)
private void handleWindowVisibility(IBinder token, boolean show)
private void handleRelaunchActivity(ActivityClientRecord tmp)
ActivityThread.scheduleGcIdler()
代码语言:javascript
复制
void scheduleGcIdler() {
  if (!mGcIdlerScheduled) {
    mGcIdlerScheduled = true;
    Looper.myQueue().addIdleHandler(mGcIdler);
  }
  mH.removeMessages(H.GC_WHEN_IDLE);
}

Looper.myQueue() ,也就是主线程里的handler线程队列内容全部处理 结束,这个GcIdler 的 queueIdle() 就会被触发,那么GC就会被触发。已经到这,大概我们就明白GC调用的时机

scheduleGcIdler是Handler收到GC_WHEN_IDLE消息后触发,查询这个消息的来源:

ActivityThread.processInBackground()
代码语言:javascript
复制
public void processInBackground() {
  mH.removeMessages(H.GC_WHEN_IDLE);
  mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
}

代码上溯到AMS

ActivityManagerService.performAppGcLocked()
代码语言:javascript
复制
/**
 * 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.
  }
}
ActivityManagerService.performAppGcsLocked()
代码语言:javascript
复制
/**
 * 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();
  }
}

scheduleAppGcsLocked() 这个方法是用来通知Activity, Service, Application onLowMemory() 回调的

if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory),这个从字面理解就是,如果目前的oom_adj 比 ProcessList.PERCEPTIBLE_APP_ADJ 级别要高,或者进程在低内存环境下运行,就会触发这个方法,关于oom_adj,在后面内存优化会介绍。

以上可以得出结论,就是如果此时App正在前台显示运行,并且不是低内存状态,那么进程全局的GC就不会被触发。

第二种GC条件

ActivityThread.handleLowMemory()
代码语言:javascript
复制
final void handleLowMemory() {
  ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);

  final int N = callbacks.size();
  for (int i=0; i<N; i++) {
    callbacks.get(i).onLowMemory();
  }

  // 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");
}

方法的触发是收到LOW_MEMORY消息后开始。

消息的来源为:

ActivityThread.scheduleLowMemory

代码语言:javascript
复制
@Override
public void scheduleLowMemory() {
  sendMessage(H.LOW_MEMORY, null);
}

然后发现代码又回到了AMS

ActivityManagerService.performAppGcLocked()
代码语言:javascript
复制
/**
 * 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.
  }
}

从这个方法看出,如果是LowMemeory的情况会调用scheduleLowMemory方法,否则调用processInBackground。

我们就整明白了App回收是何时触发的,那么接下来我们来看看,Activity会被释放部分Activity这种情况。

回到addGcWatcher,里面的线程运行条件是当虚拟机内存占用超过虚拟机分配的最大内存的3/4时,就会触发GC,对一些Activity进行释放。释放Activity代码是:

代码语言:javascript
复制
final IActivityManager mgr = ActivityManagerNative.getDefault();
mgr.releaseSomeActivities(mAppThread);

所以代码又回到了ActivityManagerService中:

ActivityManagerService.releaseSomeActivities()

代码语言:javascript
复制
@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);
    }
  }
}

ActivityStackSupervisor.releaseSomeActivitiesLocked()

代码语言:javascript
复制
void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
  // 检查当前在进程中正在运行的所有activity
  TaskRecord firstTask = null;
  // 只有在2个或多个task任务栈时,tasks才不为空。也就是单栈app不会销毁。
  ArraySet<TaskRecord> tasks = null;
  if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + app);
  for (int i = 0; i < app.activities.size(); i++) {
    ActivityRecord r = app.activities.get(i);
    // 如果发现进程中的一个activity正在被destory,那么我们这次就不做任何处理。直接return掉
    // 因为在执行清理activity的操作之前最好保持一个稳定的状态。
    if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
      if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
      return;
    }
    // 如果一个activity处于不可销毁的状态就不处理这个activity
    if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
        || r.state == PAUSED || r.state == STOPPING) {
      if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
      continue;
    }
    if (r.task != null) {
      if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Collecting release task " + r.task
                                + " from " + r);
      if (firstTask == null) {
        firstTask = r.task;
      } else if (firstTask != r.task) { // 需要多一个TaskRecord
        if (tasks == null) {
          tasks = new ArraySet<>();
          tasks.add(firstTask);
        }
        tasks.add(r.task);
      }
    }
  }
  if (tasks == null) {
    // APP当前进程中,至少两个TaskRecord才有必要走Activity的销毁逻辑
    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 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.
      if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
        return;
      }
    }
  }
}

StackSupervisor 是什么?可以理解为activity任务栈的管理中心,系统所有应用的activity任务都在此管理.

ActivityStack.releaseSomeActivitiesLocked

代码语言:javascript
复制
final int releaseSomeActivitiesLocked(ProcessRecord 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);
  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;
    }
    if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Looking for activities to release in " + task);
    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.state + " 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;
}

至此之后就会执行到Activity 的 performDestroy方法进行ondestroy,然后就等待GC回收的处理了。

总结

  1. 对于非系统进程,通过BinderInternal.addGcWatcher添加了一个内存监测工具,后面会发现,这个工具的检测时机是每个GC节点。而对于我们上文说的回收不可见Task的时机是在关键点
  2. Java使用内存超过3/4的时候,调用AMS的releaseSomeActivities,尝试释放不可见Activity,当然,并非所有不可见的Activity会被回收,比如单栈的APP就不会销毁,多栈的也要分场景,可能选择性销毁不可见Activity,比如至少两个TaskRecord才有必要走Activity的销毁逻辑
  3. 该回收机制利用了Java虚拟机的gc机finalize
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ActivityThread.attach()
  • BinderInternal.addGcWatcher()
  • BinderInternal.GcWatcher
  • BinderInternal.forceGc()
  • 第一种GC条件
    • ActivityThread.doGcIfNeeded()
      • ActivityThread.GcIdler
        • MessageQueue.IdleHandler
          • MessageQueue.next()
            • ActivityThread
              • ActivityThread.unscheduleGcIdler()
                • ActivityThread.scheduleGcIdler()
                  • ActivityThread.processInBackground()
                    • ActivityManagerService.performAppGcLocked()
                      • ActivityManagerService.performAppGcsLocked()
                      • 第二种GC条件
                        • ActivityThread.handleLowMemory()
                          • ActivityManagerService.performAppGcLocked()
                          • ActivityManagerService.releaseSomeActivities()
                          • ActivityStackSupervisor.releaseSomeActivitiesLocked()
                          • ActivityStack.releaseSomeActivitiesLocked
                          相关产品与服务
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档