Bug 1367851 - Use ComponentCallbacks to listen for low memory notifications. r?ahunt draft
authorJan Henning <jh+bugzilla@buttercookie.de>
Thu, 25 May 2017 21:22:24 +0200
changeset 584578 68e8e02db9244e4a14365bd46d860fa2b1628960
parent 584573 dc33cf1148f660a4389cad857f2fee531b1213b7
child 630451 a6a5124f9835682d42284eade4793ba0940eed45
push id60813
push usermozilla@buttercookie.de
push dateThu, 25 May 2017 19:41:04 +0000
reviewersahunt
bugs1367851
milestone55.0a1
Bug 1367851 - Use ComponentCallbacks to listen for low memory notifications. r?ahunt At the moment, we forward these from GeckoActivity, which means that they won't work if no GeckoActivity-based activity is active (can happen within our settings for example). As of API14, we can now register an app-wide ComponentCallbacks(2) class, allowing us to easily receive low memory notifications no matter which activity is currently alive. MozReview-Commit-ID: 5GjSjsTKxAD
mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
@@ -30,21 +30,9 @@ public abstract class GeckoActivity exte
             ANRReporter.unregister();
         }
         super.onDestroy();
     }
 
     public boolean isApplicationInBackground() {
         return ((GeckoApplication) getApplication()).isApplicationInBackground();
     }
-
-    @Override
-    public void onLowMemory() {
-        MemoryMonitor.getInstance().onLowMemory();
-        super.onLowMemory();
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        MemoryMonitor.getInstance().onTrimMemory(level);
-        super.onTrimMemory(level);
-    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1275,18 +1275,16 @@ public abstract class GeckoApp extends G
         // business later and dispose of the reference.
         GeckoLoader.setLastIntent(intent);
 
         // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
         try {
             Class.forName("android.os.AsyncTask");
         } catch (ClassNotFoundException e) { }
 
-        MemoryMonitor.getInstance().init(getApplicationContext());
-
         // GeckoAppShell is tightly coupled to us, rather than
         // the app context, because various parts of Fennec (e.g.,
         // GeckoScreenOrientation) use GAS to access the Activity in
         // the guise of fetching a Context.
         // When that's fixed, `this` can change to
         // `(GeckoApplication) getApplication()` here.
         GeckoAppShell.setContextGetter(this);
         GeckoAppShell.setGeckoInterface(this);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -229,16 +229,17 @@ public class GeckoApplication extends Ap
 
         mIsInitialResume = true;
 
         mRefWatcher = LeakCanary.install(this);
 
         sSessionUUID = UUID.randomUUID().toString();
 
         GeckoActivityMonitor.getInstance().initialize(this);
+        MemoryMonitor.getInstance().init(this);
 
         final Context context = getApplicationContext();
         GeckoAppShell.setApplicationContext(context);
         HardwareUtils.init(context);
         FilePicker.init(context);
         DownloadsIntegration.init();
         HomePanelsManager.getInstance().init(context);
 
--- a/mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java
+++ b/mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java
@@ -8,54 +8,58 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.home.ImageLoader;
 import org.mozilla.gecko.icons.storage.MemoryStorage;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
 /**
-  * This is a utility class to keep track of how much memory and disk-space pressure
-  * the system is under. It receives input from GeckoActivity via the onLowMemory() and
-  * onTrimMemory() functions, and also listens for some system intents related to
-  * disk-space notifications. Internally it will track how much memory and disk pressure
+  * This is a utility class to keep track of how much memory and disk-space pressure the
+  * system is under. It registers itself as a ComponentCallbacks to receive all onLowMemory()/
+  * onTrimMemory() notifications for our app, and also listens for some system intents related
+  * to disk-space notifications. Internally it will track how much memory and disk pressure
   * the system is under, and perform various actions to help alleviate the pressure.
   *
   * Note that since there is no notification for when the system has lots of free memory
   * again, this class also assumes that, over time, the system will free up memory. This
   * assumption is implemented using a timer that slowly lowers the internal memory
   * pressure state if no new low-memory notifications are received.
   *
   * Synchronization note: MemoryMonitor contains an inner class PressureDecrementer. Both
   * of these classes may be accessed from various threads, and have both been designed to
   * be thread-safe. In terms of lock ordering, code holding the PressureDecrementer lock
   * is allowed to pick up the MemoryMonitor lock, but not vice-versa.
   */
-class MemoryMonitor extends BroadcastReceiver {
+class MemoryMonitor extends BroadcastReceiver implements ComponentCallbacks2 {
     private static final String LOGTAG = "GeckoMemoryMonitor";
     private static final String ACTION_MEMORY_DUMP = "org.mozilla.gecko.MEMORY_DUMP";
     private static final String ACTION_FORCE_PRESSURE = "org.mozilla.gecko.FORCE_MEMORY_PRESSURE";
 
     // Memory pressure levels. Keep these in sync with those in AndroidJavaWrappers.h
     private static final int MEMORY_PRESSURE_NONE = 0;
     private static final int MEMORY_PRESSURE_CLEANUP = 1;
     private static final int MEMORY_PRESSURE_LOW = 2;
     private static final int MEMORY_PRESSURE_MEDIUM = 3;
     private static final int MEMORY_PRESSURE_HIGH = 4;
 
+    // We're living as long as the application, so keeping a static reference to it is okay.
+    @SuppressLint("StaticFieldLeak")
     private static final MemoryMonitor sInstance = new MemoryMonitor();
 
     static MemoryMonitor getInstance() {
         return sInstance;
     }
 
     private Context mAppContext;
     private final PressureDecrementer mPressureDecrementer;
@@ -75,30 +79,33 @@ class MemoryMonitor extends BroadcastRec
 
         mAppContext = context.getApplicationContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
         filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
         filter.addAction(ACTION_MEMORY_DUMP);
         filter.addAction(ACTION_FORCE_PRESSURE);
         mAppContext.registerReceiver(this, filter);
+        mAppContext.registerComponentCallbacks(this);
         mInited = true;
     }
 
+    @Override
     public void onLowMemory() {
         Log.d(LOGTAG, "onLowMemory() notification received");
         if (increaseMemoryPressure(MEMORY_PRESSURE_HIGH)) {
             // We need to wait on Gecko here, because if we haven't reduced
             // memory usage enough when we return from this, Android will kill us.
             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
                 GeckoThread.waitOnGecko();
             }
         }
     }
 
+    @Override
     public void onTrimMemory(int level) {
         Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
         if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
             // We seem to get this just by entering the task switcher or hitting the home button.
             // Seems bogus, because we are the foreground app, or at least not at the end of the LRU list.
             // Just ignore it, and if there is a real memory pressure event (CRITICAL, MODERATE, etc),
             // we'll respond appropriately.
             return;
@@ -271,9 +278,14 @@ class MemoryMonitor extends BroadcastRec
 
             final ContentResolver cr = mContext.getContentResolver();
             mDB.expireHistory(cr, BrowserContract.ExpirePriority.AGGRESSIVE);
             mDB.removeThumbnails(cr);
 
             // TODO: drop or shrink disk caches
         }
     }
+
+    // Needed to implement the ComponentCallbacks2 interface - we're only really interested in the
+    // memory-related calls, though.
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) { }
 }