1. 程式人生 > >Android應用程式元件Content Provider在應用程式之間共享資料的原理分析

Android應用程式元件Content Provider在應用程式之間共享資料的原理分析

               

        在Android系統中,不同的應用程式是不能直接讀寫對方的資料檔案的,如果它們想共享資料的話,只能通過Content Provider元件來實現。那麼,Content Provider元件又是如何突破應用程式邊界許可權控制來實現在不同的應用程式之間共享資料的呢?在前面的文章中,我們已經簡要介紹過它是通過Binder程序間通訊機制以及匿名共享記憶體機制來實現的,在本文中,我們將詳細分析它的資料共享原理。

《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!

        Android應用程式之間不能直接訪問對方的資料檔案的障礙在於每一個應用程式都有自己的使用者ID,而每一個應用程式所建立的檔案的讀寫許可權都是隻賦予給自己所屬的使用者,因此,就限制了應用程式之間相互讀寫資料的操作,關於Android應用程式的許可權問題,具體可以參考前面一篇文章

Android應用程式元件Content Provider簡要介紹和學習計劃。通過前面Android程序間通訊(IPC)機制Binder簡要介紹和學習計劃等一系列文章的學習,我們知道,Binder程序間通訊機制可以突破了以應用程式為邊界的許可權控制來實現在不同應用程式之間傳輸資料,而Content Provider元件在不同應用程式之間共享資料正是基於Binder程序間通訊機制來實現的。雖然Binder程序間通訊機制突破了以應用程式為邊界的許可權控制,但是它是安全可控的,因為資料的訪問介面是由資料的所有者來提供的,換句話來說,就是資料提供方可以在介面層來實現安全控制,決定哪些資料是可以讀,哪些資料可以寫。雖然Content Provider元件本身也提供了讀寫許可權控制,但是它的控制粒度是比較粗的,如果有需要,我們還是可以在介面訪問層做更細粒度的許可權控制以達到資料安全的目的。

        Binder程序間通訊機制雖然打通了應用程式之間共享資料的通道,但是還有一個問題需要解決,那就是資料要以什麼來作來媒介來傳輸。我們知道,應用程式採用Binder程序間通訊機制進行通訊時,要傳輸的資料都是採用函式引數的形式進行的,對於一般的程序間調來來說,這是沒有問題的,然而,對於應用程式之間的共享資料來說,它們的資料量可能是非常大的,如果還是簡單的用函式引數的形式來傳遞,效率就會比較低下。通過前面Android系統匿名共享記憶體Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃等一系列文章的學習,我們知道,在應用程式程序之間以匿名共享記憶體的方式來傳輸資料效率是非常高的,因為它們之間只需要傳遞一個檔案描述符就可以了。因此,Content Provider元件在不同應用程式之間傳輸資料正是基於匿名共享記憶體機制來實現的。

        在繼續分析Content Provider元件在不同應用程式之間共享資料的原理之前,我們假設應用程式之間需要共享的資料是儲存在資料庫(SQLite)中的,因此,接下來的分析都是基於SQLite資料庫遊標(SQLiteCursor)來進行的。SQLiteCursor在共享資料的傳輸過程中發揮著重要的作用,因此,我們先來它和其它相關的類的關係圖,如下所示:


        首先在第三方應用程式這一側,當它需要訪問Content Provider中的資料時,它會在本程序中建立一個CursorWindow物件,它在內部建立了一塊匿名共享記憶體,同時,它實現了Parcel介面,因此它可以在程序間傳輸。接下來第三方應用程式把這個CursorWindow物件(連同它內部的匿名共享記憶體檔案描述符)通過Binder程序間呼叫傳輸到Content Provider這一側。這個匿名共享記憶體檔案描述符傳輸到Binder驅動程式的時候,Binder驅動程式就會在目標程序(即Content Provider所在的程序)中建立另一個匿名共享檔案描述符,指向前面已經建立好的匿名共享記憶體,因此,就實現了在兩個程序中共享同一塊匿名記憶體,這個過程具體可以參考Android系統匿名共享記憶體Ashmem(Anonymous Shared Memory)在程序間共享的原理分析一文。

        在Content Provider這一側,利用在Binder驅動程式為它建立好的這個匿名共享記憶體檔案描述符,在本程序中建立了一個CursorWindow物件。現在,Content Provider開始要從本地中從資料庫中查詢第三方應用程式想要獲取的資料了。Content Provider首先會建立一個SQLiteCursor物件,即SQLite資料庫遊標物件,它繼承了AbstractWindowedCursor類,後者又繼承了AbstractCursor類,而AbstractCursor類又實現了CrossProcessCursor和Cursor介面。其中,最重要的是在AbstractWindowedCursor類中,有一個成員變數mWindow,它的型別為CursorWindow,這個成員變數是通過AbstractWindowedCursor的子類SQLiteCursor的setWindow成員函式來設定的。這個SQLiteCursor物件設定好了父類AbstractWindowedCursor類的mWindow成員變數之後,它就具有傳輸資料的能力了,因為這個mWindow物件內部包含一塊匿名共享記憶體。此外,這個SQLiteCursor物件的內部有兩個成員變數,一個是SQLite資料庫物件mDatabase,另外一個是SQLite資料庫查詢物件mQuery。SQLite資料庫查詢物件mQuery的型別為SQLiteQuery,它繼承了SQLiteProgram類,後者又繼承了SQLiteClosable類。SQLiteProgram類代表一個數據庫存查詢計劃,它的成員變數mCompiledSql包含了一個已經編譯好的SQL查詢語句,SQLiteCursor物件就是利用這個編譯好的SQL查詢語句來獲得資料的,但是它並不是馬上就去獲取資料的,而是等到需要時才去獲取。

        那麼,要等到什麼時候才會需要獲取資料呢?一般來說,如果第三方應用程式在請求Content Provider返回資料時,如果指定了要返回關於這些資料的元資訊時,例如資料條目的數量,那麼Content Provider在把這個SQLiteCursor物件返回給第三方應用程式之前,就會去獲取資料,因為只有獲取了資料之後,才知道資料條目的數量是多少。SQLiteCursor物件通過呼叫成員變數mQuery的fillWindow成員函式來把從SQLite資料庫中查詢得到的資料儲存其父類AbstractWindowedCursor的成員變數mWindow中去,即儲存到第三方應用程式建立的這塊匿名共享記憶體中去。如果第三方應用程式在請求Content Provider返回資料時,沒有指定要返回關於這些資料的元資訊,那麼,就要等到第三方應用程式首次呼叫這個從Content Provider處返回的SQLiteCursor物件的資料獲取方法時,才會真正執行從資料庫存中查詢資料的操作,例如呼叫了SQLiteCursor物件的getCount或者moveToFirst成員函式時。這是一種資料懶載入機制,需要的時候才去載入,這樣就提高了資料傳輸過程中的效率。

        上面說到,Content Provider向第三方應用程式返回的資料實際上是一個SQLiteCursor物件,那麼,這個SQLiteCursor物件是如何傳輸到第三方應用程式的呢?因為它本身並不是一個Binder物件,我們需要對它進行適配一下。首先,Content Provider會根據這個SQLiteCursor物件來建立一個CursorToBulkCursorAdaptor介面卡物件,這個介面卡物件是一個Binder物件,因此,它可以在程序間傳輸,同時,它實現了IBulkCursor介面。Content Provider接著就通過Binder程序間通訊機制把這個CursorToBulkCursorAdaptor物件返回給第三方應用程式,第三方應用程式得到了這個CursorToBulkCursorAdaptor之後,再在本地建立一個BulkCursorToCursorAdaptor物件,這個BulkCursorToCursorAdaptor物件的繼承結構和SQLiteCursor物件是一樣的,不過,它沒有設定父類AbstractWindowedCursor的mWindow成員變數,因此,它只可以通過它內部的CursorToBulkCursorAdaptor物件引用來訪問匿名共享記憶體中的資料,即通過訪問Content Provider這一側的SQLiteCursor物件來訪問共享資料。

        上面描述的資料共享模型還是比較複雜的,一下子理解不了也不要緊,下面我們還會結合第三方應用程式和Content Provider傳輸共享資料的完整過程來進一步分析Content Provider的資料共享原理,到時候再回過頭來看這個資料共享模型就會清晰很多了。在接下來的內容中,我們就繼續Android應用程式元件Content Provider應用例項一文的例子來分析Content Provider在不同應用程式之間共享資料的原理。在這篇文章介紹的應用程式Article中,它的主視窗MainActivity是通過呼叫它的內部ArticlesAdapter物件的getArticleByPos成員函式來把ArticlesProvider中的文章資訊條目一條一條地取回來顯示在ListView中的,在這篇文章中,我們就從ArticlesAdapter類的getArticleByPos函式開始,一步一步地分析第三方應用程式Article從ArticlesProvider這個Content Provider中獲取資料的過程。同樣,我們先來看看這個過程的序列圖,然後再詳細分析每一個步驟:


      Step 1. ArticlesAdapter.getArticleByPos

      這個函式定義在前面一篇文章Android應用程式元件Content Provider應用例項介紹的應用程式Artilce原始碼工程目錄下,在檔案為packages/experimental/Article/src/shy/luo/article/ArticlesAdapter.java中:

