mobile/android/base/MemoryMonitor.java

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import org.mozilla.gecko.db.BrowserContract;
     9 import org.mozilla.gecko.db.BrowserDB;
    10 import org.mozilla.gecko.favicons.Favicons;
    11 import org.mozilla.gecko.util.ThreadUtils;
    13 import android.content.BroadcastReceiver;
    14 import android.content.ComponentCallbacks2;
    15 import android.content.Context;
    16 import android.content.Intent;
    17 import android.content.IntentFilter;
    18 import android.os.Build;
    19 import android.util.Log;
    21 /**
    22   * This is a utility class to keep track of how much memory and disk-space pressure
    23   * the system is under. It receives input from GeckoActivity via the onLowMemory() and
    24   * onTrimMemory() functions, and also listens for some system intents related to
    25   * disk-space notifications. Internally it will track how much memory and disk pressure
    26   * the system is under, and perform various actions to help alleviate the pressure.
    27   *
    28   * Note that since there is no notification for when the system has lots of free memory
    29   * again, this class also assumes that, over time, the system will free up memory. This
    30   * assumption is implemented using a timer that slowly lowers the internal memory
    31   * pressure state if no new low-memory notifications are received.
    32   *
    33   * Synchronization note: MemoryMonitor contains an inner class PressureDecrementer. Both
    34   * of these classes may be accessed from various threads, and have both been designed to
    35   * be thread-safe. In terms of lock ordering, code holding the PressureDecrementer lock
    36   * is allowed to pick up the MemoryMonitor lock, but not vice-versa.
    37   */
    38 class MemoryMonitor extends BroadcastReceiver {
    39     private static final String LOGTAG = "GeckoMemoryMonitor";
    40     private static final String ACTION_MEMORY_DUMP = "org.mozilla.gecko.MEMORY_DUMP";
    41     private static final String ACTION_FORCE_PRESSURE = "org.mozilla.gecko.FORCE_MEMORY_PRESSURE";
    43     // Memory pressue levels, keep in sync with those in AndroidJavaWrappers.h
    44     private static final int MEMORY_PRESSURE_NONE = 0;
    45     private static final int MEMORY_PRESSURE_CLEANUP = 1;
    46     private static final int MEMORY_PRESSURE_LOW = 2;
    47     private static final int MEMORY_PRESSURE_MEDIUM = 3;
    48     private static final int MEMORY_PRESSURE_HIGH = 4;
    50     private static MemoryMonitor sInstance = new MemoryMonitor();
    52     static MemoryMonitor getInstance() {
    53         return sInstance;
    54     }
    56     private final PressureDecrementer mPressureDecrementer;
    57     private int mMemoryPressure;
    58     private boolean mStoragePressure;
    59     private boolean mInited;
    61     private MemoryMonitor() {
    62         mPressureDecrementer = new PressureDecrementer();
    63         mMemoryPressure = MEMORY_PRESSURE_NONE;
    64         mStoragePressure = false;
    65     }
    67     public void init(final Context context) {
    68         if (mInited) {
    69             return;
    70         }
    72         IntentFilter filter = new IntentFilter();
    73         filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
    74         filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
    75         filter.addAction(ACTION_MEMORY_DUMP);
    76         filter.addAction(ACTION_FORCE_PRESSURE);
    77         context.getApplicationContext().registerReceiver(this, filter);
    78         mInited = true;
    79     }
    81     public void onLowMemory() {
    82         Log.d(LOGTAG, "onLowMemory() notification received");
    83         if (increaseMemoryPressure(MEMORY_PRESSURE_HIGH)) {
    84             // We need to wait on Gecko here, because if we haven't reduced
    85             // memory usage enough when we return from this, Android will kill us.
    86             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createNoOpEvent());
    87         }
    88     }
    90     public void onTrimMemory(int level) {
    91         Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
    92         if (Build.VERSION.SDK_INT < 14) {
    93             // this won't even get called pre-ICS
    94             return;
    95         }
    97         if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
    98             increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
    99         } else if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
   100             increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
   101         } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
   102             // includes TRIM_MEMORY_BACKGROUND
   103             increaseMemoryPressure(MEMORY_PRESSURE_CLEANUP);
   104         } else {
   105             // levels down here mean gecko is the foreground process so we
   106             // should be less aggressive with wiping memory as it may impact
   107             // user experience.
   108             increaseMemoryPressure(MEMORY_PRESSURE_LOW);
   109         }
   110     }
   112     @Override
   113     public void onReceive(Context context, Intent intent) {
   114         if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
   115             Log.d(LOGTAG, "Device storage is low");
   116             mStoragePressure = true;
   117             ThreadUtils.postToBackgroundThread(new StorageReducer(context));
   118         } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
   119             Log.d(LOGTAG, "Device storage is ok");
   120             mStoragePressure = false;
   121         } else if (ACTION_MEMORY_DUMP.equals(intent.getAction())) {
   122             String label = intent.getStringExtra("label");
   123             if (label == null) {
   124                 label = "default";
   125             }
   126             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Memory:Dump", label));
   127         } else if (ACTION_FORCE_PRESSURE.equals(intent.getAction())) {
   128             increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
   129         }
   130     }
   132     private boolean increaseMemoryPressure(int level) {
   133         int oldLevel;
   134         synchronized (this) {
   135             // bump up our level if we're not already higher
   136             if (mMemoryPressure > level) {
   137                 return false;
   138             }
   139             oldLevel = mMemoryPressure;
   140             mMemoryPressure = level;
   141         }
   143         // since we don't get notifications for when memory pressure is off,
   144         // we schedule our own timer to slowly back off the memory pressure level.
   145         // note that this will reset the time to next decrement if the decrementer
   146         // is already running, which is the desired behaviour because we just got
   147         // a new low-mem notification.
   148         mPressureDecrementer.start();
   150         if (oldLevel == level) {
   151             // if we're not going to a higher level we probably don't
   152             // need to run another round of the same memory reductions
   153             // we did on the last memory pressure increase.
   154             return false;
   155         }
   157         // TODO hook in memory-reduction stuff for different levels here
   158         if (level >= MEMORY_PRESSURE_MEDIUM) {
   159             //Only send medium or higher events because that's all that is used right now
   160             if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   161                 GeckoAppShell.dispatchMemoryPressure();
   162             }
   164             Favicons.clearMemCache();
   165         }
   166         return true;
   167     }
   169     private boolean decreaseMemoryPressure() {
   170         int newLevel;
   171         synchronized (this) {
   172             if (mMemoryPressure <= 0) {
   173                 return false;
   174             }
   176             newLevel = --mMemoryPressure;
   177         }
   178         Log.d(LOGTAG, "Decreased memory pressure to " + newLevel);
   180         return true;
   181     }
   183     class PressureDecrementer implements Runnable {
   184         private static final int DECREMENT_DELAY = 5 * 60 * 1000; // 5 minutes
   186         private boolean mPosted;
   188         synchronized void start() {
   189             if (mPosted) {
   190                 // cancel the old one before scheduling a new one
   191                 ThreadUtils.getBackgroundHandler().removeCallbacks(this);
   192             }
   193             ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
   194             mPosted = true;
   195         }
   197         @Override
   198         public synchronized void run() {
   199             if (!decreaseMemoryPressure()) {
   200                 // done decrementing, bail out
   201                 mPosted = false;
   202                 return;
   203             }
   205             // need to keep decrementing
   206             ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
   207         }
   208     }
   210     class StorageReducer implements Runnable {
   211         private final Context mContext;
   212         public StorageReducer(final Context context) {
   213             this.mContext = context;
   214         }
   216         @Override
   217         public void run() {
   218             // this might get run right on startup, if so wait 10 seconds and try again
   219             if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   220                 ThreadUtils.getBackgroundHandler().postDelayed(this, 10000);
   221                 return;
   222             }
   224             if (!mStoragePressure) {
   225                 // pressure is off, so we can abort
   226                 return;
   227             }
   229             BrowserDB.expireHistory(mContext.getContentResolver(),
   230                                     BrowserContract.ExpirePriority.AGGRESSIVE);
   231             BrowserDB.removeThumbnails(mContext.getContentResolver());
   232             // TODO: drop or shrink disk caches
   233         }
   234     }
   235 }

mercurial