鍍金池/ 教程/ Android/ 第7章? 深入理解 ContentProvider
第2章? 深入理解 Java Binder 和 MessageQueue
第7章? 深入理解 ContentProvider
第5章? 深入理解 PowerManagerService
第3章? 深入理解 SystemServer
第8章? 深入理解ContentService 和 AccountManagerService
第1章?開發(fā)環(huán)境部署
第4章? 深入理解 PackageManagerService
第6章?深入理解ActivityManagerService

第7章? 深入理解 ContentProvider

本章主要內容:

·??深入分析ContentProvider的創(chuàng)建和啟動,以及SQLite相關的知識點

·??深入分析Cursor query和close函數(shù)的實現(xiàn)

·??深入分析ContentResolver openAssetFileDescriptor函數(shù)的實現(xiàn)

本章所涉及的源代碼文件名及位置:

·??ActivityManagerService.java

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

·??ContextImpl.java

frameworks/base/core/java/android/app/ContextImpl.java

·??ActivityThread.java

frameworks/base/core/java/android/app/ActivityThread.java

·??MediaStore.java

frameworks/base/core/java/android/provider/MediaStore.java

·??ContentResolver.java

frameworks/base/core/java/android/content/ContentResolver.java

·??ContentProvider.java

frameworks/base/core/java/android/content/ContentProvider.java

·??MediaProvider.java

package/providers/MediaProvider/src/java/com/android/MediaProvider/MediaProvider.java

·??SQLiteDatabase.java

frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java

·??SQLiteCompliteSql.java

frameworks/base/core/java/android/database/sqlite/SQLiteCompliteSql.java

·??android_database_SQLiteDatabase.cpp

frameworks/base/core/jni/android_database_SQLiteDatabase.cpp

·??android_database_SQLiteCompliteSql.cpp

frameworks/base/core/jni/android_database_SQLiteCompliteSql.cpp

·??sqlite3_android.cpp

external/sqlite3/android/sqlite3_android.cpp

·??SQLiteQueryBuilder.java

frameworks/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java

·??SQLiteCursorDriver.java

frameworks/base/core/java/android/database/sqlite/SQLiteCursorDriver.java

·??SQLiteQuery.java

frameworks/base/core/java/android/database/sqlite/SQLiteQuery.java

·??SQLiteCursor.java

frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java

·??SQLiteProgram.java

frameworks/base/core/java/android/database/sqlite/SQLiteProgram.java

·??CursorToBulkCursorAdaptor.java

frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java

·??BulkCursorToCursorAdaptor.java

frameworks/base/core/java/android/database/BulkCursorToCursorAdaptor.java

·??CursorWindow.java

frameworks/base/core/java/android/database/CursorWindow.java

·??android_database_CursorWindow.cpp

frameworks/base/core/jni/android_database_CursorWindow.cpp

·??CursorWindow.cpp

frameworks/base/libs/binder/CursorWindow.cpp

·??android_database_SQLiteQuery.cpp

frameworks/base/core/jni/android_database_SQLiteQuery.cpp

·??CursorWrapper.java

frameworks/base/core/java/android/database/CursorWrapper.java

·??AbstractCursor.java

frameworks/base/core/java/android/database/AbstractCursor.java

·??BulkCursorNative.java

frameworks/base/core/java/android/database/BulkCursorNative.java

·??ParcelFileDescriptor.java

frameworks/base/core/java/android/os/ParcelFileDescriptor.java

·??MediaProvider.java

packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

·??android_util_Binder.cpp

frameworks/base/core/jni/android_util_Binder.cpp

·??Parcel.cpp

frameworks/base/libs/binder/Parcel.cpp

·??binder.c

kernel/drivers/staging/android/binder.c

7.1 ?概述

本章重點分析ContentProvider、SQLite、Cursor query、close函數(shù)的實現(xiàn)及ContentResolver openAssetFileDescriptor函數(shù)。為了幫助讀者進一步理解本章的知識點,筆者特意挑選了四條分析路線。

·??第一條:以客戶端進程通過MediaStore.Images.Media類的靜態(tài)函數(shù)query來查詢MediaProvider中Image相關信息為入口點,分析系統(tǒng)如何創(chuàng)建和啟動MediaProvider。此分析路線著重關注客戶端進程、ActivityManagerService及MediaProvider所在進程間的交互。

·??第二條:沿襲第一條分析路徑,但是將關注焦點轉移到SQLiteDatabase如何創(chuàng)建數(shù)據(jù)庫的分析上。另外,本條路線還將對SQLite進行相關介紹。

·??第三條:將重點研究Cursor query和close函數(shù)的實現(xiàn)細節(jié)。

·??第四條:將分析ContentResolver openAssetFileDescriptor函數(shù)的實現(xiàn)。

閑話少說,立即開始本次分析之旅。

7.2? MediaProvider的啟動及創(chuàng)建

第一、二、三條分析路線都將以下面這段示例為參考。

[-->MediaProvider客戶端示例]

void QueryImage(Context context){

? //①得到ContentResolver對象

?ContentResolver cr = context.getContentResover();

? Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

? //②查詢數(shù)據(jù)庫

? Cursorcursor = MediaStore.Images.Media.query(cr,uri,null);

?cursor.moveToFirst();//③移動游標到頭部

? ......//從游標中取出數(shù)據(jù)集

??cursor.close();//④關閉游標

}