public class ArticlesAdapter { ...... private ContentResolver resolver = null; public ArticlesAdapter(Context context) {  resolver = context.getContentResolver(); } ...... public Article getArticleByPos(int pos) {  Uri uri = ContentUris.withAppendedId(Articles.CONTENT_POS_URI, pos);  String[] projection = new String[] {   Articles.ID,   Articles.TITLE,   Articles.ABSTRACT,   Articles.URL  };  Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);  if (!cursor.moveToFirst()) {   return null;  }  int id = cursor.getInt(0);  String title = cursor.getString(1);  String abs = cursor.getString(2);  String url = cursor.getString(3);  return new Article(id, title, abs, url); }}
      這個函式通過應用程式上下文的ContentResolver介面resolver的query函式來獲得與Articles.CONTENT_POS_URI這個URI對應的文章資訊條目。常量Articles.CONTENT_POS_URI是在應用程式ArticlesProvider中定義的,它的值為“content://shy.luo.providers.articles/pos”,通過呼叫ContentUris.withAppendedId函式來在後面添加了一個整數,表示要獲取指定位置的文章資訊條目。這個位置是指ArticlesProvider這個Content Provider中的所有文章資訊條目按照Articles.DEFAULT_SORT_ORDER來排序後得到的位置的,常量Articles.DEFAULT_SORT_ORDER也是在應用程式ArticlesProvider中定義的,它的值為“_id asc”,即按照文章資訊的ID值從小到大來排列。

      Step 2. ContentResolver.query

      這個函式定義在frameworks/base/core/java/android/content/ContentResolver.java檔案中:

