GeckoThread.LaunchState now covers the entire GeckoThread lifetime and not just launch, so it's renamed to GeckoThread.State. More utility methods are added to check for the current state.
274 lines
11 KiB
Java
274 lines
11 KiB
Java
/* -*- 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.AppConstants.Versions;
|
|
import org.mozilla.gecko.db.BrowserDB;
|
|
import org.mozilla.gecko.db.BrowserContract;
|
|
import org.mozilla.gecko.favicons.Favicons;
|
|
import org.mozilla.gecko.home.ImageLoader;
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
|
|
|
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.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 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;
|
|
|
|
private static final MemoryMonitor sInstance = new MemoryMonitor();
|
|
|
|
static MemoryMonitor getInstance() {
|
|
return sInstance;
|
|
}
|
|
|
|
private final PressureDecrementer mPressureDecrementer;
|
|
private int mMemoryPressure; // Synchronized access only.
|
|
private volatile boolean mStoragePressure; // Accessed via UI thread intent, background runnables.
|
|
private boolean mInited;
|
|
|
|
private MemoryMonitor() {
|
|
mPressureDecrementer = new PressureDecrementer();
|
|
mMemoryPressure = MEMORY_PRESSURE_NONE;
|
|
}
|
|
|
|
public void init(final Context context) {
|
|
if (mInited) {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
mInited = true;
|
|
}
|
|
|
|
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 (Versions.preICS) {
|
|
// This won't even get called pre-ICS.
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
switch (level) {
|
|
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
|
|
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
|
|
// TRIM_MEMORY_MODERATE is the highest level we'll respond to while backgrounded
|
|
increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
|
|
break;
|
|
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
|
|
increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
|
|
break;
|
|
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
|
|
increaseMemoryPressure(MEMORY_PRESSURE_LOW);
|
|
break;
|
|
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
|
|
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
|
|
increaseMemoryPressure(MEMORY_PRESSURE_CLEANUP);
|
|
break;
|
|
default:
|
|
Log.d(LOGTAG, "Unhandled onTrimMemory() level " + level);
|
|
break;
|
|
}
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
Log.d(LOGTAG, "increasing memory pressure to " + 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.isRunning()) {
|
|
GeckoAppShell.dispatchMemoryPressure();
|
|
}
|
|
|
|
Favicons.clearMemCache();
|
|
ImageLoader.clearLruCache();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Thread-safe due to mStoragePressure's volatility.
|
|
*/
|
|
boolean isUnderStoragePressure() {
|
|
return mStoragePressure;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
private static class StorageReducer implements Runnable {
|
|
private final Context mContext;
|
|
private final BrowserDB mDB;
|
|
|
|
public StorageReducer(final Context context) {
|
|
this.mContext = context;
|
|
// Since this may be called while Fennec is in the background, we don't want to risk accidentally
|
|
// using the wrong context. If the profile we get is a guest profile, use the default profile instead.
|
|
GeckoProfile profile = GeckoProfile.get(mContext);
|
|
if (profile.inGuestMode()) {
|
|
// If it was the guest profile, switch to the default one.
|
|
profile = GeckoProfile.get(mContext, GeckoProfile.DEFAULT_PROFILE);
|
|
}
|
|
|
|
mDB = profile.getDB();
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
// this might get run right on startup, if so wait 10 seconds and try again
|
|
if (!GeckoThread.isRunning()) {
|
|
ThreadUtils.getBackgroundHandler().postDelayed(this, 10000);
|
|
return;
|
|
}
|
|
|
|
if (!MemoryMonitor.getInstance().isUnderStoragePressure()) {
|
|
// Pressure is off, so we can abort.
|
|
return;
|
|
}
|
|
|
|
final ContentResolver cr = mContext.getContentResolver();
|
|
mDB.expireHistory(cr, BrowserContract.ExpirePriority.AGGRESSIVE);
|
|
mDB.removeThumbnails(cr);
|
|
|
|
// TODO: drop or shrink disk caches
|
|
}
|
|
}
|
|
}
|