mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-28 04:02:19 -05:00
MessageProvider: making sure exposed cursor gets dereferenced on close (and closed on finalization) to prevent memory leak
This commit is contained in:
parent
e8fd9683e6
commit
9db44bf4a9
@ -84,7 +84,7 @@ public class MessageProvider extends ContentProvider
|
|||||||
String INCREMENT = "id";
|
String INCREMENT = "id";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected interface QueryHandler
|
protected static interface QueryHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The path this instance is able to respond to.
|
* The path this instance is able to respond to.
|
||||||
@ -451,272 +451,359 @@ public class MessageProvider extends ContentProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class ThrottlingQueryHandler implements QueryHandler
|
/**
|
||||||
{
|
* Cursor wrapper that release a semaphore on close. Close is also triggered
|
||||||
|
* on {@link #finalize()}.
|
||||||
protected final class MonitoredCursor implements CrossProcessCursor
|
*/
|
||||||
|
protected static class MonitoredCursor implements CrossProcessCursor
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The underlying cursor implementation that handles regular
|
||||||
|
* requests
|
||||||
|
*/
|
||||||
private CrossProcessCursor mCursor;
|
private CrossProcessCursor mCursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether {@link #close()} was invoked
|
||||||
|
*/
|
||||||
|
private AtomicBoolean mClosed = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private Semaphore mSemaphore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cursor
|
||||||
|
* Never <code>null</code>.
|
||||||
|
* @param semaphore
|
||||||
|
* The semaphore to release on close. Never
|
||||||
|
* <code>null</code>.
|
||||||
|
*/
|
||||||
|
protected MonitoredCursor(final CrossProcessCursor cursor, final Semaphore semaphore)
|
||||||
|
{
|
||||||
|
this.mCursor = cursor;
|
||||||
|
this.mSemaphore = semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* Close the underlying cursor and dereference it.
|
||||||
|
*
|
||||||
|
* @see android.database.Cursor#close()
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
mCursor.close();
|
|
||||||
if (mClosed.compareAndSet(false, true))
|
if (mClosed.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
Log.d(K9.LOG_TAG, "Cursor closed, releasing semaphore");
|
mCursor.close();
|
||||||
|
Log.d(K9.LOG_TAG, "Cursor closed, null'ing & releasing semaphore");
|
||||||
|
mCursor = null;
|
||||||
mSemaphore.release();
|
mSemaphore.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed()
|
||||||
|
{
|
||||||
|
return mClosed.get() || mCursor.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
*
|
||||||
|
* Making sure cursor gets closed on garbage collection
|
||||||
|
*
|
||||||
|
* @see java.lang.Object#finalize()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkClosed() throws IllegalStateException
|
||||||
|
{
|
||||||
|
if (mClosed.get())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Cursor was closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillWindow(int pos, CursorWindow winow)
|
public void fillWindow(int pos, CursorWindow winow)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.fillWindow(pos, winow);
|
mCursor.fillWindow(pos, winow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CursorWindow getWindow()
|
public CursorWindow getWindow()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getWindow();
|
return mCursor.getWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(int oldPosition, int newPosition)
|
public boolean onMove(int oldPosition, int newPosition)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.onMove(oldPosition, newPosition);
|
return mCursor.onMove(oldPosition, newPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AtomicBoolean mClosed = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
|
|
||||||
protected MonitoredCursor(final CrossProcessCursor cursor)
|
|
||||||
{
|
|
||||||
this.mCursor = cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
|
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.copyStringToBuffer(columnIndex, buffer);
|
mCursor.copyStringToBuffer(columnIndex, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deactivate()
|
public void deactivate()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.deactivate();
|
mCursor.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getBlob(int columnIndex)
|
public byte[] getBlob(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getBlob(columnIndex);
|
return mCursor.getBlob(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnCount()
|
public int getColumnCount()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getColumnCount();
|
return mCursor.getColumnCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnIndex(String columnName)
|
public int getColumnIndex(String columnName)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getColumnIndex(columnName);
|
return mCursor.getColumnIndex(columnName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getColumnIndexOrThrow(columnName);
|
return mCursor.getColumnIndexOrThrow(columnName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName(int columnIndex)
|
public String getColumnName(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getColumnName(columnIndex);
|
return mCursor.getColumnName(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getColumnNames()
|
public String[] getColumnNames()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getColumnNames();
|
return mCursor.getColumnNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount()
|
public int getCount()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getCount();
|
return mCursor.getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getDouble(int columnIndex)
|
public double getDouble(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getDouble(columnIndex);
|
return mCursor.getDouble(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle getExtras()
|
public Bundle getExtras()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getExtras();
|
return mCursor.getExtras();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getFloat(int columnIndex)
|
public float getFloat(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getFloat(columnIndex);
|
return mCursor.getFloat(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getInt(int columnIndex)
|
public int getInt(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getInt(columnIndex);
|
return mCursor.getInt(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLong(int columnIndex)
|
public long getLong(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getLong(columnIndex);
|
return mCursor.getLong(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPosition()
|
public int getPosition()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getPosition();
|
return mCursor.getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public short getShort(int columnIndex)
|
public short getShort(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getShort(columnIndex);
|
return mCursor.getShort(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getString(int columnIndex)
|
public String getString(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getString(columnIndex);
|
return mCursor.getString(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getWantsAllOnMoveCalls()
|
public boolean getWantsAllOnMoveCalls()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.getWantsAllOnMoveCalls();
|
return mCursor.getWantsAllOnMoveCalls();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAfterLast()
|
public boolean isAfterLast()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.isAfterLast();
|
return mCursor.isAfterLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBeforeFirst()
|
public boolean isBeforeFirst()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.isBeforeFirst();
|
return mCursor.isBeforeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isClosed()
|
|
||||||
{
|
|
||||||
return mCursor.isClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFirst()
|
public boolean isFirst()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.isFirst();
|
return mCursor.isFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLast()
|
public boolean isLast()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.isLast();
|
return mCursor.isLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNull(int columnIndex)
|
public boolean isNull(int columnIndex)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.isNull(columnIndex);
|
return mCursor.isNull(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean move(int offset)
|
public boolean move(int offset)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.move(offset);
|
return mCursor.move(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveToFirst()
|
public boolean moveToFirst()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.moveToFirst();
|
return mCursor.moveToFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveToLast()
|
public boolean moveToLast()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.moveToLast();
|
return mCursor.moveToLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveToNext()
|
public boolean moveToNext()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.moveToNext();
|
return mCursor.moveToNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveToPosition(int position)
|
public boolean moveToPosition(int position)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.moveToPosition(position);
|
return mCursor.moveToPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean moveToPrevious()
|
public boolean moveToPrevious()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.moveToPrevious();
|
return mCursor.moveToPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerContentObserver(ContentObserver observer)
|
public void registerContentObserver(ContentObserver observer)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.registerContentObserver(observer);
|
mCursor.registerContentObserver(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerDataSetObserver(DataSetObserver observer)
|
public void registerDataSetObserver(DataSetObserver observer)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.registerDataSetObserver(observer);
|
mCursor.registerDataSetObserver(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requery()
|
public boolean requery()
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.requery();
|
return mCursor.requery();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle respond(Bundle extras)
|
public Bundle respond(Bundle extras)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
return mCursor.respond(extras);
|
return mCursor.respond(extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNotificationUri(ContentResolver cr, Uri uri)
|
public void setNotificationUri(ContentResolver cr, Uri uri)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.setNotificationUri(cr, uri);
|
mCursor.setNotificationUri(cr, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unregisterContentObserver(ContentObserver observer)
|
public void unregisterContentObserver(ContentObserver observer)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.unregisterContentObserver(observer);
|
mCursor.unregisterContentObserver(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unregisterDataSetObserver(DataSetObserver observer)
|
public void unregisterDataSetObserver(DataSetObserver observer)
|
||||||
{
|
{
|
||||||
|
checkClosed();
|
||||||
mCursor.unregisterDataSetObserver(observer);
|
mCursor.unregisterDataSetObserver(observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected class ThrottlingQueryHandler implements QueryHandler
|
||||||
|
{
|
||||||
|
|
||||||
private QueryHandler mDelegate;
|
private QueryHandler mDelegate;
|
||||||
|
|
||||||
public ThrottlingQueryHandler(final QueryHandler delegate)
|
public ThrottlingQueryHandler(final QueryHandler delegate)
|
||||||
@ -740,12 +827,13 @@ public class MessageProvider extends ContentProvider
|
|||||||
cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder);
|
cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder);
|
||||||
|
|
||||||
/* Android content resolvers can only process CrossProcessCursor instances */
|
/* Android content resolvers can only process CrossProcessCursor instances */
|
||||||
if (cursor == null || !(cursor instanceof CrossProcessCursor))
|
if (!(cursor instanceof CrossProcessCursor))
|
||||||
{
|
{
|
||||||
|
Log.w(K9.LOG_TAG, "Unsupported cursor, returning null: " + cursor);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor);
|
final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor, mSemaphore);
|
||||||
|
|
||||||
/* use a weak reference not to actively prevent garbage collection */
|
/* use a weak reference not to actively prevent garbage collection */
|
||||||
final WeakReference<MonitoredCursor> weakReference = new WeakReference<MonitoredCursor>(wrapped);
|
final WeakReference<MonitoredCursor> weakReference = new WeakReference<MonitoredCursor>(wrapped);
|
||||||
@ -758,7 +846,7 @@ public class MessageProvider extends ContentProvider
|
|||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
final MonitoredCursor monitored = weakReference.get();
|
final MonitoredCursor monitored = weakReference.get();
|
||||||
if (monitored != null && monitored.mClosed.compareAndSet(false, true))
|
if (monitored != null && !monitored.isClosed())
|
||||||
{
|
{
|
||||||
Log.w(K9.LOG_TAG, "Forcibly closing remotely exposed cursor");
|
Log.w(K9.LOG_TAG, "Forcibly closing remotely exposed cursor");
|
||||||
try
|
try
|
||||||
@ -865,6 +953,9 @@ public class MessageProvider extends ContentProvider
|
|||||||
|
|
||||||
private MessageHelper mMessageHelper;
|
private MessageHelper mMessageHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many simultaneous cursors we can affort to expose at once
|
||||||
|
*/
|
||||||
/* package */
|
/* package */
|
||||||
Semaphore mSemaphore = new Semaphore(1);
|
Semaphore mSemaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user