public abstract class ContentResolver { ...... public final Cursor query(Uri uri, String[] projection,   String selection, String[] selectionArgs, String sortOrder) {  IContentProvider provider = acquireProvider(uri);  if (provider == null) {   return null;  }  try {   ......   Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);   ......      return new CursorWrapperInner(qCursor, provider);  } catch (RemoteException e) {   ......  } catch(RuntimeException e) {   ......  } } ......}
      這個函式首先通過呼叫acquireProvider函式來獲得與引數uri對應的Content Provider介面,然後再通過這個介面的query函式來獲得相應的資料。我們先來看看acquireProvider函式的實現,再回過頭來分析這個Content Provider介面的query函式的實現。

      Step 3. ContentResolver.acquireProvider

      Step 4. ApplicationContentResolver.acquireProvider

      Step 5. ActivityThread.acquireProvider

      Step 6. ActivityThread.getProvider

      從Step 3到Step 6是獲取與上面Step 2中傳進來的引數uri對應的Content Provider介面的過程。在前面一篇文章Android應用程式元件Content Provider的啟動過程原始碼分析中,我們已經詳細介紹過這個過程了,這裡不再詳述。不過這裡我們假設,這個Content Provider介面之前已經建立好了,因此,在Step 6的ActivityThread.getProvider函式中,通過getExistingProvider函式就把之前已經好的Content Provider介面返回來了。

      回到Step 2中的ContentResolver.query函式中,它繼續呼叫這個返回來的Content Provider介面來獲取資料。從這篇文章Android應用程式元件Content Provider的啟動過程原始碼分析中,我們知道,這個Content Provider介面實際上是一個在ContentProvider類的內部所建立的一個Transport物件的遠端介面。這個Transport類繼承了ContentProviderNative類,是一個Binder物件的Stub類,因此,接下來就會進入到這個Binder物件的Proxy類ContentProviderProxy中執行query函式。

      Step 7. ContentProviderProxy.query

      這個函式定義在frameworks/base/core/java/android/content/ContentProviderNative.java檔案中:

final class ContentProviderProxy implements IContentProvider { ...... 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 = bulkQueryInternal(   url, projection, selection, selectionArgs, sortOrder,   adaptor.getObserver(), window,   adaptor);  if (bulkCursor == null) {   return null;  }  return adaptor; } ......}

        這個函式首先會建立一個CursorWindow物件,前面已經說過,這個CursorWindow物件包含了一塊匿名共享記憶體,它的作用是把這塊匿名共享記憶體通過Binder程序間通訊機制傳給Content Proivder,好讓Content Proivder在裡面返回所請求的資料。下面我們就先看看這個CursorWindow物件的建立過程,重點關注它是如何在內部建立匿名共享記憶體的,然後再回過頭來看看它呼叫bulkQueryInternal函式來做了些什麼事情。

        CursorWindow類定義在frameworks/base/core/java/android/database/CursorWindow.java檔案中,我們來看看它的建構函式的實現:

public class CursorWindow extends SQLiteClosable implements Parcelable { ...... private int nWindow; ...... public CursorWindow(boolean localWindow) {  ......  native_init(localWindow); } ......}

        它主要呼叫本地方法native_init來執行初始化的工作,主要就是建立匿名共享記憶體了,傳進來的引數localWindow為false,表示這個匿名共享記憶體只能通過遠端呼叫來訪問,即前面我們所說的,通過Content Proivder返回來的Cursor介面來訪問這塊匿名共享記憶體裡面的資料。

        Step 8. CursorWindow.native_init

        這是一個JNI方法,它對應定義在frameworks/base/core/jni/android_database_CursorWindow.cpp檔案中的native_init_empty函式:

static JNINativeMethod sMethods[] ={     /* name, signature, funcPtr */    {"native_init", "(Z)V", (void *)native_init_empty},    ......};
         函式native_init_empty的定義如下所示:
static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly){ ...... CursorWindow * window; window = new CursorWindow(MAX_WINDOW_SIZE); ...... if (!window->initBuffer(localOnly)) {  ...... } ...... SET_WINDOW(env, object, window);}
         這個函式在C++層建立了一個CursorWindow物件,然後通過呼叫SET_WINDOW巨集來把這個C++層的CursorWindow物件與Java層的CursorWindow物件關係起來:
#define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))
         這裡的gWindowField即定義為Java層的CursorWindow物件中的nWindow成員變數:
static jfieldID gWindowField;......int register_android_database_CursorWindow(JNIEnv * env){ jclass clazz; clazz = env->FindClass("android/database/CursorWindow"); ...... gWindowField = env->GetFieldID(clazz, "nWindow", "I"); ......}
        這種用法在Android應用程式框架層中非常普遍。

        下面我們重點關注C++層的CursorWindow物件的initBuffer函式的實現。

        Step 9. CursorWindow.initBuffer

        C++層的CursorWindow類定義在frameworks/base/core/jni/CursorWindow.cpp檔案中:

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;    ......   }  }  ...... } else {  ...... }}
        這裡我們就可以很清楚地看到,在CursorWindow類的內部有一個成員變數mMemory,它的型別是MemoryBase。MemoryBase類為我們封裝了匿名共享記憶體的訪問以及在程序間的傳輸等問題,具體可以參考前面一篇文章Android系統匿名共享記憶體(Anonymous Shared Memory)C++呼叫介面分析,這裡就不再詳述了。

        通過Step 8和Step 9兩步,用來在第三方應用程式和Content Provider之間傳輸資料的媒介就準備好了,我們回到Step 7中,看看系統是如何把這個匿名共享存傳遞給Content Provider使用的。在Step 7中,最後呼叫bulkQueryInternal函式來進一步操作。

        Step 10. ContentProviderProxy.bulkQueryInternal

      這個函式定義在frameworks/base/core/java/android/content/ContentProviderNative.java檔案中:

final class ContentProviderProxy implements IContentProvider{ ...... 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);  url.writeToParcel(data, 0);  int length = 0;  if (projection != null) {   length = projection.length;  }  data.writeInt(length);  for (int i = 0; i < length; i++) {   data.writeString(projection[i]);  }  data.writeString(selection);  if (selectionArgs != null) {   length = selectionArgs.length;  } else {   length = 0;  }  data.writeInt(length);  for (int i = 0; i < length; i++) {   data.writeString(selectionArgs[i]);  }  data.writeString(sortOrder);  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);  IBulkCursor bulkCursor = null;  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; } ......}
      這個函式有點長,不過它的邏輯很簡單,就是把查詢引數都寫到一個Parcel物件data中去,然後通過下面Binder程序間通訊機制把查詢請求傳給Content Provider處理:
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
      從這個Binder呼叫返回以後,就會得到一個IBulkCursor介面,它是一個Binder引用,實際是指向在Content Provider這一側建立的一個CursorToBulkCursorAdaptor物件,後面我們將會看到。有了這個IBulkCursor介面之後,我們就可以通過Binder程序間呼叫來訪問從Content Provider中查詢得到的資料了。這個IBulkCursor介面最終最設定了上面Step 7中建立的BulkCursorToCursorAdaptor物件adaptor中去:
adaptor.set(bulkCursor, rowCount, idColumnPosition);
      BulkCursorToCursorAdaptor類實現了Cursor介面,因此,我們可以通過Curosr介面來訪問這些查詢得到的共享資料。

      在前面把查詢引數寫到Parcel物件data中去的過程中,有兩個步驟是比較重要的,分別下面這段執行語句:

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);
      呼叫window.writeToParcel是把window物件內部的匿名共享記憶體塊通過Binder程序間通訊機制傳輸給Content Provider來使用;而當傳進來的引數adaptor不為null時,就會往data中寫入一個整數1,表示讓Content Provider把查詢得到資料的元資訊一起返回來,例如資料的行數、資料行的ID列的索引位置等資訊,這個整數值會促使Content Provider把前面說的IBulkCursor介面返回給第三方應用程式之前,真正執行一把資料庫查詢操作,後面我們將看到這個過程。

      現在,我們重點來關注一下CursorWindow類的writeToParcel函式,看看它是如何把它內部的匿名共享記憶體物件寫到資料流data中去的。

      Step 11. CursorWindow.writeToParcel

      這個函式定義在frameworks/base/core/java/android/database/CursorWindow.java檔案中:

public class CursorWindow extends SQLiteClosable implements Parcelable { ...... public void writeToParcel(Parcel dest, int flags) {  ......  dest.writeStrongBinder(native_getBinder());  ...... } ......}
      這個函式最主要的操作就是往資料流dest寫入一個Binder物件,這個Binder物件是通過呼叫本地方法native_getBinder來得到的。

      Step 12. CursorWindow.native_getBinder

      這個函式定義在frameworks/base/core/jni/android_database_CursorWindow.cpp檔案中:

static jobject native_getBinder(JNIEnv * env, jobject object){ CursorWindow * window = GET_WINDOW(env, object); if (window) {  sp<IMemory> memory = window->getMemory();  if (memory != NULL) {   sp<IBinder> binder = memory->asBinder();   return javaObjectForIBinder(env, binder);  } } return NULL;}
      在前面的Step 8中,我們在C++層建立了一個CursorWindow物件,這個物件儲存在Java層建立的CursorWindow物件的成員變數nWindow中,這裡通過GET_WINDOW巨集來把這個在C++層建立的CurosrWindow物件返回來:
