mobile/android/base/MemoryMonitor.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial