resolved conflicts for merge of 6b50986a
to gingerbread-plus-aosp
Change-Id: I7c5ec7ae717e75ad242e2c1ad4537052e4eea718
This commit is contained in:
@ -1162,6 +1162,7 @@ public class Activity extends ContextThemeWrapper
|
||||
*/
|
||||
protected void onPause() {
|
||||
mCalled = true;
|
||||
QueuedWork.waitToFinish();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,7 +152,7 @@ public final class ActivityThread {
|
||||
= new ArrayList<Application>();
|
||||
// set of instantiated backup agents, keyed by package name
|
||||
final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
|
||||
static final ThreadLocal sThreadLocal = new ThreadLocal();
|
||||
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal();
|
||||
Instrumentation mInstrumentation;
|
||||
String mInstrumentationAppDir = null;
|
||||
String mInstrumentationAppPackage = null;
|
||||
@ -186,6 +186,8 @@ public final class ActivityThread {
|
||||
final GcIdler mGcIdler = new GcIdler();
|
||||
boolean mGcIdlerScheduled = false;
|
||||
|
||||
static Handler sMainThreadHandler; // set once in main()
|
||||
|
||||
private static final class ActivityClientRecord {
|
||||
IBinder token;
|
||||
int ident;
|
||||
@ -1111,7 +1113,7 @@ public final class ActivityThread {
|
||||
}
|
||||
|
||||
public static final ActivityThread currentActivityThread() {
|
||||
return (ActivityThread)sThreadLocal.get();
|
||||
return sThreadLocal.get();
|
||||
}
|
||||
|
||||
public static final String currentPackageName() {
|
||||
@ -1780,6 +1782,8 @@ public final class ActivityThread {
|
||||
}
|
||||
}
|
||||
|
||||
QueuedWork.waitToFinish();
|
||||
|
||||
try {
|
||||
if (data.sync) {
|
||||
if (DEBUG_BROADCAST) Slog.i(TAG,
|
||||
@ -2007,6 +2011,9 @@ public final class ActivityThread {
|
||||
data.args.setExtrasClassLoader(s.getClassLoader());
|
||||
}
|
||||
int res = s.onStartCommand(data.args, data.flags, data.startId);
|
||||
|
||||
QueuedWork.waitToFinish();
|
||||
|
||||
try {
|
||||
ActivityManagerNative.getDefault().serviceDoneExecuting(
|
||||
data.token, 1, data.startId, res);
|
||||
@ -2035,6 +2042,9 @@ public final class ActivityThread {
|
||||
final String who = s.getClassName();
|
||||
((ContextImpl) context).scheduleFinalCleanup(who, "Service");
|
||||
}
|
||||
|
||||
QueuedWork.waitToFinish();
|
||||
|
||||
try {
|
||||
ActivityManagerNative.getDefault().serviceDoneExecuting(
|
||||
token, 0, 0, 0);
|
||||
@ -3598,6 +3608,9 @@ public final class ActivityThread {
|
||||
Process.setArgV0("<pre-initialized>");
|
||||
|
||||
Looper.prepareMainLooper();
|
||||
if (sMainThreadHandler == null) {
|
||||
sMainThreadHandler = new Handler();
|
||||
}
|
||||
|
||||
ActivityThread thread = new ActivityThread();
|
||||
thread.attach(false);
|
||||
|
@ -116,9 +116,12 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
class ReceiverRestrictedContext extends ContextWrapper {
|
||||
ReceiverRestrictedContext(Context base) {
|
||||
@ -167,8 +170,8 @@ class ContextImpl extends Context {
|
||||
private static ThrottleManager sThrottleManager;
|
||||
private static WifiManager sWifiManager;
|
||||
private static LocationManager sLocationManager;
|
||||
private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
|
||||
new HashMap<File, SharedPreferencesImpl>();
|
||||
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
|
||||
new HashMap<String, SharedPreferencesImpl>();
|
||||
|
||||
private AudioManager mAudioManager;
|
||||
/*package*/ LoadedApk mPackageInfo;
|
||||
@ -332,15 +335,14 @@ class ContextImpl extends Context {
|
||||
@Override
|
||||
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||
SharedPreferencesImpl sp;
|
||||
File f = getSharedPrefsFile(name);
|
||||
synchronized (sSharedPrefs) {
|
||||
sp = sSharedPrefs.get(f);
|
||||
sp = sSharedPrefs.get(name);
|
||||
if (sp != null && !sp.hasFileChanged()) {
|
||||
//Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
|
||||
return sp;
|
||||
}
|
||||
}
|
||||
|
||||
File f = getSharedPrefsFile(name);
|
||||
FileInputStream str = null;
|
||||
File backup = makeBackupFile(f);
|
||||
if (backup.exists()) {
|
||||
@ -373,10 +375,10 @@ class ContextImpl extends Context {
|
||||
//Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
|
||||
sp.replace(map);
|
||||
} else {
|
||||
sp = sSharedPrefs.get(f);
|
||||
sp = sSharedPrefs.get(name);
|
||||
if (sp == null) {
|
||||
sp = new SharedPreferencesImpl(f, mode, map);
|
||||
sSharedPrefs.put(f, sp);
|
||||
sSharedPrefs.put(name, sp);
|
||||
}
|
||||
}
|
||||
return sp;
|
||||
@ -2696,10 +2698,12 @@ class ContextImpl extends Context {
|
||||
private final File mFile;
|
||||
private final File mBackupFile;
|
||||
private final int mMode;
|
||||
private Map mMap;
|
||||
private final FileStatus mFileStatus = new FileStatus();
|
||||
private long mTimestamp;
|
||||
|
||||
private Map<String, Object> mMap; // guarded by 'this'
|
||||
private long mTimestamp; // guarded by 'this'
|
||||
private int mDiskWritesInFlight = 0; // guarded by 'this'
|
||||
|
||||
private final Object mWritingToDiskLock = new Object();
|
||||
private static final Object mContent = new Object();
|
||||
private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
|
||||
|
||||
@ -2708,19 +2712,21 @@ class ContextImpl extends Context {
|
||||
mFile = file;
|
||||
mBackupFile = makeBackupFile(file);
|
||||
mMode = mode;
|
||||
mMap = initialContents != null ? initialContents : new HashMap();
|
||||
if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
|
||||
mTimestamp = mFileStatus.mtime;
|
||||
mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
|
||||
FileStatus stat = new FileStatus();
|
||||
if (FileUtils.getFileStatus(file.getPath(), stat)) {
|
||||
mTimestamp = stat.mtime;
|
||||
}
|
||||
mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
|
||||
}
|
||||
|
||||
public boolean hasFileChanged() {
|
||||
FileStatus stat = new FileStatus();
|
||||
if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
|
||||
return true;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
|
||||
return true;
|
||||
}
|
||||
return mTimestamp != mFileStatus.mtime;
|
||||
return mTimestamp != stat.mtime;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2747,7 +2753,7 @@ class ContextImpl extends Context {
|
||||
public Map<String, ?> getAll() {
|
||||
synchronized(this) {
|
||||
//noinspection unchecked
|
||||
return new HashMap(mMap);
|
||||
return new HashMap<String, Object>(mMap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2766,7 +2772,7 @@ class ContextImpl extends Context {
|
||||
}
|
||||
public long getLong(String key, long defValue) {
|
||||
synchronized (this) {
|
||||
Long v = (Long) mMap.get(key);
|
||||
Long v = (Long)mMap.get(key);
|
||||
return v != null ? v : defValue;
|
||||
}
|
||||
}
|
||||
@ -2789,10 +2795,31 @@ class ContextImpl extends Context {
|
||||
}
|
||||
}
|
||||
|
||||
public Editor edit() {
|
||||
return new EditorImpl();
|
||||
}
|
||||
|
||||
// Return value from EditorImpl#commitToMemory()
|
||||
private static class MemoryCommitResult {
|
||||
public boolean changesMade; // any keys different?
|
||||
public List<String> keysModified; // may be null
|
||||
public Set<OnSharedPreferenceChangeListener> listeners; // may be null
|
||||
public Map<?, ?> mapToWriteToDisk;
|
||||
public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
|
||||
public volatile boolean writeToDiskResult = false;
|
||||
|
||||
public void setDiskWriteResult(boolean result) {
|
||||
writeToDiskResult = result;
|
||||
writtenToDiskLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public final class EditorImpl implements Editor {
|
||||
private final Map<String, Object> mModified = Maps.newHashMap();
|
||||
private boolean mClear = false;
|
||||
|
||||
private AtomicBoolean mCommitInFlight = new AtomicBoolean(false);
|
||||
|
||||
public Editor putString(String key, String value) {
|
||||
synchronized (this) {
|
||||
mModified.put(key, value);
|
||||
@ -2839,30 +2866,67 @@ class ContextImpl extends Context {
|
||||
}
|
||||
|
||||
public void startCommit() {
|
||||
// TODO: implement
|
||||
commit();
|
||||
if (!mCommitInFlight.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException("can't call startCommit() twice");
|
||||
}
|
||||
|
||||
final MemoryCommitResult mcr = commitToMemory();
|
||||
final Runnable awaitCommit = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
mcr.writtenToDiskLatch.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QueuedWork.add(awaitCommit);
|
||||
|
||||
Runnable postWriteRunnable = new Runnable() {
|
||||
public void run() {
|
||||
awaitCommit.run();
|
||||
mCommitInFlight.set(false);
|
||||
QueuedWork.remove(awaitCommit);
|
||||
}
|
||||
};
|
||||
|
||||
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
|
||||
|
||||
// Okay to notify the listeners before it's hit disk
|
||||
// because the listeners should always get the same
|
||||
// SharedPreferences instance back, which has the
|
||||
// changes reflected in memory.
|
||||
notifyListeners(mcr);
|
||||
}
|
||||
|
||||
public boolean commit() {
|
||||
boolean returnValue;
|
||||
|
||||
boolean hasListeners;
|
||||
boolean changesMade = false;
|
||||
List<String> keysModified = null;
|
||||
Set<OnSharedPreferenceChangeListener> listeners = null;
|
||||
|
||||
// Returns true if any changes were made
|
||||
private MemoryCommitResult commitToMemory() {
|
||||
MemoryCommitResult mcr = new MemoryCommitResult();
|
||||
synchronized (SharedPreferencesImpl.this) {
|
||||
hasListeners = mListeners.size() > 0;
|
||||
// We optimistically don't make a deep copy until
|
||||
// a memory commit comes in when we're already
|
||||
// writing to disk.
|
||||
if (mDiskWritesInFlight > 0) {
|
||||
// We can't modify our mMap as a currently
|
||||
// in-flight write owns it. Clone it before
|
||||
// modifying it.
|
||||
// noinspection unchecked
|
||||
mMap = new HashMap<String, Object>(mMap);
|
||||
}
|
||||
mcr.mapToWriteToDisk = mMap;
|
||||
mDiskWritesInFlight++;
|
||||
|
||||
boolean hasListeners = mListeners.size() > 0;
|
||||
if (hasListeners) {
|
||||
keysModified = new ArrayList<String>();
|
||||
listeners =
|
||||
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
|
||||
mcr.keysModified = new ArrayList<String>();
|
||||
mcr.listeners =
|
||||
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (mClear) {
|
||||
if (!mMap.isEmpty()) {
|
||||
changesMade = true;
|
||||
mcr.changesMade = true;
|
||||
mMap.clear();
|
||||
}
|
||||
mClear = false;
|
||||
@ -2872,53 +2936,122 @@ class ContextImpl extends Context {
|
||||
String k = e.getKey();
|
||||
Object v = e.getValue();
|
||||
if (v == this) { // magic value for a removal mutation
|
||||
if (mMap.containsKey(k)) {
|
||||
mMap.remove(k);
|
||||
changesMade = true;
|
||||
if (!mMap.containsKey(k)) {
|
||||
continue;
|
||||
}
|
||||
mMap.remove(k);
|
||||
} else {
|
||||
boolean isSame = false;
|
||||
if (mMap.containsKey(k)) {
|
||||
Object existingValue = mMap.get(k);
|
||||
isSame = existingValue != null && existingValue.equals(v);
|
||||
}
|
||||
if (!isSame) {
|
||||
mMap.put(k, v);
|
||||
changesMade = true;
|
||||
if (existingValue != null && existingValue.equals(v)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mMap.put(k, v);
|
||||
}
|
||||
|
||||
mcr.changesMade = true;
|
||||
if (hasListeners) {
|
||||
keysModified.add(k);
|
||||
mcr.keysModified.add(k);
|
||||
}
|
||||
}
|
||||
|
||||
mModified.clear();
|
||||
}
|
||||
|
||||
returnValue = writeFileLocked(changesMade);
|
||||
}
|
||||
return mcr;
|
||||
}
|
||||
|
||||
if (hasListeners) {
|
||||
for (int i = keysModified.size() - 1; i >= 0; i--) {
|
||||
final String key = keysModified.get(i);
|
||||
for (OnSharedPreferenceChangeListener listener : listeners) {
|
||||
public boolean commit() {
|
||||
MemoryCommitResult mcr = commitToMemory();
|
||||
SharedPreferencesImpl.this.enqueueDiskWrite(
|
||||
mcr, null /* sync write on this thread okay */);
|
||||
try {
|
||||
mcr.writtenToDiskLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
notifyListeners(mcr);
|
||||
return mcr.writeToDiskResult;
|
||||
}
|
||||
|
||||
private void notifyListeners(final MemoryCommitResult mcr) {
|
||||
if (mcr.listeners == null || mcr.keysModified == null ||
|
||||
mcr.keysModified.size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
|
||||
final String key = mcr.keysModified.get(i);
|
||||
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
|
||||
if (listener != null) {
|
||||
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Run this function on the main thread.
|
||||
ActivityThread.sMainThreadHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
notifyListeners(mcr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
public Editor edit() {
|
||||
return new EditorImpl();
|
||||
/**
|
||||
* Enqueue an already-committed-to-memory result to be written
|
||||
* to disk.
|
||||
*
|
||||
* They will be written to disk one-at-a-time in the order
|
||||
* that they're enqueued.
|
||||
*
|
||||
* @param postWriteRunnable if non-null, we're being called
|
||||
* from startCommit() and this is the runnable to run after
|
||||
* the write proceeds. if null (from a regular commit()),
|
||||
* then we're allowed to do this disk write on the main
|
||||
* thread (which in addition to reducing allocations and
|
||||
* creating a background thread, this has the advantage that
|
||||
* we catch them in userdebug StrictMode reports to convert
|
||||
* them where possible to startCommit...)
|
||||
*/
|
||||
private void enqueueDiskWrite(final MemoryCommitResult mcr,
|
||||
final Runnable postWriteRunnable) {
|
||||
final Runnable writeToDiskRunnable = new Runnable() {
|
||||
public void run() {
|
||||
synchronized (mWritingToDiskLock) {
|
||||
writeToFile(mcr);
|
||||
}
|
||||
synchronized (SharedPreferencesImpl.this) {
|
||||
mDiskWritesInFlight--;
|
||||
}
|
||||
if (postWriteRunnable != null) {
|
||||
postWriteRunnable.run();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final boolean isFromSyncCommit = (postWriteRunnable == null);
|
||||
|
||||
// Typical #commit() path with fewer allocations, doing a write on
|
||||
// the current thread.
|
||||
if (isFromSyncCommit) {
|
||||
boolean wasEmpty = false;
|
||||
synchronized (SharedPreferencesImpl.this) {
|
||||
wasEmpty = mDiskWritesInFlight == 1;
|
||||
}
|
||||
if (wasEmpty) {
|
||||
writeToDiskRunnable.run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
|
||||
}
|
||||
|
||||
private FileOutputStream createFileOutputStream(File file) {
|
||||
private static FileOutputStream createFileOutputStream(File file) {
|
||||
FileOutputStream str = null;
|
||||
try {
|
||||
str = new FileOutputStream(file);
|
||||
@ -2941,21 +3074,24 @@ class ContextImpl extends Context {
|
||||
return str;
|
||||
}
|
||||
|
||||
private boolean writeFileLocked(boolean changesMade) {
|
||||
// Note: must hold mWritingToDiskLock
|
||||
private void writeToFile(MemoryCommitResult mcr) {
|
||||
// Rename the current file so it may be used as a backup during the next read
|
||||
if (mFile.exists()) {
|
||||
if (!changesMade) {
|
||||
if (!mcr.changesMade) {
|
||||
// If the file already exists, but no changes were
|
||||
// made to the underlying map, it's wasteful to
|
||||
// re-write the file. Return as if we wrote it
|
||||
// out.
|
||||
return true;
|
||||
mcr.setDiskWriteResult(true);
|
||||
return;
|
||||
}
|
||||
if (!mBackupFile.exists()) {
|
||||
if (!mFile.renameTo(mBackupFile)) {
|
||||
Log.e(TAG, "Couldn't rename file " + mFile
|
||||
+ " to backup file " + mBackupFile);
|
||||
return false;
|
||||
mcr.setDiskWriteResult(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mFile.delete();
|
||||
@ -2968,22 +3104,26 @@ class ContextImpl extends Context {
|
||||
try {
|
||||
FileOutputStream str = createFileOutputStream(mFile);
|
||||
if (str == null) {
|
||||
return false;
|
||||
mcr.setDiskWriteResult(false);
|
||||
return;
|
||||
}
|
||||
XmlUtils.writeMapXml(mMap, str);
|
||||
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
|
||||
str.close();
|
||||
setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
|
||||
if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
|
||||
mTimestamp = mFileStatus.mtime;
|
||||
FileStatus stat = new FileStatus();
|
||||
if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
|
||||
synchronized (this) {
|
||||
mTimestamp = stat.mtime;
|
||||
}
|
||||
}
|
||||
|
||||
// Writing was successful, delete the backup file if there is one.
|
||||
mBackupFile.delete();
|
||||
return true;
|
||||
mcr.setDiskWriteResult(true);
|
||||
return;
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.w(TAG, "writeFileLocked: Got exception:", e);
|
||||
Log.w(TAG, "writeToFile: Got exception:", e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "writeFileLocked: Got exception:", e);
|
||||
Log.w(TAG, "writeToFile: Got exception:", e);
|
||||
}
|
||||
// Clean up an unsuccessfully written file
|
||||
if (mFile.exists()) {
|
||||
@ -2991,7 +3131,7 @@ class ContextImpl extends Context {
|
||||
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
mcr.setDiskWriteResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
91
core/java/android/app/QueuedWork.java
Normal file
91
core/java/android/app/QueuedWork.java
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.app;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Internal utility class to keep track of process-global work that's
|
||||
* outstanding and hasn't been finished yet.
|
||||
*
|
||||
* This was created for writing SharedPreference edits out
|
||||
* asynchronously so we'd have a mechanism to wait for the writes in
|
||||
* Activity.onPause and similar places, but we may use this mechanism
|
||||
* for other things in the future.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class QueuedWork {
|
||||
|
||||
// The set of Runnables that will finish or wait on any async
|
||||
// activities started by the application.
|
||||
private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
|
||||
new ConcurrentLinkedQueue<Runnable>();
|
||||
|
||||
private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class
|
||||
|
||||
/**
|
||||
* Returns a single-thread Executor shared by the entire process,
|
||||
* creating it if necessary.
|
||||
*/
|
||||
public static ExecutorService singleThreadExecutor() {
|
||||
synchronized (QueuedWork.class) {
|
||||
if (sSingleThreadExecutor == null) {
|
||||
// TODO: can we give this single thread a thread name?
|
||||
sSingleThreadExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
return sSingleThreadExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a runnable to finish (or wait for) a deferred operation
|
||||
* started in this context earlier. Typically finished by e.g.
|
||||
* an Activity#onPause. Used by SharedPreferences$Editor#startCommit().
|
||||
*
|
||||
* Note that this doesn't actually start it running. This is just
|
||||
* a scratch set for callers doing async work to keep updated with
|
||||
* what's in-flight. In the common case, caller code
|
||||
* (e.g. SharedPreferences) will pretty quickly call remove()
|
||||
* after an add(). The only time these Runnables are run is from
|
||||
* waitToFinish(), below.
|
||||
*/
|
||||
public static void add(Runnable finisher) {
|
||||
sPendingWorkFinishers.add(finisher);
|
||||
}
|
||||
|
||||
public static void remove(Runnable finisher) {
|
||||
sPendingWorkFinishers.remove(finisher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes or waits for async operations to complete.
|
||||
* (e.g. SharedPreferences$Editor#startCommit writes)
|
||||
*
|
||||
* Is called from the Activity base class's onPause(), after
|
||||
* BroadcastReceiver's onReceive, after Service command handling,
|
||||
* etc. (so async work is never lost)
|
||||
*/
|
||||
public static void waitToFinish() {
|
||||
Runnable toFinish;
|
||||
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
|
||||
toFinish.run();
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,9 @@ public interface SharedPreferences {
|
||||
/**
|
||||
* Called when a shared preference is changed, added, or removed. This
|
||||
* may be called even if a preference is set to its existing value.
|
||||
*
|
||||
*
|
||||
* <p>This callback will be run on your main thread.
|
||||
*
|
||||
* @param sharedPreferences The {@link SharedPreferences} that received
|
||||
* the change.
|
||||
* @param key The key of the preference that was changed, added, or
|
||||
@ -187,9 +189,6 @@ public interface SharedPreferences {
|
||||
* <p>If you call this from an {@link android.app.Activity},
|
||||
* the base class will wait for any async commits to finish in
|
||||
* its {@link android.app.Activity#onPause}.</p>
|
||||
*
|
||||
* @return Returns true if the new values were successfully written
|
||||
* to persistent storage.
|
||||
*/
|
||||
void startCommit();
|
||||
}
|
||||
|
@ -563,13 +563,13 @@ public class Handler {
|
||||
return mMessenger;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class MessengerImpl extends IMessenger.Stub {
|
||||
public void send(Message msg) {
|
||||
Handler.this.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Message getPostMessage(Runnable r) {
|
||||
Message m = Message.obtain();
|
||||
m.callback = r;
|
||||
|
@ -1195,7 +1195,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
|
||||
|
||||
private void tryCommit(SharedPreferences.Editor editor) {
|
||||
if (mPreferenceManager.shouldCommit()) {
|
||||
editor.commit();
|
||||
editor.startCommit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1737,7 +1737,7 @@ public class BluetoothService extends IBluetooth.Stub {
|
||||
mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
|
||||
mContext.MODE_PRIVATE).edit();
|
||||
editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
|
||||
editor.commit();
|
||||
editor.startCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +165,9 @@ public class BootReceiver extends BroadcastReceiver {
|
||||
if (prefs != null) {
|
||||
long lastTime = prefs.getLong(filename, 0);
|
||||
if (lastTime == fileTime) return; // Already logged this particular file
|
||||
prefs.edit().putLong(filename, fileTime).commit();
|
||||
// TODO: move all these SharedPreferences Editor commits
|
||||
// outside this function to the end of logBootEvents
|
||||
prefs.edit().putLong(filename, fileTime).startCommit();
|
||||
}
|
||||
|
||||
Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
|
||||
|
Reference in New Issue
Block a user