#define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))
      獲得了這個CursorWindow物件以後,就呼叫它的getMemory函式來獲得一個IMemory介面,這是一個Binder介面,具體可以參考前面一篇文章Android系統匿名共享記憶體(Anonymous Shared Memory)C++呼叫介面分析

      Step 13. CursorWindow.getMemory

      這個函式定義在frameworks/base/core/jni/CursorWindow.h檔案中:

class CursorWindow{public: ...... sp<IMemory>         getMemory() {return mMemory;} ......}
      這個CursorWindow物件的成員變數mMemory就是在前面Step 9中建立的了。

      這樣,在第三方應用程式這一側建立的匿名共享存物件就可以傳遞給Content Provider來使用了。回到前面的Step 10中,所有的引數都就準備就緒以後,就通過Binder程序間通訊機制把資料查詢請求傳送給相應的Content Proivder了。這個請求是在ContentProviderNative類的onTransact函式中響應的。

      Step 14. ContentProviderNative.onTransact

      這個函式定義在frameworks/base/core/java/android/content/ContentProviderNative.java檔案中:

abstract public class ContentProviderNative extends Binder implements IContentProvider { ...... @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {  try {   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);     // 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;    }   ......   }  } catch (Exception e) {   DatabaseUtils.writeExceptionToParcel(reply, e);   return true;  }  return super.onTransact(code, data, reply, flags); } ......}
      這一步其實就是前面Step 10的逆操作,把請求引數從資料流data中讀取出來。這裡我們同樣是重點關注下面這兩個引數讀取的步驟:
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;
      通過呼叫CursorWindow.CREATOR.createFromParcel函式來從資料流data中重建一個本地的CursorWindow物件;接著又將資料流data的下一個整數值讀取出來,如果這個整數值不為0,變數wantsCursorMetadata的值就為true,說明Content Provider在返回IBulkCursor介面給第三方應用程式之前,要先實際執行一把資料庫查詢操作,以便把結果資料的元資訊返回給第三方應用程式。

      通過下面的程式碼我們可以看到,呼叫bulkQuery函式之後,就得到了一個IBulkCursor介面,這表示要返回的資料準備就緒了,但是這時候實際上還沒有把結果資料從資料庫中提取出來,而只是準備好了一個SQL查詢計劃,等到真正要使用這些結果資料時,系統才會真正執行查詢資料庫的操作:

if (wantsCursorMetadata) {    reply.writeInt(bulkCursor.count());    ......}
      在將這個IBulkCursor介面返回給第三方應用程式之前,如果發現wantsCursorMetadata的值就為true,就會呼叫它的count函式來獲得結果資料的總行數,這樣就會導致系統真正去執行資料庫查詢操作,並把結果資料儲存到前面得到的CursorWindow物件中的匿名共享記憶體中去。

      下面我們就重點關注CursorWindow.CREATOR.createFromParcel函式是如何從資料流data中在本地構造一個CursorWindow物件的。

      Step 15. CursorWindow.CREATOR.createFromParcel

      這個函式定義在frameworks/base/core/java/android/database/CursorWindow.java檔案中:

public class CursorWindow extends SQLiteClosable implements Parcelable { ...... private CursorWindow(Parcel source) {  IBinder nativeBinder = source.readStrongBinder();  ......  native_init(nativeBinder); } ...... public static final Parcelable.Creator<CursorWindow> CREATOR   = new Parcelable.Creator<CursorWindow>() {  public CursorWindow createFromParcel(Parcel source) {   return new CursorWindow(source);  }  ...... }; ......}
      在建立CursorWindow物件的過程中,首先是從資料流source中將在前面Step 10中寫入的Binder介面讀取出來,然後使用這個Binder介面來初始化這個CursorWindow物件,通過前面的Step 13,我們知道,這個Binder介面的實際型別為IMemory,它封裝了對匿名共享記憶體的訪問操作。初始化這個匿名共享記憶體物件的操作是由本地方法native_init函式來實現的,下面我們就看看它的實現。

      Step 16. CursorWindow.native_init

      這個函式定義在frameworks/base/core/jni/android_database_CursorWindow.cpp檔案中,對應的函式為native_init_memory函式:

static JNINativeMethod sMethods[] ={    ......    {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},};
      函式native_init_memory的實現如下所示:
static void native_init_memory(JNIEnv * env, jobject object, jobject memObj){    sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));    ......    CursorWindow * window = new CursorWindow();    ......    if (!window->setMemory(memory)) {       ......    }    ......    SET_WINDOW(env, object, window);}
      函式首先是將前面Step 15中傳進來的Binder介面轉換為IMemory介面,接著建立一個C++層的CursorWindow物件,再接著用這個IMemory介面來初始化這個C++層的CursorWindow物件,最後像前面的Step 8一樣,通過巨集SET_WINDOW把這個C++層的CursorWindow物件和前面在Step 15中建立的Java層CursorWindow物件關聯起來。

      下面我們就重點關注CursorWindow類的setMemory函式的實現,看看它是如何使用這個IMemory介面來初始化其內部的匿名共享記憶體物件的。

      Step 17. CursorWindow.setMemory

      這個函式定義在frameworks/base/core/jni/CursorWindow.cpp檔案中:

