·??深入分析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
本章重點分析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)。
閑話少說,立即開始本次分析之旅。
第一、二、三條分析路線都將以下面這段示例為參考。
[-->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。
根據(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。
?
第二個關鍵點是在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ā)中結合具體情況決定取舍,萬不可鉆牛角尖。
[-->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ù)將被調用,希望它不要再被層層轉包了。
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。
getContentProvider的功能主要由getContentProviderImpl函數(shù)實現(xiàn),故此處可直接對它進行分析。
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)。
根據(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到底是什么?
要說清楚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ā)布出去。
要把目標進程的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ā)起請求。
回顧一下整個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了,所以筆者將這條路線放到了本章進行分析。
作為Android多媒體系統(tǒng)中媒體信息的倉庫,MediaProvider使用了SQLite數(shù)據(jù)庫來管理系統(tǒng)中多媒體相關的數(shù)據(jù)信息。作為第二條分析路線,本節(jié)的目標是分析MediaProvider如何利用SQLite創(chuàng)建數(shù)據(jù)庫,同時還將介紹和SQLite相關的一些知識點。
先來看大名鼎鼎的SQLite及Java層的SQLiteDatabase家族。
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