Log:
09-13 11:46:42.093 14778 17309 I dalvikvm: Ljava/lang/RuntimeException;: No memory in memObj
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.database.CursorWindow.native_init(Native Method)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.database.CursorWindow.<init>(CursorWindow.java:569)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.database.CursorWindow.<init>(CursorWindow.java:36)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:544)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:542)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:116)
09-13 11:46:42.093 14778 17309 I dalvikvm: at android.os.Binder.execTransact(Binder.java:336)
09-13 11:46:42.093 14778 17309 I dalvikvm: at dalvik.system.NativeStart.run(Native Method)
09-13 11:46:42.093 14778 17309 I dalvikvm: "Binder Thread #3" prio=5 tid=10 NATIVE
09-13 11:46:42.093 14778 17309 I dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b28
09-13 11:46:42.093 14778 17309 I dalvikvm: | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=3356232
09-13 11:46:42.093 14778 17309 I dalvikvm: | schedstat=( 1261444095 8805053706 2920 )
09-13 11:46:42.093 14778 17309 I dalvikvm: at dalvik.system.NativeStart.run(Native Method)
09-13 11:46:42.093 14778 17309 E dalvikvm: VM aborting
09-13 11:46:42.093 14778 17309 I dalvikvm: "Binder Thread #3" prio=5 tid=10 NATIVE
09-13 11:46:42.093 14778 17309 I dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b28
09-13 11:46:42.093 14778 17309 I dalvikvm: | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=3356232
09-13 11:46:42.093 14778 17309 I dalvikvm: | schedstat=( 1261444095 8805053706 2920 )
09-13 11:46:42.093 14778 17309 I dalvikvm: at dalvik.system.NativeStart.run(Native Method)
分析:
檢查代碼位置,此Exception出現在MediaProvider Server端響應QUERY_TRANSACTION時,由於傳來的Parcel指向的內存地址爲空引起。
考慮整個調用流程,CursorWindow實例由ContentProviderProxy在Binder調用前時產生,故此對象產生於用戶進程,並傳給Server端,在處理QUERY_TRANSACTION時,由於讀出的CursorWindow實例內存地址爲空拋出異常引起android.process.media退出。
而仔細檢查相關程序代碼,並未發現再出現內存不足時在Log中應出現的那些信息,故排除掉內存不足情形。而且Log中也沒有Leaked Cursor信息。
最終原因剖析:
當MediaProvider收到外部出現的query請求時,此外部程序所在進程退出,導致所傳進來的CursorWindow所擁有的IMememory binder被清空,所以當MediaProvider處理QUERY_TRANSACTION時發現收到的IMemory對象指向的內存地址爲NULL,最終拋出異常致android.media.process退出。
Binder調用前代碼如下:
ContentProviderNative.java
final class ContentProviderProxy implements IContentProvider
{
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
CursorWindow window = new CursorWindow(false /* window will be used remotely */); //生成空的CursorWindow實例
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
IBulkCursor bulkCursor = bulkQueryInternal(
url, projection, selection, selectionArgs, sortOrder,
adaptor.getObserver(), window,
adaptor);
if (bulkCursor == null) {
window.close();
adaptor.close();
return null;
}
return adaptor;
}
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();
data.writeInterfaceToken(IContentProvider.descriptor);
...
data.writeString(sortOrder);
data.writeStrongBinder(observer.asBinder());
window.writeToParcel(data, 0); //把CursorWindow對象寫入到Parcel
...
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); //調用到server端
DatabaseUtils.readExceptionFromParcel(reply);
...
在query方法中,調用newCursorWindow(false) -> initBuffer(false) 生成空的CursorWindow。window.writeToParcel將把IMemory所在的Binder對象寫入Parcel.
CursorWindow.java
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(native_getBinder()); //把IMemory所在的Binder對象寫入Parcel
dest.writeInt(mStartPos);
}
bool CursorWindow::initBuffer(bool localOnly)
{
sp<MemoryHeapBase> heap;
heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
if (heap != NULL) {
mMemory = new MemoryBase(heap, 0, mMaxSize);
if (mMemory != NULL) {
mData = (uint8_t *) mMemory->pointer();
if (mData) {
mHeader = (window_header_t *) mData;
mSize = mMaxSize;
// Put the window into a clean state
clear();
LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData);
return true;
}
}
LOGE("CursorWindow heap allocation failed"); //如果mData爲空,由於此Log未出現,因此其必不爲空
return false;
} else { //如果分配堆內存失敗
LOGE("failed to create the CursorWindow heap");
return false;
}
}
由於Log中上述Log均未出現,因此內存分配成功。這裏調用到Server端:
abstract public class ContentProviderNative extends Binder implements IContentProvider {
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
...
switch (code) {
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) {
projection = new String[num];
for (int i = 0; i < num; i++) {
projection[i] = data.readString();
}
}
// String selection, String[] selectionArgs...
String selection = data.readString();
num = data.readInt();
String[] selectionArgs = null;
if (num > 0) {
selectionArgs = new String[num];
for (int i = 0; i < num; i++) {
selectionArgs[i] = data.readString();
}
}
String sortOrder = data.readString();
IContentObserver observer = IContentObserver.Stub.
asInterface(data.readStrongBinder());
CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); //這裏調用產生Exception,即從此Parcel中讀取CursorWindow對象時,由於該對象的內存指針爲空引起異常
以下爲從Parcel中重建CursorWindow的代碼:
CursorWindow.java
public class CursorWindow extends SQLiteClosable implements Parcelable {
private CursorWindow(Parcel source) { //從Parcel中重建
IBinder nativeBinder = source.readStrongBinder();
mStartPos = source.readInt();
native_init(nativeBinder); //Exception here ----
}
// Creates a new empty window.
public CursorWindow(boolean localWindow) {
mStartPos = 0;
native_init(localWindow);
}
public static final Parcelable.Creator<CursorWindow> CREATOR
= new Parcelable.Creator<CursorWindow>() { //從Parcel中重建CursorWindow實例
public CursorWindow createFromParcel(Parcel source) {
return new CursorWindow(source);
}
public CursorWindow[] newArray(int size) {
return new CursorWindow[size];
}
};
android_database_CursorWindow.cpp
static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
{
sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj)); //將Java對象轉化爲IMemory實例
if (memory == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
return;
}
CursorWindow * window = new CursorWindow();
if (!window) {
jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
return;
}
if (!window->setMemory(memory)) { //異常拋出點
jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
delete window;
return;
}
LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
SET_WINDOW(env, object, window);
}
CursorWindow.cpp
bool CursorWindow::setMemory(const sp<IMemory>& memory)
{
mMemory = memory;
mData = (uint8_t *) memory->pointer();
if (mData == NULL) { //顯然此處爲NULL導致Exception
return false;
}
mHeader = (window_header_t *) mData;
// Make the window read-only
ssize_t size = memory->size();
mSize = size;
mMaxSize = size;
mFreeOffset = size;
LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData);
return true;
}