bool CursorWindow::setMemory(const sp<IMemory>& memory){ mMemory = memory; mData = (uint8_t *) memory->pointer(); ...... mHeader = (window_header_t *) mData; // Make the window read-only ssize_t size = memory->size(); mSize = size; mMaxSize = size; mFreeOffset = size; ...... return true;}
      從前面一篇文章Android系統匿名共享記憶體(Anonymous Shared Memory)C++呼叫介面分析中,我們知道,這裡得到的IMemory介面,實際上是一個Binder引用,它指向前面在Step 9中建立的MemoryBase物件,當我們第一次呼叫這個介面的pointer函式時,它便會通過Binder程序間通訊機制去請求這個MemoryBase物件把它內部的匿名共享記憶體檔案描述符返回來給它,而Binder驅動程式發現要傳輸的是一個檔案描述符的時候,就會在目標程序中建立另外一個檔案描述符,這個新建的檔案描述符與要傳輸的檔案描述符指向的是同一個檔案,在我們這個情景中,這個檔案就是我們前面建立的匿名共享記憶體檔案了。因此,在目標程序中,即在Content Provider程序中,它可以通過這個新建的檔案描述符來訪問這塊匿名共享記憶體,這也是匿名共享記憶體在程序間的共享原理,具體可以參考另外一篇文章Android系統匿名共享記憶體Ashmem(Anonymous Shared Memory)在程序間共享的原理分析

      這樣,在Content Provider這一側,就可以把第三方應用程式請求的資料儲存在這個匿名共享記憶體中了,回到前面的Step 14中,下一步要執行的函式便是bulkQuery了,它的作用為請求的資料制定好一個SQL資料庫查詢計劃。這個bulkQuery函式是由一個實現了IContentProvider介面的Binder物件來實現的,具體可以參考前面一篇文章

      Step 18. Transport.bulkQuery

      這個函式定義在frameworks/base/core/java/android/content/ContentProvider.java檔案中:

public abstract class ContentProvider implements ComponentCallbacks { ...... class Transport extends ContentProviderNative {  ......  public IBulkCursor bulkQuery(Uri uri, String[] projection,    String selection, String[] selectionArgs, String sortOrder,    IContentObserver observer, CursorWindow window) {   ......   Cursor cursor = ContentProvider.this.query(uri, projection,    selection, selectionArgs, sortOrder);   ......   return new CursorToBulkCursorAdaptor(cursor, observer,    ContentProvider.this.getClass().getName(),    hasWritePermission(uri), window);  }  ...... } ......}

        這個函式主要做了兩件事情,一是呼叫ContentProvider的子類的query函式構造一個數據庫查詢計劃,注意,從這個函式返回來的時候,還沒有真正執行資料庫查詢的操作,而只是按照查詢條件準備好了一個SQL語句,要等到第一次使用的時候才會去執行資料庫查詢操作;二是使用前面一步得到的Cursor介面以及傳下來的引數window來建立一個CursorToBulkCursorAdaptor物件,這個物件實現了IBulkCursor介面,同時它也是一個Binder物件,是用來返回給第三方應用程式使用的,第三方應用程式必須通過這個介面來獲取從ContentProvider中查詢得到的資料,而這個CursorToBulkCursorAdaptor物件的功能就是利用前面獲得的Cursor介面來執行資料庫查詢操作,然後把查詢得到的結果儲存在從引數傳下來的window物件內部所引用的匿名共享記憶體中去。我們先來看ContentProvider的子類的query函式的實現,在我們這個情景中,這個子類就是ArticlesProvider了,然後再回過頭來看看這個CursorToBulkCursorAdaptor物件是如何把資料庫查詢計劃與匿名共享記憶體關聯起來的。

        Step 19. ArticlesProvider.query

        這個函式定義在前面一篇文章Android應用程式元件Content Provider應用例項介紹的應用程式ArtilcesProvider原始碼工程目錄下,在檔案packages/experimental/ArticlesProvider/src/shy/luo/providers/articles/ArticlesProvider.java 中:

public class ArticlesProvider extends ContentProvider { ...... @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {  SQLiteDatabase db = dbHelper.getReadableDatabase();  SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();  String limit = null;  switch (uriMatcher.match(uri)) {   ......   case Articles.ITEM_POS: {    String pos = uri.getPathSegments().get(1);    sqlBuilder.setTables(DB_TABLE);    sqlBuilder.setProjectionMap(articleProjectionMap);    limit = pos + ", 1";    break;   }   ......  }  Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Articles.DEFAULT_SORT_ORDER : sortOrder, limit);  ......  return cursor; } ......}
      從前面的Step 1中可以看到,傳進來的引數uri的值為“content://shy.luo.providers.articles/pos”,通過uriMatcher的match函式來匹配這個uri的時候,得到的匹配碼為Articles.ITEM_POS,這個知識點可以參考前面這篇文章Android應用程式元件Content Provider應用例項。因為我們的資料是儲存在SQLite資料庫裡面的,因此,必須要構造一個SQL語句來將所請求的資料查詢出來。這裡是通過SQLiteQueryBuilder類來構造這個SQL查詢語句的,構造好了以後,就呼叫它的query函式來準備一個數據庫查詢計劃。

      Step 20. SQLiteQueryBuilder.query

      這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java檔案中:

public class SQLiteQueryBuilder{ ...... public Cursor query(SQLiteDatabase db, String[] projectionIn,   String selection, String[] selectionArgs, String groupBy,   String having, String sortOrder, String limit) {  ......  String sql = buildQuery(   projectionIn, selection, groupBy, having,   sortOrder, limit);  ......  return db.rawQueryWithFactory(   mFactory, sql, selectionArgs,   SQLiteDatabase.findEditTable(mTables)); } ......}
      這裡首先是呼叫buildQuery函式來構造一個SQL語句,它無非就是根據從引數傳來列名子句、select子句、where子句、group by子句、having子句、order子句以及limit子句來構造一個完整的SQL子句,這些都是SQL語法的基礎知識了,這裡我們就不關注了。構造好這個SQL查詢語句之後,就呼叫從引數傳下來的資料庫物件db的rawQueryWithFactory函式來進一步操作了。

       Step 21. SQLiteDatabase.rawQueryWithFactory

       這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java檔案中:

public class SQLiteDatabase extends SQLiteClosable { ...... public Cursor rawQueryWithFactory(   CursorFactory cursorFactory, String sql, String[] selectionArgs,   String editTable) {  ......  SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);  Cursor cursor = null;  try {   cursor = driver.query(    cursorFactory != null ? cursorFactory : mFactory,    selectionArgs);  } finally {   ......  }  return cursor; } ......}
      這個函式會在內部建立一個SQLiteCursorDriver物件driver,然後呼叫它的query函式來建立一個Cursor物件,這個Cursor物件的實際型別是SQLiteCursor,下面我們將會看到,前面我們也已經看到,這個SQLiteCursor的內部就包含了一個數據庫查詢計劃。

      Step 22. SQLiteCursorDriver.query

      這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java檔案中:

public class SQLiteDirectCursorDriver implements SQLiteCursorDriver { ...... public Cursor query(CursorFactory factory, String[] selectionArgs) {  // Compile the query  SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);  try {   ......   // Create the cursor   if (factory == null) {    mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);   } else {    mCursor = factory.newCursor(mDatabase, this, mEditTable, query);   }   ......   return mCursor;  } finally {   ......  } } ......}
      這裡我們就可以清楚地看到,這個函式首先會根據資料庫物件mDatabase和原生SQL語句來構造一個SQLiteQuery物件,這個物件的建立的過程中,就會解析這個原生SQL語句,並且建立好資料庫查詢計劃,這樣做的好處是等到真正查詢的時候就可以馬上從資料庫中獲得取資料了,而不用去分析和理解這個SQL字串語句,這個就是所謂的SQL語句編譯了。有了這個SQLiteQuery物件之後,再把它和資料庫物件mDatabase等待資訊一起來建立一個SQLiteCursor物件,於是,這個SQLiteCursor物件就圈定要將來要從資料庫中獲取的資料了。這一步執行完成之後,就把這個SQLiteCursor物件返回給上層,最終回到Step 18中的Transport類bulkQuery函式中。有了這個SQLiteCursor物件之後,就通過建立一個CursorToBulkCursorAdaptor物件來把它和匿名共享記憶體關聯起來,這樣,就為將來從資料庫中查詢得到的資料找到了歸宿。

      CursorToBulkCursorAdaptor類定義在frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java檔案中,我們來看看它的物件的構造過程,即它的建構函式的實現:

public final class CursorToBulkCursorAdaptor extends BulkCursorNative  implements IBinder.DeathRecipient { ...... public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName,   boolean allowWrite, CursorWindow window) {  try {   mCursor = (CrossProcessCursor) cursor;   if (mCursor instanceof AbstractWindowedCursor) {    AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;    ......    windowedCursor.setWindow(window);   } else {    ......   }  } catch (ClassCastException e) {   ......  }    ...... } ......}
      這裡傳進來的引數cursor的型別為SQLiteCursor,從上面的類圖我們可以知道,SQLiteCursor實現了CrossProcessCursor介面,並且繼承了AbstractWindowedCursor類,因此,上面第一個if語句的條件成立,於是就會把這個SQLiteCurosr物件轉換為一個AbstractWindowedCursor物件,目的是為了呼叫它的setWindow函式來把傳進來的CursorWindow物件window儲存起來,以便後面用來儲存資料。

      Step 23. AbstractWindowedCursor.setWindow

      這個函式定義在frameworks/base/core/java/android/database/AbstractWindowedCursor.java檔案中:

public abstract class AbstractWindowedCursor extends AbstractCursor{ ...... public void setWindow(CursorWindow window) {  ......  mWindow = window; } ...... protected CursorWindow mWindow;}
      這個函式很簡單,只是把引數window儲存在AbstractWindowedCursor類的成員變數mWindow中。注意,這個成員變數mWindow的訪問許可權為protected,即AbstractWindowedCursor的子類可以直接訪問這個成員變數。

