Log:
09-13 11:46:42.093 14778 17309 I dalvikvm: Ljava/lang/RuntimeException;: No memory in memObj09-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 NATIVE09-13 11:46:42.093 14778 17309 I dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b2809-13 11:46:42.093 14778 17309 I dalvikvm: | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=335623209-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 aborting09-13 11:46:42.093 14778 17309 I dalvikvm: "Binder Thread #3" prio=5 tid=10 NATIVE09-13 11:46:42.093 14778 17309 I dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b2809-13 11:46:42.093 14778 17309 I dalvikvm: | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=335623209-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;}
由此可見,當Binder裝置喚醒Server端處理Query請求時,發送的CursorWindow執行個體所擁有的IMemory對象已經無效,極可能由於調用者進程crash導致該對象被清除。