• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.memory;
6 
7 import androidx.annotation.VisibleForTesting;
8 
9 import org.chromium.base.ApplicationState;
10 import org.chromium.base.ApplicationStatus;
11 import org.chromium.base.BaseFeatureMap;
12 import org.chromium.base.BaseFeatures;
13 import org.chromium.base.MemoryPressureLevel;
14 import org.chromium.base.MemoryPressureListener;
15 import org.chromium.base.ThreadUtils;
16 import org.chromium.base.TimeUtils;
17 import org.chromium.base.metrics.RecordHistogram;
18 
19 /**
20  * This class is similar in principle to MemoryPurgeManager in blink, but on the browser process
21  * side. It triggers a critical memory pressure notification once the application has been in the
22  * background for more than a few minutes.
23  *
24  * UI thread only.
25  */
26 public class MemoryPurgeManager implements ApplicationStatus.ApplicationStateListener {
27     private boolean mStarted;
28     private long mLastBackgroundPeriodStart = NEVER;
29     private boolean mDelayedPurgeTaskPending;
30     private boolean mHasBeenInForeground;
31 
32     // Arbitrary delay, a few minutes is what is used for background renderer purge, and 5 minutes
33     // for freezing.
34     // TODO(crbug.com/1356242): Should ideally be tuned according to the distribution of background
35     // time residency.
36     @VisibleForTesting static final long PURGE_DELAY_MS = 4 * 60 * 1000;
37     private static final long NEVER = -1;
38 
39     @VisibleForTesting
40     static final String BACKGROUND_DURATION_HISTOGRAM_NAME =
41             "Android.ApplicationState.TimeInBackgroundBeforeForegroundedAgain";
42 
43     private static final MemoryPurgeManager sInstance = new MemoryPurgeManager();
44 
45     @VisibleForTesting
MemoryPurgeManager()46     MemoryPurgeManager() {}
47 
getInstance()48     public static MemoryPurgeManager getInstance() {
49         return sInstance;
50     }
51 
52     /**
53      * Start the background memory purge, if enabled. May be called several times.
54      *
55      * This attempts to trigger a critical memory pressure notification after 4 continuous minutes
56      * in background.
57      */
start()58     public void start() {
59         ThreadUtils.assertOnUiThread();
60         if (mStarted) return;
61         mStarted = true;
62         if (!BaseFeatureMap.isEnabled(BaseFeatures.BROWSER_PROCESS_MEMORY_PURGE)) return;
63 
64         ApplicationStatus.registerApplicationStateListener(this);
65 
66         // We may already be in background, capture the initial state.
67         onApplicationStateChange(getApplicationState());
68     }
69 
70     @Override
onApplicationStateChange(int state)71     public void onApplicationStateChange(int state) {
72         switch (state) {
73             case ApplicationState.UNKNOWN:
74             case ApplicationState.HAS_RUNNING_ACTIVITIES:
75             case ApplicationState.HAS_PAUSED_ACTIVITIES:
76                 if (mLastBackgroundPeriodStart != NEVER && mHasBeenInForeground) {
77                     long durationInBackgroundMs =
78                             TimeUtils.elapsedRealtimeMillis() - mLastBackgroundPeriodStart;
79                     RecordHistogram.recordLongTimesHistogram(
80                             BACKGROUND_DURATION_HISTOGRAM_NAME, durationInBackgroundMs);
81                 }
82                 mHasBeenInForeground = true;
83                 mLastBackgroundPeriodStart = NEVER;
84                 break;
85             case ApplicationState.HAS_STOPPED_ACTIVITIES:
86                 if (mLastBackgroundPeriodStart == NEVER) {
87                     mLastBackgroundPeriodStart = TimeUtils.elapsedRealtimeMillis();
88                     maybePostDelayedPurgingTask(PURGE_DELAY_MS);
89                 }
90                 break;
91             case ApplicationState.HAS_DESTROYED_ACTIVITIES:
92                 // Ignored on purpose: the initial state of a process which never had any activity
93                 // is HAS_DESTROYED_ACTIVITIES, and we don't want to trigger in this case.
94                 break;
95         }
96     }
97 
delayedPurge()98     private void delayedPurge() {
99         // Came back to foreground in the meantime, do not repost a task, this will happen next time
100         // we go to background.
101         if (mLastBackgroundPeriodStart == NEVER) return;
102 
103         assert mLastBackgroundPeriodStart < TimeUtils.elapsedRealtimeMillis();
104         long inBackgroundFor = TimeUtils.elapsedRealtimeMillis() - mLastBackgroundPeriodStart;
105         if (inBackgroundFor < PURGE_DELAY_MS) {
106             maybePostDelayedPurgingTask(PURGE_DELAY_MS - inBackgroundFor);
107             return;
108         }
109 
110         notifyMemoryPressure();
111     }
112 
113     protected void notifyMemoryPressure() {
114         MemoryPressureListener.notifyMemoryPressure(MemoryPressureLevel.CRITICAL);
115     }
116 
117     protected int getApplicationState() {
118         return ApplicationStatus.getStateForApplication();
119     }
120 
121     private void maybePostDelayedPurgingTask(long delayMillis) {
122         ThreadUtils.assertOnUiThread();
123         if (mDelayedPurgeTaskPending) return;
124 
125         ThreadUtils.postOnUiThreadDelayed(
126                 () -> {
127                     mDelayedPurgeTaskPending = false;
128                     delayedPurge();
129                 },
130                 delayMillis);
131         mDelayedPurgeTaskPending = true;
132     }
133 }
134