      這一步完成以後,就返回到前面的Step 14中去了,執行下面語句:

if (bulkCursor != null) { reply.writeStrongBinder(bulkCursor.asBinder()); if (wantsCursorMetadata) {  reply.writeInt(bulkCursor.count());  reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(   bulkCursor.getColumnNames())); }} else { ......}
      這裡的bulkCursor不為null,於是,就會把這個bulkCursor物件寫入到資料流reply中,這個介面是要通過Binder程序間通訊機制返回到第三方應用程式的,它的實際型別就是我們在前面Step 18中建立的CursorToBulkCursorAdaptor物件了。

      從前面的Step 14的分析中,我們知道,這裡的布林變數wantsCursorMetadata為true,於是就會把請求資料的行數以及資料行的ID列索引號一起寫入到資料流reply中去了。這裡,我們重點分析IBulkCursor介面的count函式,因為這個呼叫使得這個Content Provider會真正去執行資料庫查詢的操作。至於是如何得到從資料庫查詢出來的資料行的ID列的位置呢?回憶前面這篇文章Android應用程式元件Content Provider應用例項,我們提到,如果我們想將資料庫表中的某一列作為資料行的ID列的話,那麼就必須把這個列的名稱設定為"_id",這裡的BulkCursorToCursorAdaptor類的靜態成員函式findRowIdColumnIndex函式就是根據這個列名"_id"來找到它是位於資料行的第幾列的。

      CursorToBulkCursorAdaptor類定義在frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java檔案中,它的count成員函式的實現如下所示:

public final class CursorToBulkCursorAdaptor extends BulkCursorNative  implements IBinder.DeathRecipient { ...... public int count() {  return mCursor.getCount(); } ......}
      它的成員變數mCursor即為在前面Step 22中建立的SQLiteCursor物件,於是,下面就會執行SQLiteCursor類的getCount成員函式。

      Step 24. SQLiteCursor.getCount
      這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java檔案中:

public class SQLiteCursor extends AbstractWindowedCursor { ...... @Override public int getCount() {  if (mCount == NO_COUNT) {   fillWindow(0);  }  return mCount; } ......}
      它裡面的成員變數mCount的初始化為NO_COUNT,表示還沒有去執行資料庫查詢操作,因此,還不知道它的值是多少,需要通過呼叫fillWindow函式來從資料據庫中查詢中,第三方應用程式所請求的資料一共有多少行。

      Step 25. QLiteCursor.fillWindow
      這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java檔案中:


public class SQLiteCursor extends AbstractWindowedCursor { ...... private void fillWindow (int startPos) {  ......  mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);    ...... } ......}
      注意,這裡的成員變數mWindow實際上是SQLiteCursor的父類AbstractWindowedCursor的成員變數,是在Step 23中設定的,它的訪問許可權為protected,因此,SQLiteCursor類可以直接訪問它。真正的資料庫查詢操作是由SQLiteCursor類的成員變數mQuery來執行的,它的型別是SQLiteCursor,是前面的Step 22中建立的,它知道如何去把第三方應用程式請求的資料從資料庫中提取出來。

      Step 26. SQLiteCursor.fillWindow

      這個函式定義在frameworks/base/core/java/android/database/sqlite/SQLiteQuery.java檔案中:

public class SQLiteQuery extends SQLiteProgram { ...... /* package */ int fillWindow(CursorWindow window,   int maxRead, int lastPos) {  ......  try {   ......   try {    ......    // if the start pos is not equal to 0, then most likely window is    // too small for the data set, loading by another thread    // is not safe in this situation. the native code will ignore maxRead    int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,     maxRead, lastPos);    ......    return numRows;   } catch (IllegalStateException e){    ......   } catch (SQLiteDatabaseCorruptException e) {    ......   } finally {    ......   }  } finally {   ......  } } ......} 
      這裡我們可以看到,真正的資料庫查詢操作是由本地方法native_fill_window來執行的,它最終也是呼叫了sqlite的庫函式來執行資料庫查詢的操作,這裡我們就不跟進去了,對sqlite有興趣的讀者可以自己研究一下。這個函式執行完成之後,就會把從資料庫中查詢得到的資料的行數返回來,這個行數最終返回到Step 25中的SQLiteCursor.fillWindow函式,設定在SQLiteCursor類的成員變數mCount中,於是,下次再呼叫它的getCount函式時,就可以馬上返回了。

      這一步執行完成之後,就回到前面的Step 14中,最終就把從Content Provider中查詢得到的資料通過匿名共享記憶體返回給第三方應用程式了。

      至此,Android應用程式元件Content Provider在應用程式之間共享資料的原理就分析完成了,總的來說,它就是通過Binder程序間通訊機制和匿名共享記憶體來實現的了。

      關於應用程式間的資料共享還有另外的一個重要話題,就是資料更新通知機制了。因為資料是在多個應用程式中共享的,當其中一個應用程式改變了這些共享資料的時候,它有責任通知其它應用程式,讓它們知道共享資料被修改了,這樣它們就可以作相應的處理。在下一篇文章中,我們將分析Android應用程式元件Content Provider的資料更新通知機制,敬請關注。