Speed up ContentProvider.query() in simple case by ~30%

When query() uses bulkQuery() and we know we're going to need some
metadata right afterwards (number of rows and column index of _id, if
present), just asked for it in the initial binder transaction instead
of immediately fetching it again.

Also, this defers loading column names until the client asks for them.

This gets down the simpler (and very common) use cases of
ContentProvider.query() down to 3 binder calls:

   QUERY_TRANSACTION to android.content.ContentProvider$Transport
   GET_CURSOR_WINDOW_TRANSACTION to android.database.CursorToBulkCursorAdaptor
   CLOSE_TRANSACTION to android.database.CursorToBulkCursorAdaptor

More can still be done, but this is a good bite-sized first piece.

Change-Id: I7ad45949f53e0097ff18c2478d659f0f36929693
This commit is contained in:
Brad Fitzpatrick
2010-03-09 13:18:02 -08:00
parent 33d1fdd6aa
commit 9ffdfa0c23
5 changed files with 101 additions and 32 deletions

View File

@@ -73,7 +73,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case QUERY_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
Uri url = Uri.CREATOR.createFromParcel(data);
// String[] projection
int num = data.readInt();
String[] projection = null;
if (num > 0) {
@@ -82,6 +85,8 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
projection[i] = data.readString();
}
}
// String selection, String[] selectionArgs...
String selection = data.readString();
num = data.readInt();
String[] selectionArgs = null;
@@ -91,19 +96,33 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
selectionArgs[i] = data.readString();
}
}
String sortOrder = data.readString();
IContentObserver observer = IContentObserver.Stub.
asInterface(data.readStrongBinder());
CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
// Flag for whether caller wants the number of
// rows in the cursor and the position of the
// "_id" column index (or -1 if non-existent)
// Only to be returned if binder != null.
boolean wantsCursorMetadata = data.readInt() != 0;
IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
selectionArgs, sortOrder, observer, window);
reply.writeNoException();
if (bulkCursor != null) {
reply.writeStrongBinder(bulkCursor.asBinder());
if (wantsCursorMetadata) {
reply.writeInt(bulkCursor.count());
reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
bulkCursor.getColumnNames()));
}
} else {
reply.writeStrongBinder(null);
}
return true;
}
@@ -266,9 +285,12 @@ final class ContentProviderProxy implements IContentProvider
return mRemote;
}
public IBulkCursor bulkQuery(Uri url, String[] projection,
String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
CursorWindow window) throws RemoteException {
// Like bulkQuery() but sets up provided 'adaptor' if not null.
private IBulkCursor bulkQueryInternal(
Uri url, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
IContentObserver observer, CursorWindow window,
BulkCursorToCursorAdaptor adaptor) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -297,6 +319,12 @@ final class ContentProviderProxy implements IContentProvider
data.writeStrongBinder(observer.asBinder());
window.writeToParcel(data, 0);
// Flag for whether or not we want the number of rows in the
// cursor and the position of the "_id" column index (or -1 if
// non-existent). Only to be returned if binder != null.
final boolean wantsCursorMetadata = (adaptor != null);
data.writeInt(wantsCursorMetadata ? 1 : 0);
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
@@ -305,26 +333,43 @@ final class ContentProviderProxy implements IContentProvider
IBinder bulkCursorBinder = reply.readStrongBinder();
if (bulkCursorBinder != null) {
bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
if (wantsCursorMetadata) {
int rowCount = reply.readInt();
int idColumnPosition = reply.readInt();
if (bulkCursor != null) {
adaptor.set(bulkCursor, rowCount, idColumnPosition);
}
}
}
data.recycle();
reply.recycle();
return bulkCursor;
}
public IBulkCursor bulkQuery(Uri url, String[] projection,
String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
CursorWindow window) throws RemoteException {
return bulkQueryInternal(
url, projection, selection, selectionArgs, sortOrder,
observer, window,
null /* BulkCursorToCursorAdaptor */);
}
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
//TODO make a pool of windows so we can reuse memory dealers
CursorWindow window = new CursorWindow(false /* window will be used remotely */);
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
IBulkCursor bulkCursor = bulkQuery(url, projection, selection, selectionArgs, sortOrder,
adaptor.getObserver(), window);
IBulkCursor bulkCursor = bulkQueryInternal(
url, projection, selection, selectionArgs, sortOrder,
adaptor.getObserver(), window,
adaptor);
if (bulkCursor == null) {
return null;
}
adaptor.set(bulkCursor);
return adaptor;
}

