Bug 760956 - Add SimpleCancellableCursorLoader; add warning to SimpleCursorLoader. r=margaret

This bug was originally suspected to be caused by the lack of cancellation in
SimpleCursorLoader but as comments 35-37 discovered, the Android framework has
an implementation with canceling and without. As such, this bug is not
necessarily caused by the lack of canceling. However, solution was pretty close
to being complete so I figured we should land it and see if it helps the crash
at all.

--HG--
extra : commitid : AbwaGhDufxC
extra : rebase_source : 00fc3830aadc1c8894cc9dd6750630dfc0dcb211
This commit is contained in:
Michael Comella 2015-11-20 12:12:03 -08:00
Родитель 8d2594e7e7
Коммит fa5b6a3b29
3 изменённых файлов: 171 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,166 @@
/*
* This is an adapted version of Android's original CursorLoader
* with the ContentProvider-specific bits pushed outside of this class.
*
* Copyright (C) 2011 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 org.mozilla.gecko.home;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.os.CancellationSignal;
import android.support.v4.os.OperationCanceledException;
/**
* An implementation of the framework's CursorLoader class that pushes the
* ContentResolver access outside of the this class. It occurs in an abstract method
* {@link #loadCursor(CancellationSignal)}. We do this because the legacy code uses
* and wraps Cursors directly (e.g. {@link org.mozilla.gecko.db.TopSitesCursorWrapper},
* making it difficult and more work to do the ContentResolver work in here.
*
* This implementation was created because it may fix the crash from bug 760956 but the
* framework implementation ({@link android.content.CursorLoader}) is preferred.
*/
abstract class SimpleCancellableCursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
Cursor mCursor;
CancellationSignal mCancellationSignal;
public SimpleCancellableCursorLoader(Context context) {
super(context);
mObserver = new ForceLoadContentObserver();
}
/**
* Loads the target cursor for this loader. This method is called
* on a worker thread.
*/
protected abstract Cursor loadCursor(CancellationSignal cancellationSignal);
/* Runs on a worker thread */
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
Cursor cursor = loadCursor(mCancellationSignal);
if (cursor != null) {
try {
// Ensure the cursor window is filled
cursor.getCount();
cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}
@Override
public void cancelLoadInBackground() {
super.cancelLoadInBackground();
synchronized (this) {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
}
}
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
/**
* Starts an asynchronous load of the list data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*
* Must be called from the UI thread
*/
@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
/**
* Must be called from the UI thread
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
}

Просмотреть файл

@ -23,6 +23,10 @@ import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;
/**
* This implmentation may be causing the crash in bug 760956 use at your own risk.
* Prefer the framework implementation ({@link android.content.CursorLoader}) or {@link SimpleCancellableCursorLoader}.
*/
abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
Cursor mCursor;

Просмотреть файл

@ -406,6 +406,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'home/SearchEngineBar.java',
'home/SearchEngineRow.java',
'home/SearchLoader.java',
'home/SimpleCancellableCursorLoader.java',
'home/SimpleCursorLoader.java',
'home/SpacingDecoration.java',
'home/TabMenuStrip.java',