ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
第二个关键点是在MediaProvider客户端示例中所调用的MediaStore.Image.Media 的query函数。MediaStore是多媒体开发中常用的类,其内部定义了专门针对Image、Audio、Video等不同多媒体信息的内部类来帮助客户端开发人员更好底和MediaProvider交互。这些类及相互之间的关系如图7-1所示。 :-: ![](http://img.blog.csdn.net/20150803130759625?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-1 MediaStore类图 由图7-1可知,MediaStore定义了较多的内部类,我们重点展示作为内部类之一的Image的情况,其中: - MediaColumns定义了所有与媒体相关的数据库表都会用到的数据库字段,而ImageColumns定义了单独针对Image的数据库字段。 - Image类定义了一个名为Media的内部类用于查询和Image相关的信息,同时Image类还定义了一个名为Thumbnails的内部类用于查询和Image相关的缩略图的信息(在Android平台上,缩略图的来源有两种,一种是Image,另一种是Video,故Image定义了名为Thumbnails的内部类,而Video也定义了一个名为Thumbnails的内部类)。 * * * * * **提示**:MediaStore类较为复杂,主要原因是它定义了一些同名类。读者阅读代码时务须仔细。 * * * * * 下面来看Image.Media的query函数,其代码非常简单,如下所示: **MediaStore.java::Image.Media.query** ~~~ public static final class Media implementsImageColumns { public static final Cursor query(ContentResolvercr,Uri uri, String[]projection) { //直接调用ContentResolver的query函数 returncr.query(uri, projection, null, null,DEFAULT_SORT_ORDER); } ~~~ Image.Media的query函数直接调用ContentResolver的query函数,虽然cr的真实类型是ApplicationContentResolver,但是此函数却由其基类ContentResolver实现。 * * * * * **提示**:追求运行效率的程序员也许会对上边这段代码的实现略有微词,因为Image.Media的query函数基本上没做任何有意义的工作。假如客户端直接调用cr.query函数,则此处的query就增加了一次函数调用和返回的开销(即Image.Media query调用和返回时参数的入栈/出栈)。但是,通过Image.Media的封装将使程序更清晰和易读(与直接使用ContentResolver的query相比,代码阅读者一看Image.Media就知道其query函数应该和Image有关,否则需要通过解析uri参数才能确定查询的信息是什么)。代码清晰易读和运行效率高,往往是软件开发中的熊掌和鱼,它们之间的对立性,将在本章中体现得淋漓尽致。笔者建议读者在实际开发中结合具体情况决定取舍,万不可钻牛角尖。 * * * * * 1. ContentResolver的query函数分析 **ContentResolver.java::query** ~~~ public final Cursor query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder) { //调用acquireProvider函数,参数为uri,函数也由ContentResolver实现 IContentProvider provider = acquireProvider(uri); //注意:下面将和ContentProvider交互, 相关知识留待7.4节再分析 ...... } ~~~ ContentResolver的query将调用acquireProvider,该函数定义在ContentResolver类中,代码如下: **ContentResolver.java::query** ~~~ public final IContentProvider acquireProvider(Uriuri) { if(!SCHEME_CONTENT.equals(uri.getScheme())) return null; Stringauth = uri.getAuthority(); if (auth!= null) { //acquireProvider是一个抽象函数,由ContentResolver的子类实现。在本例中,该函数 //将由ApplicationContentResolver实现。uri.getAuthority将返回代表目标 //ContentProvider的名字 returnacquireProvider(mContext, uri.getAuthority()); } returnnull; } ~~~ 如上所述,acquireProvider由ContentResolver的子类实现,在本例中该函数由ApplicationContentResolver定义,代码如下: **ContextImpl.java::acquireProvider** ~~~ protected IContentProvider acquireProvider(Contextcontext, String name) { //mMainThread指向代表应用进程主线程的ActivityThread对象,每个应用进程只有一个 //ActivityThread对象 return mMainThread.acquireProvider(context,name); } ~~~ 如以上代码所示,最终ActivityThread的acquireProvider函数将被调用,希望它不要再被层层转包了。 2. AcitvityThread的acquireProvider函数分析 ActivityThread的 acquireProvider函数的代码如下: **ActivityThread.java::acquireProvider** ~~~ public final IContentProvideracquireProvider(Context c, String name) { //①调用getProvider函数,它很重要。见下文分析 IContentProvider provider = getProvider(c,name); ...... IBinderjBinder = provider.asBinder(); synchronized(mProviderMap) { //客户端进程将本进程使用的ContentProvider信息保存到mProviderRefCountMap中, //其主要功能与引用计数和资源释放有关,读者暂可不理会它 ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if(prc== null) mProviderRefCountMap.put(jBinder, newProviderRefCount(1)); else prc.count++; } returnprovider; } ~~~ 在acquireProvider内部调用getProvider得到一个IContentProvider类型的对象,该函数非常重要,其代码为: **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); }...... //注意:记住下面这个函数调用,此时是在客户端进程中 IContentProvider prov = installProvider(context, holder.provider, holder.info, true); ...... returnprov; } ~~~ 以上代码中让人比较头疼的是其中新出现的几种数据类型,如IContentProvider、ContentProviderHolder。先来分析AMS的getContentProvider。 3. AMS的getContentProvider函数分析 getContentProvider的功能主要由getContentProviderImpl函数实现,故此处可直接对它进行分析。 (1) getContentProviderImpl启动目标进程 getContentProviderImpl函数较长,可分段来看,先来分析下面一段代码。 **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参数为调用进程指定的代表目标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)创建一个ContentProviderRecord对象。结合第6章的知识,AMS为四大组件都设计了对应的数据结构,如ActivityRecord、BroadcastRecord等。 接着看getContentProviderImpl,其下一步的工作就是启动目标进程: **ActivityManagerService.java::getContentProviderImpl** ~~~ /* canRunHere函数用于判断目标CP能否运行在r所对应的进程(即调用者所在进程) 该函数内部做如下判断: (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对应的进程是否正处于启动状态 //如果i大于等于N,表明目标进程的信息不在mLaunchingProviders中 if (i>= N) { finallong origId = Binder.clearCallingIdentity(); ...... //调用startProcessLocked函数创建目标进程 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); /* 下面这个函数将为客户端进程和目标CP进程建立紧密的关系,即当目标CP进程死亡后, AMS将根据该函数建立的关系找到客户端进程并杀死(kill)它们。在7.2.3节 有对这个函数的相关解释 */ incProviderCount(r, cpr); if(cpr.launchingApp == null) return null; try { cpr.wait();//等待目前进程的启动 } ...... }// synchronized(this)结束 returncpr; } ~~~ 通过对以上代码的分析发现,getContentProviderImpl将等待一个事件,想必读者也能明白,此处一定是在等待目标进程启动并创建好MediaProvider。目标进程的这部分工作用专业词语来表达就是发布目标ContentProvider(即本例的MediaProvider)。 (2) MediaProvider的创建 根据第6章的介绍,目标进程启动后要做的第一件大事就是调用AMS的attachApplication函数,该函数的主要功能由attachApplicationLocked完成。我们回顾一下相关代码。 **ActivityManagerService.java::attachApplicationLocked** ~~~ private final booleanattachApplicationLocked(IApplicationThread thread, int pid) { ...... //通过PKMS查询运行在该进程中的CP信息,并保存到mProvidersByClass中 Listproviders = normalMode ? generateApplicationProvidersLocked(app) : null; //调用目标应用进程的bindApplication函数,此处将providers信息传递给目标进程 thread.bindApplication(processName,appInfo, providers, app.instrumentationClass, profileFile, ......); ...... } ~~~ 再来看目标进程bindApplication的实现,其内部最终会通过handleBindApplication函数处理,我们回顾一下相关代码。 **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,注意该函数传递的第二个参数为null IContentProvider cp = installProvider(context, null, cpi, false); if (cp!= null) { IActivityManager.ContentProviderHolder cph = newIActivityManager.ContentProviderHolder(cpi); cph.provider = cp; results.add(cph);//将信息添加到results数组中 ......//创建引用计数 } }//while循环结束 try { //②调用AMS的publishContentProviders发布ContentProviders ActivityManagerNative.getDefault().publishContentProviders( getApplicationThread(), results); } ...... } ~~~ 以上代码列出了两个关键点,分别是: - 调用installProvider得到一个IContentProvider类型的对象。 - 调用AMS的publishContentProviders发布本进程所运行的ContentProvider。该函数留到后面再作分析 在继续分析之前,笔者要特别强调installProvider,该函数既在客户端进程中被调用(还记得7.2.2节ActivityThread的acquireProvider函数中那句注释吗?),又在目标进程(即此处MediaProvider所在进程)中被调用。与客户端进程的调用相比,只在一处有明显的不同: - 客户端进程调用installProvider函数时,该函数的第二个参数不为null。 - 目标进程调用installProvider函数时,该函数的第二个参数硬编码为null。 我们曾经在6.2.3分析过installProvider函数,结合那里的介绍可知:installProvider是一个通用函数,不论客户端使用远端的CP还是目标进程安装运行在其上的CP上,最终都会调用它,只不过参数不同罢了。 来看installProvider函数,其代码如下: **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节分析过了,其目的就是为了得到正确的 //Context用于加载Java字节码 try{ final java.lang.ClassLoader cl = c.getClassLoader(); //通过Java反射机制创建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 的情况(即客户端调用的情况),该函数没有 什么特殊的处理 */ ...... /* 引用计数及设置DeathReceipient等相关操作。在6.2.3节的第2个小标题中 曾说过,目标进程为自己进程中的CP实例设置DeathReceipient没有作用,因为二者在同一个 进程中,自己怎么能接收自己的讣告消息呢?不过,如果客户端进程为目标进程的CP设置 DeathReceipient又有作用吗?仔细思考这个问题 */ returnprovider;//最终返回的对象是IContentProvider类型,它到底是什么呢? } ~~~ 由以代码可知,installProvider最终返回的是一个IContentProvider类型的对象。对于目标进程而言,该对象是通过调用CP的实例对象的(本例就是MediaProvider) getIContentProvider函数得来的。而对于客户端进程而言,该对象是由installProvider第二个参数传递进来的,那么,这个IContentProvider到底是什么? (3) IContentProvider的真面目 要说清楚IContentProvider,就先来看ContentProvider家族的类图,如图7-2所示。 :-: ![](http://img.blog.csdn.net/20150803130820677?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-2 ContentProvider类图 图7-2揭示了IContentProvider的真面目,具体介绍如下: - 每个ContentProvider实例中都有一个mTransport成员,其类型为Transport。 - Transport类从ContentProviderNative派生。由图7-2可知,ContentProviderNative从Binder类派生,并实现了IContentProvider接口。结合前面的代码,IContentProvider将是客户端进程和目标进程交互的接口,即目标进程使用IContentProvider的Bn端Transport,而客户端使用IContentProvider的Bp端,其类型是ContentProviderProxy(定义在ContentProviderNative.java中)。 客户端如何通过IContentProvider query函数和目标CP进程交互的呢?其流程如下: - CP客户端得到IContentProvider的Bp端(实际类型是ContentProviderProxy),并调用其query函数,在该函数内部将参数信息打包,传递给Transport(它是IContentProvider的Bn端)。 - Transport的onTransact函数将调用Transport的query函数,而Transport的query函数又将调用ContentProvider子类定义的query函数(即MediaProvider的query函数)。 关于目标进程这一系列的调用函数,不妨先看看Transport的query函数,其代码为: **ContentProvider.java::Transport.query** ~~~ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { enforceReadPermission(uri); //Transport为ContentProvider的内部类,此处将调用ContentProvider的query函数 //本例中,该query函数由MediaProvider实现,故最终会调用MediaProvider的query returnContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder); } ~~~ 务必弄清楚,此处只有一个目标ContentProvider的实例,即只有一个MediaProvider对象。Transport的 query函数内部虽然调用的是基类ContentProvider的query函数,但是根据面向对象的多态原理,该函数最终由其子类(本例中是MediaProvider)来实现。 认识了IContentProvider,即知道了客户端进程和目标进程的交互接口。 继续我们的分析。此时目标进程需要将MediaProvider的信息通过AMS发布出去。 (4) AMS pulishContentProviders分析 要把目标进程的CP信息发布出去,需借助AMS 的pulishContentProviders函数,其代码如下: **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; //触发还等在(wait)getContentProvider中的那个客户端进程 dst.notifyAll(); } updateOomAdjLocked(r);//调节目标进程的oom_adj等相关参数 }// if(dst != null)判断结束 ...... } } ~~~ 至此,客户端进程将从getContentProvider中返回,并调用installProvider函数。根据前面的分析,客户端进程调用installProvider时,其第二个参数不为null,即客户端进程已经从AMS中得到了能直接和目标进程交互的IContentProvider Bp端对象。此后,客户端就可直接使用该对象向目标进程发起请求。