View File

@@ -43,25 +43,43 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
try {
mCount = mBulkCursor.count();
mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls();
// Search for the rowID column index and set it for our parent
mColumns = mBulkCursor.getColumnNames();
int length = mColumns.length;
for (int i = 0; i < length; i++) {
if (mColumns[i].equals("_id")) {
mRowIdColumnIndex = i;
break;
}
}
mRowIdColumnIndex = findRowIdColumnIndex(mColumns);
} catch (RemoteException ex) {
Log.e(TAG, "Setup failed because the remote process is dead");
}
}
/**
* Version of set() that does fewer Binder calls if the caller
* already knows BulkCursorToCursorAdaptor's properties.
*/
public void set(IBulkCursor bulkCursor, int count, int idIndex) {
mBulkCursor = bulkCursor;
mColumns = null; // lazily retrieved
mCount = count;
mRowIdColumnIndex = idIndex;
}
/**
* Returns column index of "_id" column, or -1 if not found.
*/
public static int findRowIdColumnIndex(String[] columnNames) {
int length = columnNames.length;
for (int i = 0; i < length; i++) {
if (columnNames[i].equals("_id")) {
return i;
}
}
return -1;
}
/**
* Gets a SelfDataChangeOberserver that can be sent to a remote
* process to receive change notifications over IPC.
*
*
* @return A SelfContentObserver hooked up to this Cursor
*/
public synchronized IContentObserver getObserver() {
@@ -190,6 +208,14 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
@Override
public String[] getColumnNames() {
if (mColumns == null) {
try {
mColumns = mBulkCursor.getColumnNames();
} catch (RemoteException ex) {
Log.e(TAG, "Unable to fetch column names because the remote process is dead");
return null;
}
}
return mColumns;
}
@@ -255,4 +281,3 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
}
}
}

View File

@@ -44,7 +44,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
/**
* Returns the starting position of this window within the entire
* Cursor's result set.
*
*
* @return the starting position of this window within the entire
* Cursor's result set.
*/

View File

@@ -27,11 +27,10 @@ import java.util.Map;
* This interface provides a low-level way to pass bulk cursor data across
* both process and language boundries. Application code should use the Cursor
* interface directly.
*
*
* {@hide}
*/
public interface IBulkCursor extends IInterface
{
public interface IBulkCursor extends IInterface {
/**
* Returns a BulkCursorWindow, which either has a reference to a shared
* memory segment with the rows, or an array of JSON strings.
@@ -60,7 +59,7 @@ public interface IBulkCursor extends IInterface
public boolean deleteRow(int position) throws RemoteException;
public void deactivate() throws RemoteException;
public void close() throws RemoteException;
public int requery(IContentObserver observer, CursorWindow window) throws RemoteException;
@@ -87,4 +86,3 @@ public interface IBulkCursor extends IInterface
static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
}

View File

@@ -19,14 +19,15 @@ package android.database.sqlite;
import android.database.CursorWindow;
/**
* An object create from a SQLiteDatabase that can be closed.
* An object created from a SQLiteDatabase that can be closed.
*/
public abstract class SQLiteClosable {
public abstract class SQLiteClosable {
private int mReferenceCount = 1;
private Object mLock = new Object();
protected abstract void onAllReferencesReleased();
protected void onAllReferencesReleasedFromContainer(){}
protected void onAllReferencesReleasedFromContainer() {}
public void acquireReference() {
synchronized(mLock) {
if (mReferenceCount <= 0) {
@@ -36,7 +37,7 @@ public abstract class SQLiteClosable {
mReferenceCount++;
}
}
public void releaseReference() {
synchronized(mLock) {
mReferenceCount--;
@@ -45,14 +46,14 @@ public abstract class SQLiteClosable {
}
}
}
public void releaseReferenceFromContainer() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleasedFromContainer();
}
}
}
}
private String getObjInfo() {