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