Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
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 | } |