先介紹一下這段示例的情況:客戶端(即運行本示例的進程)查詢(query)的目標ContentProvider是MediaProvider,它運行于進程android.process.media中。假設目標進程此時還未啟動。

本節(jié)的關注點集中在:

·??MediaProvider所在進程是如何創(chuàng)建的?MediaProvider又是如何創(chuàng)建的?

·??客戶端通過什么和位于目標進程中的MediaProvider交互的?

先來看第一個關鍵函數(shù)getContentResolver。

7.2.1? Context的getContentResolver函數(shù)分析

根據(jù)第6章對Context的介紹,Context的getContentResolver最終會調用它所代理的ContextImpl對象的getContentResolver函數(shù),此處直接看ContextImpl的代碼。

[-->ContextImpl.java::getContentResolver]

public ContentResolver getContentResolver() {

?? returnmContentResolver;

}

該函數(shù)直接返回mContentResolver,此變量在ContextImpl初始化時創(chuàng)建,相關代碼如下:

[-->ContextImpl.java::init]

final void init(LoadedApk packageInfo, IBinder activityToken,

?????? ActivityThreadmainThread, Resources container,String basePackageName) {

? ?......

??mMainThread = mainThread;//mainThread指向AcitivityThread對象

? //mContentResolver的真實類型是ApplicationContentResolver

??mContentResolver = new ApplicationContentResolver(this, mainThread);

?? ......

?}

由以上代碼可知,mContentResolver的真實類型是ApplicationContentResolver,它是ContextImpl定義的內部類并繼承了ContentResolver。

getContentResolver函數(shù)比較簡單,就分析到此。下面來看第二個關鍵點。

提示為了書寫方便,將ContentProvider簡稱為CP,將ContentResolver簡稱為CR。

?

7.2.2? MediaStore.Image.Media的query函數(shù)分析

第二個關鍵點是在MediaProvider客戶端示例中所調用的MediaStore.Image.Media 的query函數(shù)。MediaStore是多媒體開發(fā)中常用的類,其內部定義了專門針對Image、Audio、Video等不同多媒體信息的內部類來幫助客戶端開發(fā)人員更好底和MediaProvider交互。這些類及相互之間的關系如圖7-1所示。

http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter7/image001.png" alt="image" />

圖7-1? MediaStore類圖

由圖7-1可知,MediaStore定義了較多的內部類,我們重點展示作為內部類之一的Image的情況,其中:

·??MediaColumns定義了所有與媒體相關的數(shù)據(jù)庫表都會用到的數(shù)據(jù)庫字段,而ImageColumns定義了單獨針對Image的數(shù)據(jù)庫字段。

·??Image類定義了一個名為Media的內部類用于查詢和Image相關的信息,同時Image類還定義了一個名為Thumbnails的內部類用于查詢和Image相關的縮略圖的信息(在Android平臺上,縮略圖的來源有兩種,一種是Image,另一種是Video,故Image定義了名為Thumbnails的內部類,而Video也定義了一個名為Thumbnails的內部類)。

提示MediaStore類較為復雜,主要原因是它定義了一些同名類。讀者閱讀代碼時務須仔細。

下面來看Image.Media的query函數(shù),其代碼非常簡單,如下所示:

[-->MediaStore.java::Image.Media.query]

