* commit 'ef4c3790e52beab467359f6b5125b66fbe1993ef': Isolate calls to each remote DocumentsProvider.
This commit is contained in:
@ -26,6 +26,7 @@ import android.util.TimeUtils;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Abstract Loader that provides an {@link AsyncTask} to do the work. See
|
||||
@ -123,6 +124,8 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
|
||||
}
|
||||
}
|
||||
|
||||
private final Executor mExecutor;
|
||||
|
||||
volatile LoadTask mTask;
|
||||
volatile LoadTask mCancellingTask;
|
||||
|
||||
@ -131,7 +134,13 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
|
||||
Handler mHandler;
|
||||
|
||||
public AsyncTaskLoader(Context context) {
|
||||
this(context, AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public AsyncTaskLoader(Context context, Executor executor) {
|
||||
super(context);
|
||||
mExecutor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,7 +232,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
|
||||
}
|
||||
}
|
||||
if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
|
||||
mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
|
||||
mTask.executeOnExecutor(mExecutor, (Void[]) null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,12 @@ public class CreateDirectoryFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final String displayName = text1.getText().toString();
|
||||
new CreateDirectoryTask(displayName).execute();
|
||||
|
||||
final DocumentsActivity activity = (DocumentsActivity) getActivity();
|
||||
final DocumentInfo cwd = activity.getCurrentDirectory();
|
||||
|
||||
new CreateDirectoryTask(displayName).executeOnExecutor(
|
||||
ProviderExecutor.forAuthority(cwd.authority));
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
@ -27,6 +27,7 @@ import static com.android.documentsui.model.DocumentInfo.getCursorInt;
|
||||
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
|
||||
import static com.android.documentsui.model.DocumentInfo.getCursorString;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
@ -113,6 +114,7 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
private boolean mHideGridTitles = false;
|
||||
|
||||
private boolean mSvelteRecents;
|
||||
private Point mThumbSize;
|
||||
|
||||
private DocumentsAdapter mAdapter;
|
||||
@ -203,6 +205,19 @@ public class DirectoryFragment extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
// Cancel any outstanding thumbnail requests
|
||||
final ViewGroup target = (mListView.getAdapter() != null) ? mListView : mGridView;
|
||||
final int count = target.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final View view = target.getChildAt(i);
|
||||
mRecycleListener.onMovedToScrapHeap(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@ -225,6 +240,10 @@ public class DirectoryFragment extends Fragment {
|
||||
mHideGridTitles = (doc != null) && doc.isGridTitlesHidden();
|
||||
}
|
||||
|
||||
final ActivityManager am = (ActivityManager) context.getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
mSvelteRecents = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
|
||||
|
||||
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
|
||||
@Override
|
||||
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
|
||||
@ -260,7 +279,7 @@ public class DirectoryFragment extends Fragment {
|
||||
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
|
||||
if (!isAdded()) return;
|
||||
|
||||
mAdapter.swapResult(result.cursor, result.exception);
|
||||
mAdapter.swapResult(result);
|
||||
|
||||
// Push latest state up to UI
|
||||
// TODO: if mode change was racing with us, don't overwrite it
|
||||
@ -286,7 +305,7 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<DirectoryResult> loader) {
|
||||
mAdapter.swapResult(null, null);
|
||||
mAdapter.swapResult(null);
|
||||
}
|
||||
};
|
||||
|
||||
@ -654,13 +673,13 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
private List<Footer> mFooters = Lists.newArrayList();
|
||||
|
||||
public void swapResult(Cursor cursor, Exception e) {
|
||||
mCursor = cursor;
|
||||
mCursorCount = cursor != null ? cursor.getCount() : 0;
|
||||
public void swapResult(DirectoryResult result) {
|
||||
mCursor = result != null ? result.cursor : null;
|
||||
mCursorCount = mCursor != null ? mCursor.getCount() : 0;
|
||||
|
||||
mFooters.clear();
|
||||
|
||||
final Bundle extras = cursor != null ? cursor.getExtras() : null;
|
||||
final Bundle extras = mCursor != null ? mCursor.getExtras() : null;
|
||||
if (extras != null) {
|
||||
final String info = extras.getString(DocumentsContract.EXTRA_INFO);
|
||||
if (info != null) {
|
||||
@ -675,7 +694,7 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
if (result != null && result.exception != null) {
|
||||
mFooters.add(new MessageFooter(
|
||||
3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
|
||||
}
|
||||
@ -776,7 +795,7 @@ public class DirectoryFragment extends Fragment {
|
||||
final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
||||
final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
|
||||
|| MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
|
||||
final boolean showThumbnail = supportsThumbnail && allowThumbnail;
|
||||
final boolean showThumbnail = supportsThumbnail && allowThumbnail && !mSvelteRecents;
|
||||
|
||||
boolean cacheHit = false;
|
||||
if (showThumbnail) {
|
||||
@ -790,7 +809,7 @@ public class DirectoryFragment extends Fragment {
|
||||
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
|
||||
uri, iconMime, iconThumb, mThumbSize);
|
||||
iconThumb.setTag(task);
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
task.executeOnExecutor(ProviderExecutor.forAuthority(docAuthority));
|
||||
}
|
||||
}
|
||||
|
||||
@ -983,6 +1002,8 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Uri... params) {
|
||||
if (isCancelled()) return null;
|
||||
|
||||
final Context context = mIconThumb.getContext();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
|
@ -79,7 +79,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
|
||||
public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
|
||||
int userSortOrder) {
|
||||
super(context);
|
||||
super(context, ProviderExecutor.forAuthority(root.authority));
|
||||
mType = type;
|
||||
mRoot = root;
|
||||
mDoc = doc;
|
||||
@ -157,11 +157,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + mUserSortOrder + " --> mode="
|
||||
+ result.mode + ", sortOrder=" + result.sortOrder);
|
||||
|
||||
ContentProviderClient client = null;
|
||||
try {
|
||||
result.client = DocumentsApplication.acquireUnstableProviderOrThrow(
|
||||
resolver, authority);
|
||||
client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
|
||||
|
||||
cursor = result.client.query(
|
||||
cursor = client.query(
|
||||
mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
|
||||
cursor.registerContentObserver(mObserver);
|
||||
|
||||
@ -175,11 +175,12 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
cursor = new SortingCursorWrapper(cursor, result.sortOrder);
|
||||
}
|
||||
|
||||
result.client = client;
|
||||
result.cursor = cursor;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to query", e);
|
||||
result.exception = e;
|
||||
ContentProviderClient.releaseQuietly(result.client);
|
||||
ContentProviderClient.releaseQuietly(client);
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
mSignal = null;
|
||||
|
@ -91,6 +91,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class DocumentsActivity extends Activity {
|
||||
public static final String TAG = "Documents";
|
||||
@ -215,7 +216,7 @@ public class DocumentsActivity extends Activity {
|
||||
if (!mState.restored) {
|
||||
if (mState.action == ACTION_MANAGE) {
|
||||
final Uri rootUri = getIntent().getData();
|
||||
new RestoreRootTask(rootUri).execute();
|
||||
new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor());
|
||||
} else {
|
||||
new RestoreStackTask().execute();
|
||||
}
|
||||
@ -782,6 +783,15 @@ public class DocumentsActivity extends Activity {
|
||||
return mState.stack.peek();
|
||||
}
|
||||
|
||||
public Executor getCurrentExecutor() {
|
||||
final DocumentInfo cwd = getCurrentDirectory();
|
||||
if (cwd != null && cwd.authority != null) {
|
||||
return ProviderExecutor.forAuthority(cwd.authority);
|
||||
} else {
|
||||
return AsyncTask.THREAD_POOL_EXECUTOR;
|
||||
}
|
||||
}
|
||||
|
||||
public State getDisplayState() {
|
||||
return mState;
|
||||
}
|
||||
@ -855,7 +865,7 @@ public class DocumentsActivity extends Activity {
|
||||
mState.stackTouched = true;
|
||||
|
||||
if (!mRoots.isRecentsRoot(root)) {
|
||||
new PickRootTask(root).execute();
|
||||
new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
|
||||
} else {
|
||||
onCurrentDirectoryChanged(ANIM_SIDE);
|
||||
}
|
||||
@ -932,7 +942,7 @@ public class DocumentsActivity extends Activity {
|
||||
onCurrentDirectoryChanged(ANIM_DOWN);
|
||||
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
|
||||
// Explicit file picked, return
|
||||
new ExistingFinishTask(doc.derivedUri).execute();
|
||||
new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
|
||||
} else if (mState.action == ACTION_CREATE) {
|
||||
// Replace selected file
|
||||
SaveFragment.get(fm).setReplaceTarget(doc);
|
||||
@ -966,16 +976,16 @@ public class DocumentsActivity extends Activity {
|
||||
for (int i = 0; i < size; i++) {
|
||||
uris[i] = docs.get(i).derivedUri;
|
||||
}
|
||||
new ExistingFinishTask(uris).execute();
|
||||
new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public void onSaveRequested(DocumentInfo replaceTarget) {
|
||||
new ExistingFinishTask(replaceTarget.derivedUri).execute();
|
||||
new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
|
||||
}
|
||||
|
||||
public void onSaveRequested(String mimeType, String displayName) {
|
||||
new CreateFinishTask(mimeType, displayName).execute();
|
||||
new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
|
||||
}
|
||||
|
||||
private void saveStackBlocking() {
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 com.android.documentsui;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class ProviderExecutor extends Thread implements Executor {
|
||||
|
||||
@GuardedBy("sExecutors")
|
||||
private static HashMap<String, ProviderExecutor> sExecutors = Maps.newHashMap();
|
||||
|
||||
public static Executor forAuthority(String authority) {
|
||||
synchronized (sExecutors) {
|
||||
ProviderExecutor executor = sExecutors.get(authority);
|
||||
if (executor == null) {
|
||||
executor = new ProviderExecutor();
|
||||
executor.setName("ProviderExecutor: " + authority);
|
||||
executor.start();
|
||||
sExecutors.put(authority, executor);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
||||
private final LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<Runnable>();
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Preconditions.checkNotNull(command);
|
||||
mQueue.add(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
final Runnable command = mQueue.take();
|
||||
command.run();
|
||||
} catch (InterruptedException e) {
|
||||
// That was weird; let's go look for more tasks.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -49,9 +49,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
@ -74,30 +72,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
/** MIME types that should always be excluded from recents. */
|
||||
private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
|
||||
|
||||
private static ExecutorService sExecutor;
|
||||
|
||||
/**
|
||||
* Create a bounded thread pool for fetching recents; it creates threads as
|
||||
* needed (up to maximum) and reclaims them when finished.
|
||||
*/
|
||||
private synchronized static ExecutorService getExecutor(Context context) {
|
||||
if (sExecutor == null) {
|
||||
final ActivityManager am = (ActivityManager) context.getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
final int maxOutstanding = am.isLowRamDevice() ? MAX_OUTSTANDING_RECENTS_SVELTE
|
||||
: MAX_OUTSTANDING_RECENTS;
|
||||
|
||||
// Create a bounded thread pool for fetching recents; it creates
|
||||
// threads as needed (up to maximum) and reclaims them when finished.
|
||||
final ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
maxOutstanding, maxOutstanding, 10, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
executor.allowCoreThreadTimeOut(true);
|
||||
sExecutor = executor;
|
||||
}
|
||||
|
||||
return sExecutor;
|
||||
}
|
||||
private final Semaphore mQueryPermits;
|
||||
|
||||
private final RootsCache mRoots;
|
||||
private final State mState;
|
||||
@ -129,6 +104,20 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
public void run() {
|
||||
if (isCancelled()) return;
|
||||
|
||||
try {
|
||||
mQueryPermits.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
runInternal();
|
||||
} finally {
|
||||
mQueryPermits.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void runInternal() {
|
||||
ContentProviderClient client = null;
|
||||
try {
|
||||
client = DocumentsApplication.acquireUnstableProviderOrThrow(
|
||||
@ -138,6 +127,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
final Cursor cursor = client.query(
|
||||
uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder));
|
||||
mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to load " + authority + ", " + rootId, e);
|
||||
} finally {
|
||||
@ -162,12 +152,17 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
super(context);
|
||||
mRoots = roots;
|
||||
mState = state;
|
||||
|
||||
// Keep clients around on high-RAM devices, since we'd be spinning them
|
||||
// up moments later to fetch thumbnails anyway.
|
||||
final ActivityManager am = (ActivityManager) getContext().getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
mQueryPermits = new Semaphore(
|
||||
am.isLowRamDevice() ? MAX_OUTSTANDING_RECENTS_SVELTE : MAX_OUTSTANDING_RECENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectoryResult loadInBackground() {
|
||||
final ExecutorService executor = getExecutor(getContext());
|
||||
|
||||
if (mFirstPassLatch == null) {
|
||||
// First time through we kick off all the recent tasks, and wait
|
||||
// around to see if everyone finishes quickly.
|
||||
@ -182,7 +177,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
|
||||
mFirstPassLatch = new CountDownLatch(mTasks.size());
|
||||
for (RecentTask task : mTasks.values()) {
|
||||
executor.execute(task);
|
||||
ProviderExecutor.forAuthority(task.authority).execute(task);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -224,7 +219,6 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
|
||||
if (LOGD) {
|
||||
Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
|
||||
Log.d(TAG, executor.toString());
|
||||
}
|
||||
|
||||
final DirectoryResult result = new DirectoryResult();
|
||||
|
@ -66,6 +66,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
|
||||
private static final boolean CHILD_WEDGE = false;
|
||||
private static final boolean CHILD_CRASH = false;
|
||||
|
||||
private static final boolean THUMB_HUNDREDS = false;
|
||||
private static final boolean THUMB_WEDGE = false;
|
||||
private static final boolean THUMB_CRASH = false;
|
||||
|
||||
@ -225,6 +226,12 @@ public class TestDocumentsProvider extends DocumentsProvider {
|
||||
includeFile(result, "localfile3", 0);
|
||||
includeFile(result, "localfile4", 0);
|
||||
|
||||
if (THUMB_HUNDREDS) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
includeFile(result, "i maded u an picshure", Document.FLAG_SUPPORTS_THUMBNAIL);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
// Try picking up an existing network fetch
|
||||
CloudTask task = mTask != null ? mTask.get() : null;
|
||||
@ -292,7 +299,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
|
||||
if (THUMB_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
|
||||
if (THUMB_WEDGE) wedgeUntilCanceled(signal);
|
||||
if (THUMB_CRASH) System.exit(12);
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
|
||||
@ -332,6 +339,18 @@ public class TestDocumentsProvider extends DocumentsProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void wedgeUntilCanceled(CancellationSignal signal) {
|
||||
if (signal != null) {
|
||||
while (true) {
|
||||
signal.throwIfCanceled();
|
||||
SystemClock.sleep(500);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "WEDGING WITHOUT A CANCELLATIONSIGNAL");
|
||||
SystemClock.sleep(Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void includeFile(MatrixCursor result, String docId, int flags) {
|
||||
final RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
|
Reference in New Issue
Block a user