public static final class Media implementsImageColumns {

?? public static final Cursor query(ContentResolvercr,Uri uri,

?????????????????????????????????????????? String[]projection) {

? ?//直接調用ContentResolver的query函數(shù)

?? returncr.query(uri, projection, null,

???????????????????? ?null,DEFAULT_SORT_ORDER);

?}

Image.Media的query函數(shù)直接調用ContentResolver的query函數(shù),雖然cr的真實類型是ApplicationContentResolver,但是此函數(shù)卻由其基類ContentResolver實現(xiàn)。

提示追求運行效率的程序員也許會對上邊這段代碼的實現(xiàn)略有微詞,因為Image.Media的query函數(shù)基本上沒做任何有意義的工作。假如客戶端直接調用cr.query函數(shù),則此處的query就增加了一次函數(shù)調用和返回的開銷(即Image.Media query調用和返回時參數(shù)的入棧/出棧)。但是,通過Image.Media的封裝將使程序更清晰和易讀(與直接使用ContentResolver的query相比,代碼閱讀者一看Image.Media就知道其query函數(shù)應該和Image有關,否則需要通過解析uri參數(shù)才能確定查詢的信息是什么)。代碼清晰易讀和運行效率高,往往是軟件開發(fā)中的熊掌和魚,它們之間的對立性,將在本章中體現(xiàn)得淋漓盡致。筆者建議讀者在實際開發(fā)中結合具體情況決定取舍,萬不可鉆牛角尖。

1.? ContentResolver的query函數(shù)分析

[-->ContentResolver.java::query]

public final Cursor query(Uri uri, String[]projection,

???????????String selection, String[] selectionArgs, String sortOrder) {

?? //調用acquireProvider函數(shù),參數(shù)為uri,函數(shù)也由ContentResolver實現(xiàn)

??IContentProvider provider = acquireProvider(uri);

? //注意:下面將和ContentProvider交互, 相關知識留待7.4節(jié)再分析

?? ......

?}

ContentResolver的query將調用acquireProvider,該函數(shù)定義在ContentResolver類中,代碼如下:

[-->ContentResolver.java::query]

public final IContentProvider acquireProvider(Uriuri) {

??if(!SCHEME_CONTENT.equals(uri.getScheme()))?return null;

? Stringauth = uri.getAuthority();

? if (auth!= null) {

???? //acquireProvider是一個抽象函數(shù),由ContentResolver的子類實現(xiàn)。在本例中,該函數(shù)

???? //將由ApplicationContentResolver實現(xiàn)。uri.getAuthority將返回代表目標

??? ?//ContentProvider的名字

????? returnacquireProvider(mContext, uri.getAuthority());

? }

? returnnull;

?}

如上所述,acquireProvider由ContentResolver的子類實現(xiàn),在本例中該函數(shù)由ApplicationContentResolver定義,代碼如下:

[-->ContextImpl.java::acquireProvider]

protected IContentProvider acquireProvider(Contextcontext, String name) {

???//mMainThread指向代表應用進程主線程的ActivityThread對象,每個應用進程只有一個

???//ActivityThread對象

?? ?return mMainThread.acquireProvider(context,name);

?}

如以上代碼所示,最終ActivityThread的acquireProvider函數(shù)將被調用,希望它不要再被層層轉包了。

2.? AcitvityThread的acquireProvider函數(shù)分析

ActivityThread的 acquireProvider函數(shù)的代碼如下:

[-->ActivityThread.java::acquireProvider]

public final IContentProvideracquireProvider(Context c, String name) {

? //①調用getProvider函數(shù),它很重要。見下文分析

??IContentProvider provider = getProvider(c,name);

? ......

? IBinderjBinder = provider.asBinder();

??synchronized(mProviderMap) {

???? //客戶端進程將本進程使用的ContentProvider信息保存到mProviderRefCountMap中,

???? //其主要功能與引用計數(shù)和資源釋放有關,讀者暫可不理會它

?????ProviderRefCount prc = mProviderRefCountMap.get(jBinder);

????? if(prc== null)

???????? ?mProviderRefCountMap.put(jBinder, newProviderRefCount(1));

?????else?? prc.count++;

?? }

?returnprovider;

}

在acquireProvider內部調用getProvider得到一個IContentProvider類型的對象,該函數(shù)非常重要,其代碼為:

[-->ActivityThread.java::getProvider]

private IContentProvider getProvider(Contextcontext, String name) {

? /*

?? 查詢該應用進程是否已經保存了用于和遠端ContentProvider通信的對象existing。

?? 此處,我們知道existing的類型是IContentProvider,不過IContentProvider是一

?? 個interface,那么existing的真實類型是什么呢?稍后再揭示

? */

?IContentProvider existing = getExistingProvider(context, name);

? if(existing != null) return existing;//如果existing存在,則直接返回

?

?IActivityManager.ContentProviderHolder holder = null;

? try {

????? //如果existing不存在,則需要向AMS查詢,返回值的類型為ContentProviderHolder

????? holder= ActivityManagerNative.getDefault().getContentProvider(

???????????????getApplicationThread(), name);

? }......

?? //注意:記住下面這個函數(shù)調用,此時是在客戶端進程中

?IContentProvider prov = installProvider(context, holder.provider,

???????????????holder.info, true);

?? ......

? returnprov;

?}

以上代碼中讓人比較頭疼的是其中新出現(xiàn)的幾種數(shù)據(jù)類型,如IContentProvider、ContentProviderHolder。先來分析AMS的getContentProvider。

3.? AMS的getContentProvider函數(shù)分析

getContentProvider的功能主要由getContentProviderImpl函數(shù)實現(xiàn),故此處可直接對它進行分析。

(1)?getContentProviderImpl啟動目標進程

getContentProviderImpl函數(shù)較長,可分段來看,先來分析下面一段代碼。

[-->ActivityManagerService.java::getContentProviderImpl]

?privatefinal ContentProviderHolder getContentProviderImpl(

?????? ???????????????????IApplicationThread caller, String name) {

?ContentProviderRecord cpr;

?ProviderInfo cpi = null;

?

?synchronized(this) {

????ProcessRecord r = null;

???? if(caller != null) {

????????? r= getRecordForAppLocked(caller);

??????????if (r == null)......//如果查詢不到調用者信息,則拋出SecurityException

? ????}// if (caller != null)判斷結束

??? //name參數(shù)為調用進程指定的代表目標ContentProvider的authority

??? cpr =mProvidersByName.get(name);

??? //如果cpr不為空,表明該ContentProvider已經在AMS中注冊

??? booleanproviderRunning = cpr != null;

??? if(providerRunning){

???????......//如果該ContentProvider已經存在,則進行對應處理, 相關內容可自行閱讀

??? }

?? //如果目標ContentProvider對應進程還未啟動

?? if(!providerRunning) {

????? ?try {

???????????//查詢PKMS,得到指定的ProviderInfo信息

???????????cpi = AppGlobals.getPackageManager().resolveContentProvider(

???????????????????name,STOCK_PM_FLAGS |

???????????????????PackageManager.GET_URI_PERMISSION_PATTERNS);

?????? ?}......

??????? String msg;

????? //權限檢查,此處不作討論

????? if((msg=checkContentProviderPermissionLocked(cpi, r)) != null)

?????????throw new SecurityException(msg);

???? /*

????? 如果system_server還沒啟動完畢,并且該ContentProvider不運行在system_server

????? 中,則此時不允許啟動ContentProvider。讀者還記得哪個ContentProvider運行在

?????system_server進程中嗎?答案是SettingsProvider

? ??*/

???? .......

?

????ComponentName comp = new ComponentName(cpi.packageName, cpi.name);

???? cpr =mProvidersByClass.get(comp);

???? finalboolean firstClass = cpr == null;

???? //初次啟動MediaProvider對應進程時,firstClass一定為true

??? ?if (firstClass) {

????? ???try {

?????????????//查詢PKMS,得到MediaProvider所在的Application信息

???????? ?????ApplicationInfoai =

??????????????????????AppGlobals.getPackageManager().getApplicationInfo(

??????????????????????????cpi.applicationInfo.packageName, STOCK_PM_FLAGS);

?????????? ??if (ai == null) return null;

????????????//在AMS內部通過ContentProviderRecord來保存ContentProvider的信息,類似

????????????//ActivityRecord,BroadcastRecord等

????????? ???cpr = new ContentProviderRecord(cpi, ai,comp);

?????? ??}......

??? }// if(firstClass)判斷結束

以上代碼的邏輯比較簡單,主要是為目標ContentProvider(即MediaProvider)創(chuàng)建一個ContentProviderRecord對象。結合第6章的知識,AMS為四大組件都設計了對應的數(shù)據(jù)結構,如ActivityRecord、BroadcastRecord等。

接著看getContentProviderImpl,其下一步的工作就是啟動目標進程:

[-->ActivityManagerService.java::getContentProviderImpl]

?? /*

?? canRunHere函數(shù)用于判斷目標CP能否運行在r所對應的進程(即調用者所在進程)

?? 該函數(shù)內部做如下判斷:

?? (info.multiprocess|| info.processName.equals(app.processName))

????&& (uid == Process.SYSTEM_UID || uid == app.info.uid)

?? ?就本例而言,MediaProvider不能運行在客戶端進程中

?? */

??? if (r !=null && cpr.canRunHere(r)) ?returncpr;

?

??? finalint N = mLaunchingProviders.size();

??? ......//查找目標ContentProvider對應的進程是否正處于啟動狀態(tài)

??? //如果i大于等于N,表明目標進程的信息不在mLaunchingProviders中

??? if (i>= N) {

?????? finallong origId = Binder.clearCallingIdentity();

?????? ......

?????? //調用startProcessLocked函數(shù)創(chuàng)建目標進程

??????ProcessRecord proc = startProcessLocked(cpi.processName,

???????????????????????? cpr.appInfo, false, 0,"content provider",

???????????????????????? newComponentName(cpi.applicationInfo.packageName,

????????????????????????? cpi.name), false);

?????? if(proc == null)return null;

??????cpr.launchingApp = proc;

?????? //將該進程信息保存到mLaunchingProviders中

??????mLaunchingProviders.add(cpr);

??? }

?

???? if(firstClass) mProvidersByClass.put(comp, cpr);

????mProvidersByName.put(name, cpr);

???? /*

?????? 下面這個函數(shù)將為客戶端進程和目標CP進程建立緊密的關系,即當目標CP進程死亡后,

??????? AMS將根據(jù)該函數(shù)建立的關系找到客戶端進程并殺死(kill)它們。在7.2.3節(jié)

?????? ?有對這個函數(shù)的相關解釋

???? */

???? incProviderCount(r, cpr);

???? if(cpr.launchingApp == null) return null;

???? try {

??????????cpr.wait();//等待目前進程的啟動

????? } ......

?? }// synchronized(this)結束

? returncpr;

}

通過對以上代碼的分析發(fā)現(xiàn),getContentProviderImpl將等待一個事件,想必讀者也能明白,此處一定是在等待目標進程啟動并創(chuàng)建好MediaProvider。目標進程的這部分工作用專業(yè)詞語來表達就是發(fā)布目標ContentProvider(即本例的MediaProvider)。

(2)?MediaProvider的創(chuàng)建

根據(jù)第6章的介紹,目標進程啟動后要做的第一件大事就是調用AMS的attachApplication函數(shù),該函數(shù)的主要功能由attachApplicationLocked完成。我們回顧一下相關代碼。

[-->ActivityManagerService.java::attachApplicationLocked]

private final booleanattachApplicationLocked(IApplicationThread thread,

???????????int pid) {

?? ......

?? //通過PKMS查詢運行在該進程中的CP信息,并保存到mProvidersByClass中

?? Listproviders = normalMode ?

???????????????generateApplicationProvidersLocked(app) : null;

? //調用目標應用進程的bindApplication函數(shù),此處將providers信息傳遞給目標進程

?? thread.bindApplication(processName,appInfo, providers,

?????????????????????????????app.instrumentationClass, profileFile,

???????????????????? ?????????......);

?? ......

}

再來看目標進程bindApplication的實現(xiàn),其內部最終會通過handleBindApplication函數(shù)處理,我們回顧一下相關代碼。

[-->ActivtyThread.java::handleBindApplication]

private void handleBindApplication(AppBindDatadata) {

?? ......

?? if(!data.restrictedBackupMode){

???????List<ProviderInfo> providers = data.providers;

??????? if(providers != null) {

???????????//調用installContentProviders安裝

???????????installContentProviders(app, providers);

???????????......

?????? }

? ?}

?? ......

}

AMS傳遞過來的ProviderInfo列表將由目標進程的installContentProviders處理,其相關代碼如下:

[-->ActivtyThread.java::installContentProviders]

private void installContentProviders(Contextcontext,

????? ????????????????????????????????????????List<ProviderInfo>providers) {

?

? finalArrayList<IActivityManager.ContentProviderHolder> results =

????????? ??????newArrayList<IActivityManager.ContentProviderHolder>();

?Iterator<ProviderInfo> i = providers.iterator();

? while(i.hasNext()) {

???? //①也調用installProvider,注意該函數(shù)傳遞的第二個參數(shù)為null

????IContentProvider cp = installProvider(context, null, cpi, false);

???? if (cp!= null) {

?????????IActivityManager.ContentProviderHolder cph =

??????????? ??????????????newIActivityManager.ContentProviderHolder(cpi);

?????????cph.provider = cp;

??????????results.add(cph);//將信息添加到results數(shù)組中

?????????? ......//創(chuàng)建引用計數(shù)

???????? }

??? }//while循環(huán)結束

?

??? try {? //②調用AMS的publishContentProviders發(fā)布ContentProviders

???????????ActivityManagerNative.getDefault().publishContentProviders(

???????????????getApplicationThread(), results);

??????? } ......

?}

以上代碼列出了兩個關鍵點,分別是:

·??調用installProvider得到一個IContentProvider類型的對象。

·??調用AMS的publishContentProviders發(fā)布本進程所運行的ContentProvider。該函數(shù)留到后面再作分析

在繼續(xù)分析之前,筆者要特別強調installProvider,該函數(shù)既在客戶端進程中被調用(還記得7.2.2節(jié)ActivityThread的acquireProvider函數(shù)中那句注釋嗎?),又在目標進程(即此處MediaProvider所在進程)中被調用。與客戶端進程的調用相比,只在一處有明顯的不同:

·??客戶端進程調用installProvider函數(shù)時,該函數(shù)的第二個參數(shù)不為null。

·??目標進程調用installProvider函數(shù)時,該函數(shù)的第二個參數(shù)硬編碼為null。

我們曾經在6.2.3分析過installProvider函數(shù),結合那里的介紹可知:installProvider是一個通用函數(shù),不論客戶端使用遠端的CP還是目標進程安裝運行在其上的CP上,最終都會調用它,只不過參數(shù)不同罷了。

來看installProvider函數(shù),其代碼如下:

[-->ActivityThread.java::installProvider]

private IContentProvider installProvider(Contextcontext,

???????????IContentProvider provider, ProviderInfo info, boolean noisy) {

??ContentProvider localProvider = null;

?? if(provider == null) {//針對目標進程的情況

???????Context c = null;

????????ApplicationInfo ai = info.applicationInfo;

???????? if(context.getPackageName().equals(ai.packageName)) {

???????????????c = context;

???????????}......//這部分代碼已經在6.2.3節(jié)分析過了,其目的就是為了得到正確的

??????????//Context用于加載Java字節(jié)碼

???????? try{

????????????final java.lang.ClassLoader cl = c.getClassLoader();

????????????//通過Java反射機制創(chuàng)建MediaProvider實例

????????????localProvider = (ContentProvider)cl.

?????????????????????????????????????loadClass(info.name).newInstance();

????????????//注意下面這句代碼

????????????provider = localProvider.getIContentProvider();

??????????? }

???? } elseif (localLOGV) {

???????????Slog.v(TAG, "Installing external provider " + info.authority +": "

???????????????????+ info.name);

??? }// if(provider == null)判斷結束

??? /*

???? 由以上代碼可知,對于provider不為null 的情況(即客戶端調用的情況),該函數(shù)沒有

???? 什么特殊的處理

??? */

??? ......

???? /*

????? 引用計數(shù)及設置DeathReceipient等相關操作。在6.2.3節(jié)的第2個小標題中

????? 曾說過,目標進程為自己進程中的CP實例設置DeathReceipient沒有作用,因為二者在同一個

????? 進程中,自己怎么能接收自己的訃告消息呢?不過,如果客戶端進程為目標進程的CP設置

???? DeathReceipient又有作用嗎?仔細思考這個問題

??? */

??? returnprovider;//最終返回的對象是IContentProvider類型,它到底是什么呢?

?}

由以代碼可知,installProvider最終返回的是一個IContentProvider類型的對象。對于目標進程而言,該對象是通過調用CP的實例對象的(本例就是MediaProvider) getIContentProvider函數(shù)得來的。而對于客戶端進程而言,該對象是由installProvider第二個參數(shù)傳遞進來的,那么,這個IContentProvider到底是什么?

(3)?IContentProvider的真面目

要說清楚IContentProvider,就先來看ContentProvider家族的類圖,如圖7-2所示。

http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter7/image002.png" alt="image" />

圖7-2? ContentProvider類圖

圖7-2揭示了IContentProvider的真面目,具體介紹如下:

·??每個ContentProvider實例中都有一個mTransport成員,其類型為Transport。

·??Transport類從ContentProviderNative派生。由圖7-2可知,ContentProviderNative從Binder類派生,并實現(xiàn)了IContentProvider接口。結合前面的代碼,IContentProvider將是客戶端進程和目標進程交互的接口,即目標進程使用IContentProvider的Bn端Transport,而客戶端使用IContentProvider的Bp端,其類型是ContentProviderProxy(定義在ContentProviderNative.java中)。

客戶端如何通過IContentProvider query函數(shù)和目標CP進程交互的呢?其流程如下:

·??CP客戶端得到IContentProvider的Bp端(實際類型是ContentProviderProxy),并調用其query函數(shù),在該函數(shù)內部將參數(shù)信息打包,傳遞給Transport(它是IContentProvider的Bn端)。

·??Transport的onTransact函數(shù)將調用Transport的query函數(shù),而Transport的query函數(shù)又將調用ContentProvider子類定義的query函數(shù)(即MediaProvider的query函數(shù))。

關于目標進程這一系列的調用函數(shù),不妨先看看Transport的query函數(shù),其代碼為:

[-->ContentProvider.java::Transport.query]

public Cursor query(Uri uri, String[] projection,

???????????????String selection, String[] selectionArgs, String sortOrder) {

??enforceReadPermission(uri);

??//Transport為ContentProvider的內部類,此處將調用ContentProvider的query函數(shù)

?? //本例中,該query函數(shù)由MediaProvider實現(xiàn),故最終會調用MediaProvider的query

?? returnContentProvider.this.query(uri, projection, selection,

???????????????????selectionArgs, sortOrder);

?}

務必弄清楚,此處只有一個目標ContentProvider的實例,即只有一個MediaProvider對象。Transport的 query函數(shù)內部雖然調用的是基類ContentProvider的query函數(shù),但是根據(jù)面向對象的多態(tài)原理,該函數(shù)最終由其子類(本例中是MediaProvider)來實現(xiàn)。

認識了IContentProvider,即知道了客戶端進程和目標進程的交互接口。

繼續(xù)我們的分析。此時目標進程需要將MediaProvider的信息通過AMS發(fā)布出去。

(4) ?AMS pulishContentProviders分析

要把目標進程的CP信息發(fā)布出去,需借助AMS 的pulishContentProviders函數(shù),其代碼如下:

[-->ActivityManagerService.java::publishContentProviders]

public final voidpublishContentProviders(IApplicationThread caller,

?????????List<ContentProviderHolder> providers) {

?? ......

??synchronized(this) {

???? finalProcessRecord r = getRecordForAppLocked(caller);

???? finallong origId = Binder.clearCallingIdentity();

?

??? ?final int N = providers.size();

??? ?for (int i=0; i<N; i++) {

???????ContentProviderHolder src = providers.get(i);

???????ContentProviderRecord dst = r.pubProviders.get(src.info.name);

?????? if(dst != null) {

????????? ?......//將相關信息分別保存到mProviderByClass和mProvidersByName中

???????? ??int NL = mLaunchingProviders.size();

?????????...... //目標進程已經啟動,將其從mLaunchingProviders移除

?????????synchronized (dst) {

????????????dst.provider = src.provider;//將信息保存在dst中

????????????dst.proc = r;

???????????//觸發(fā)還等在(wait)getContentProvider中的那個客戶端進程

????????????dst.notifyAll();

? ????????}

????? ?updateOomAdjLocked(r);//調節(jié)目標進程的oom_adj等相關參數(shù)

???? }// if(dst != null)判斷結束

?? ......

?? }

}

至此,客戶端進程將從getContentProvider中返回,并調用installProvider函數(shù)。根據(jù)前面的分析,客戶端進程調用installProvider時,其第二個參數(shù)不為null,即客戶端進程已經從AMS中得到了能直接和目標進程交互的IContentProvider Bp端對象。此后,客戶端就可直接使用該對象向目標進程發(fā)起請求。

7.2.3? MediaProvider的啟動及創(chuàng)建總結

回顧一下整個MediaProvider的啟動和創(chuàng)建過程,如圖7-3所示。

http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter7/image003.png" alt="image" />

圖7-3? MediaProvider的啟動和創(chuàng)建流程

整個流程相對比較簡單。讀者在分析時只要注意installProvider這個函數(shù)在目標進程和客戶端進程中被調用時的區(qū)別即可。這里再次強調:

·??目標進程調用installProvider時,傳遞的第二個參數(shù)為null,使內部通過Java反射機制真正創(chuàng)建目標CP實例。

·??客戶端調用installProvider時,其第二個參數(shù)已經通過查詢AMS得到。該函數(shù)真正的工作只不過是引用計數(shù)控制和設置訃告接收對象罷了。

至此,客戶端進程和目標進程通信的通道IContentProvider已經登場。除此之外,客戶端進程和目標CP還建立了非常緊密的關系,這種關系造成的后果就是一旦目標CP進程死亡,AMS會殺死與之有關的客戶端進程。不妨回顧一下與之相關的知識點:

·??該關系的建立是在AMS getContentProviderImpl函數(shù)中調用incProviderCount完成的,關系的確立以ContentProviderRecorder保存客戶端進程的ProcessRecord信息為標識。

·??一旦CP進程死亡,AMS能根據(jù)該ContentProviderRecorder中保存的客戶端信息找到使用該CP的所有客戶端進程,然后再殺死它們。

客戶端能否撤銷這種緊密關系呢?答案是肯定的,但這和Cursor是否關閉有關。這里先簡單描述一下流程:

·??當Cursor關閉時,ContextImpl的releaseProvider會被調用。根據(jù)前面的介紹,它最終會調用ActivityThread的releaseProvider函數(shù)。

·??ActivityThread的releaseProvider函數(shù)會導致completeRemoveProvider被調用,在其內部根據(jù)該CP的引用計數(shù)判斷是否需要調用AMS的removeContentProvider。

·??通過AMS的removeContentProvider將刪除對應ContentProviderRecord中此客戶端進程的信息,這樣一來,客戶端進程和目標CP進程的緊密關系就蕩然無存了。

至此,本章第一條分析路線就介紹完畢。

提示讀者可能覺得,這條路線是對第6章的補充和延續(xù)。不過,雖然目標進程由AMS創(chuàng)建和啟動,而且ContentProvider的發(fā)布也需和AMS交互,但是對于ContentProvider來說,我們更關注客戶端和目標進程中ContentProvider實例間的交互。事實上,客戶端得到IContentProvider Bp端對象后,即可直接與目標進程的CP實例交互,也就無需借助AMS了,所以筆者將這條路線放到了本章進行分析。

7.3? SQLite創(chuàng)建數(shù)據(jù)庫分析

作為Android多媒體系統(tǒng)中媒體信息的倉庫,MediaProvider使用了SQLite數(shù)據(jù)庫來管理系統(tǒng)中多媒體相關的數(shù)據(jù)信息。作為第二條分析路線,本節(jié)的目標是分析MediaProvider如何利用SQLite創(chuàng)建數(shù)據(jù)庫,同時還將介紹和SQLite相關的一些知識點。

先來看大名鼎鼎的SQLite及Java層的SQLiteDatabase家族。

7.3.1? SQLite及SQLiteDatabase家族

1.? SQLite輕裝上陣

SQLite是一個輕量級的數(shù)據(jù)庫,它和筆者之前接觸的SQLServer或Oracle DB比起來,猶如螞蟻和大象的區(qū)別。它“輕”到什么程度呢?筆者總結了SQLite具有的兩個特點:

·??從代碼上看,SQLite所有的功能都實現(xiàn)在Sqlite3.c中,而頭文件Sqlite3.h定義了它所支持的API。其中,Sqlite3.c文件包含12萬行左右的代碼,相當于一個中等偏小規(guī)模的程序。

·??從使用者角度的來說,SQLite編譯完成后將生成一個libsqlite.so,大小僅為300多KB。

SQLite確實夠輕,但這個“輕”還不是本節(jié)標題“輕裝上陣”一詞中的“輕”。為什么?此處先向讀者們介紹一個直接使用SQLite API開發(fā)的Android native程序示例,該示例最終編譯成的二進制可執(zhí)行程序名為sqlitetest。

注意本書后文所說的SQLite API特指libsqlite.so提供的Native層的API。

使用SQLite API開發(fā)的Android native程序示例的代碼如下:

[-->SqliteTest.cpp::libsqlite示例]

#include <unistd.h>

#include <sqlite3.h> //包含sqlite API頭文件,這里的3是SQLite的版本號

#include <stdlib.h>

?

#define LOG_TAG "SQLITE_TEST"? //定義該進程logcat輸出的標簽

#include <utils/Log.h>

#ifndef NULL

?? #defineNULL (0)

#endif

//聲明數(shù)據(jù)庫文件的路徑

#define DB_PATH"/mnt/sdcard/sqlite3test.db"

/*

?? 聲明一個全局的SQLite句柄,開發(fā)者無需了解該數(shù)據(jù)結構的具體內容,只要知道它代表了使用者

?? 和數(shù)據(jù)庫的一種連接關系即可。以后凡是針對特定數(shù)據(jù)庫的操作,都需要傳入對應的SQLite句柄

*/

static sqlite3* g_pDBHandle = NULL;

?

/*

?? 定義一個宏,用于檢測SQLite API調用的返回值,如果value不等于expectValue,則打印警告,

?? 并退出程序。注意,進程退出后,系統(tǒng)會自動回收分配的內存資源。對如此簡單的例子來說,讀者大可

??? 不必苛責。其中,sqlite3_errmsg函數(shù)用于打印錯誤信息

*/

#define CHECK_DB_ERR(value,expectValue) \

do \

{ \

?? if(value!= expectValue)\

?? {\

????LOGE("Sqlite db fail:%s",g_pDBHandle==NULL?"db not \

??? ???????connected":sqlite3_errmsg(g_pDBHandle));\

????exit(0);\

?? }\

}while(0)

?

?

int main(int argc, char* argv[])

{

??LOGD("Delete old DB file");

??unlink(DB_PATH);//先刪除舊的數(shù)據(jù)庫文件

??LOGD("Create new DB file");

?? /*

??? 調用sqlite3_open創(chuàng)建一個數(shù)據(jù)庫,并將和該數(shù)據(jù)庫的連接環(huán)境保存在全局的SQLite句柄

???? g_pDBHandle中,以后操作g_pDBHandle就是操作DB_PATH對應的數(shù)據(jù)庫

??? */

?? int ret =sqlite3_open(DB_PATH,&g_pDBHandle);

??CHECK_DB_ERR(ret,SQLITE_OK);

??

??LOGD("Create Table personal_info:");

?? /*

??? 定義宏TABLE_PERSONAL_INFO,用于描述為本例數(shù)據(jù)庫建立一個表所用的SQL語句,

??? 不熟悉SQL語句的讀者可先學習相關知識。從該宏的定義可知,將建立一個名為

???personal_info的表,該表有4列,第一列是主鍵,類型是整型(SQLite中為INTEGER),

??? 每加入一行數(shù)據(jù)該值會自動遞增;第二列名為"name",數(shù)據(jù)類型是字符串(SQLite中為TEXT);

???? 第三列名為“age”,數(shù)據(jù)類型是整型;第四列名為“sex”,數(shù)據(jù)類型是字符串

?? */

?? #defineTABLE_PERSONAL_INFO? \

?? ???????"CREATETABLE personal_info" \

?? ????????"(ID INTEGER primary keyautoincrement," \

?????????? "nameTEXT," \

?? ????????"age INTEGER,"\

?? ????????"sex TEXT"\

?? ????????")"

?? //打印TABLE_PERSONAL_INFO所對應的SQL語句

??LOGD("\t%s\n",TABLE_PERSONAL_INFO);

? //調用sqlite3_exec執(zhí)行前面那條SQL語句

?? ret =sqlite3_exec(g_pDBHandle,TABLE_PERSONAL_INFO,NULL,NULL,NULL);

??CHECK_DB_ERR(ret,SQLITE_OK);

??

?? /*

??? 定義插入一行數(shù)據(jù)所使用的SQL語句,注意最后一行中的問號,它表示需要在插入數(shù)據(jù)

?? 前和具體的值綁定。插入數(shù)據(jù)庫對應的SQL語句是標準的INSERT命令

?? */

??LOGD("Insert one personal info into personal_info table");

?? #defineINSERT_PERSONAL_INFO? \

??"INSERT INTO personal_info"\

??"(name,age,sex)"\

??"VALUES"\

??"(?,?,?)"?? //注意這一行語句中的問號

??LOGD("\t%s\n",INSERT_PERSONAL_INFO);

?

?? //sqlite3_stmt是SQLite中很重要的一個結構體,它代表了一條SQL語句

?? sqlite3_stmt* pstmt = NULL;

?? /*

??? 調用sqlite3_prepare初始化pstmt,并將其和INSERT_PERSONAL_INFO綁定。

??? 也就是說,如果執(zhí)行pstmt,就會執(zhí)行INSERT_PERSONAL_INFO語句

?? */

?? ret =sqlite3_prepare(g_pDBHandle,INSERT_PERSONAL_INFO,-1,&pstmt,NULL);

??CHECK_DB_ERR(ret,SQLITE_OK);

? /*

??? 調用sqlite3_bind_xxx為該pstmt中對應的問號綁定具體的值,該函數(shù)的第二個參數(shù)用于

??? 指定第幾個問號

? */

?? ret =sqlite3_bind_text(pstmt,1,"dengfanping",-1,SQLITE_STATIC);

?? ret =sqlite3_bind_int(pstmt,2,30);

?? ret =sqlite3_bind_text(pstmt,3,"male",-1,SQLITE_STATIC);

?? //調用sqlite3_step執(zhí)行對應的SQL語句,該函數(shù)如果執(zhí)行成功,我們的personal_info

?? //表中將添加一條新記錄,對應值為(1,dengfanping,30,male)

?? ret =sqlite3_step(pstmt);

??CHECK_DB_ERR(ret,SQLITE_DONE);

?? //調用sqlite3_finalize銷毀該SQL語句

?? ret =sqlite3_finalize(pstmt);

??

?? //下面將從表中查詢name為"dengfanping"的person的age值

??LOGD("select dengfanping's age from personal_info table");

?? pstmt =NULL;

?? /*

??? 重新初始化該pstmt,并將其和SQL語句“SELECT age FROM personal_info WHERE name

???? = ?”綁定

?? */

?? ret =sqlite3_prepare(g_pDBHandle,"SELECT age FROM personal_info WHERE name

???????????????????????????????????????????? ?=? ",-1,&pstmt,NULL);

??CHECK_DB_ERR(ret,SQLITE_OK);??

?? /*

??? 綁定pstmt中第一個問號為字符串“dengfanping”,最終該SQL語句為

??? SELECTage FROM personal_info WHERE name = 'dengfanping'

?? */

?? ret =sqlite3_bind_text(pstmt,1,"dengfanp