Android7.0中关于ContentProvider组件详解
作为Android的四大组件之一,ContentProvider作为进程之间静态数据传递的重要手段,其在系统级别的应用中起了重大的作用。毫无疑问,ContentProvider核心机制之一也是Binder,但是和其它3大组件又有区别。因为ContentProvider涉及数据的增删查改,当数据量比较大的时候,继续用Parcel做容器效率会比较低,因此它还使用了匿名共享内存的方式。
但是有一个问题是,ContentProvider的提供者进程不再存活时,其他进程通过Provider读一个非常简单的数据时,都需要先把提供者进程启动起来(除非指定multiprocess=true),这对用户是相当不友好的。又因为其是间接通过db进行数据操作,所以效率也远不如直接操作db。因此在用户app中,不是很建议经常使用ContentProvider。不过对于系统级的app,它统一了数据操作的规范,利是远大于弊的。
ContentProvider发布
当进程第一次启动时候会调用handleBindApplication
if(!data.restrictedBackupMode){ if(!ArrayUtils.isEmpty(data.providers)){ installContentProviders(app,data.providers); } }
当xml中有provider时,进行provider的发布
finalArrayListresults= newArrayList (); for(ProviderInfocpi:providers){ IActivityManager.ContentProviderHoldercph=installProvider(context,null,cpi, false/*noisy*/,true/*noReleaseNeeded*/,true/*stable*/); if(cph!=null){ cph.noReleaseNeeded=true; results.add(cph); } } try{ ActivityManagerNative.getDefault().publishContentProviders( getApplicationThread(),results); }catch(RemoteExceptionex){ }
@installProvider(这个方法先简单过一下,后面会继续说)
finaljava.lang.ClassLoadercl=c.getClassLoader(); localProvider=(ContentProvider)cl. loadClass(info.name).newInstance(); provider=localProvider.getIContentProvider();
@installProviderAuthoritiesLocked
for(Stringauth:auths){ finalProviderKeykey=newProviderKey(auth,userId); finalProviderClientRecordexisting=mProviderMap.get(key); if(existing!=null){ }else{ mProviderMap.put(key,pcr); } }
这里两步把ProviderInfo通过installProvider转换成ContentProvider的Binder对象IContentProvider,并放于ContentProviderHolder中。并根据auth的不同,把发布进程的ProviderClientRecord保存在一个叫mProviderMap的成员变量中,方便第二次调用同一个ContentProvider时,无需重新到AMS中去查询。
AMS@publishContentProviders
finalintN=providers.size(); for(inti=0;i可以看到,AMS会遍历所有的ContentProviderHolder,然后调用mProviderMap把信息保存起来,这块接下来说。保存好之后,先去看看之前是不是已经有launch过的,如果已经有launch过的,不再重复launch。再说说这个mProviderMap,这个和ActivityThread中的mProviderMap不太一样,这个是一个成员实例,非真正的map。看看putProviderByClass和putProviderByName。
ProviderMap@putProviderByClass
if(record.singleton){ mSingletonByClass.put(name,record); }else{ finalintuserId=UserHandle.getUserId(record.appInfo.uid); getProvidersByClass(userId).put(name,record); }ProviderMap@putProviderByName
if(record.singleton){ mSingletonByName.put(name,record); }else{ finalintuserId=UserHandle.getUserId(record.appInfo.uid); getProvidersByName(userId).put(name,record); }可以看到,发布的Provider实际会根据class或authority存在不同的map中。如果是单例,则分别存到相应的mSingletonmap中,否则就根据userId存到相应的map中。这样发布的过程就完成了,其他进程需要使用的时候将会在AMS按需读取。
ContentReslover跨进程数据操作
当我们跨进程调用数据时候,会先调用获取用户进程的ContentResolver
context.getContentResolver().query(uri,...); publicContentResolvergetContentResolver(){ returnmContentResolver; }而这个ContentResolver在每个进程中都存在有且唯一的实例,其在ContextImpl构造函数中就已经初始化了,其初始化的实际对象是ApplicationContentResolver。
mContentResolver=newApplicationContentResolver(this,mainThread,user);这个ContentResolver是活在调用者进程中的,它是作为一个类似桥梁的作用。以插入为例:
ContentResolver@insert
IContentProviderprovider=acquireProvider(url); if(provider==null){ thrownewIllegalArgumentException("UnknownURL"+url); } try{ longstartTime=SystemClock.uptimeMillis(); UricreatedRow=provider.insert(mPackageName,url,values); ... returncreatedRow; }catch(RemoteExceptione){ returnnull; }finally{ releaseProvider(provider); }问题就转化成了,拿到其他进程的ContentProvider的Binder对象,有了binder对象就可以跨进程调用其方法了。
ContentResolver@acquireProvider
if(!SCHEME_CONTENT.equals(uri.getScheme())){ returnnull; } finalStringauth=uri.getAuthority(); if(auth!=null){ returnacquireProvider(mContext,auth); }校验其URI,其scheme必须为content。
ApplicationContentResolver@acquireProvider
protectedIContentProvideracquireProvider(Contextcontext,Stringauth){ returnmMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth),true); }这里面有个特别的函数会传递一个true的参数给ActivityThread,这意味本次连接是stable的。那stable和非stable的区别是什么呢?这么说吧:
Stableprovider:若使用过程中,provider要是挂了,你的进程也必挂。
Unstableprovider:若使用过程中,provider要是挂了,你的进程不会挂。但你会收到一个DeadObjectException的异常,可进行容错处理。
继续往下。
ActivityThread@acquireProvider
finalIContentProviderprovider=acquireExistingProvider(c,auth,userId,stable); if(provider!=null){ returnprovider; } IActivityManager.ContentProviderHolderholder=null; try{ holder=ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(),auth,userId,stable); }catch(RemoteExceptionex){ } if(holder==null){ returnnull; } holder=installProvider(c,holder,holder.info, true/*noisy*/,holder.noReleaseNeeded,stable); returnholder.provider;这里面分了三步,1、寻找自身进程的缓存,有直接返回。2、缓存没有的话,寻找AMS中的Provider。3、InstallProvider,又到了这个方法。怎么个install法?还是一会儿再说。
@acquireExistingProvider(寻找自身缓存)
synchronized(mProviderMap){ finalProviderKeykey=newProviderKey(auth,userId); finalProviderClientRecordpr=mProviderMap.get(key); if(pr==null){ returnnull; } IContentProviderprovider=pr.mProvider; IBinderjBinder=provider.asBinder(); ... ProviderRefCountprc=mProviderRefCountMap.get(jBinder); if(prc!=null){ incProviderRefLocked(prc,stable); } returnprovider;这一步就是读取我们发布时提到的mProviderMap中的缓存。当provider记录存在,且进程存活的情况下,则在provider引用计数不为空时则继续增加引用计数。
缓存不存在,则去AMS中找
AMS@getContentProviderImpl
ContentProviderRecordcpr; cpr=mProviderMap.getProviderByName(name,userId); if(providerRunning){ if(r!=null&&cpr.canRunHere(r)){ ContentProviderHolderholder=cpr.newHolder(null); holder.provider=null; returnholder; } }publicbooleancanRunHere(ProcessRecordapp){ return(info.multiprocess||info.processName.equals(app.processName)) &&uid==app.info.uid; }Provider是提供保护数据的接入访问的。一般情况下,不同进程的访问只能通过IPC来进行,但那是有些情况是可以允许访问者在自己的进程中创建本地Provider来进行访问的。
这种情况是在UID必须相同的前提下,要么同一进程,要么provider设定了multiprocess为true。
if(!providerRunning){ cpi=AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS|PackageManager.GET_URI_PERMISSION_PATTERNS,userId); ... ComponentNamecomp=newComponentName(cpi.packageName,cpi.name); cpr=mProviderMap.getProviderByClass(comp,userId); if(r!=null&&cpr.canRunHere(r)){ returncpr.newHolder(null); } ProcessRecordproc=getProcessRecordLocked( cpi.processName,cpr.appInfo.uid,false); if(proc!=null&&proc.thread!=null){ if(!proc.pubProviders.containsKey(cpi.name)){ proc.pubProviders.put(cpi.name,cpr); proc.thread.scheduleInstallProvider(cpi); } }else{ proc=startProcessLocked(cpi.processName, cpr.appInfo,false,0,"contentprovider", newComponentName(cpi.applicationInfo.packageName, cpi.name),false,false,false); } } } mProviderMap.putProviderByName(name,cpr); }这块步骤比较多,挑重点就是,先从AMS的ProviderMap对象中获取AMS缓存。获得后如果Provider没有launch,则AMS通知其进程install其provider。如果进程不存在,则新孵化一个进程。
@InstallProvider
回到第三步中的installProvider
privateIActivityManager.ContentProviderHolderinstallProvider(Contextcontext, IActivityManager.ContentProviderHolderholder,ProviderInfoinfo, booleannoisy,booleannoReleaseNeeded,booleanstable)可以看到,这个方法里面有6个参数,其中包含ContentProviderHolder、ProviderInfo、noReleaseNeeded,这几个很重要的参数。
ContentProviderHolder:当参数为空的时候,说明缓存为空,也就意味着是进程启动的时候调用发布provider。当缓存不为空的时候,还得做一些处理。
ProviderInfo:包含Provider的一些信息,不能为空。
noReleaseNeeded:为true的时候Provider对于自身进程来说或系统的Provider,是永久install的,也就是不会被destory的。
ContentProviderlocalProvider=null; IContentProviderprovider; if(holder==null||holder.provider==null){ try{ finaljava.lang.ClassLoadercl=c.getClassLoader(); localProvider=(ContentProvider)cl. loadClass(info.name).newInstance(); provider=localProvider.getIContentProvider(); if(provider==null){ returnnull; } localProvider.attachInfo(c,info); }catch(java.lang.Exceptione){ } }else{ provider=holder.provider; }这部分在发布的时候已经说了,缓存holder为null的时候,new一个实例。
IActivityManager.ContentProviderHolderretHolder; synchronized(mProviderMap){ IBinderjBinder=provider.asBinder(); if(localProvider!=null){ ComponentNamecname=newComponentName(info.packageName,info.name); ProviderClientRecordpr=mLocalProvidersByName.get(cname); if(pr!=null){ provider=pr.mProvider; }else{ holder=newIActivityManager.ContentProviderHolder(info); holder.provider=provider; holder.noReleaseNeeded=true; pr=installProviderAuthoritiesLocked(provider,localProvider,holder); mLocalProviders.put(jBinder,pr); mLocalProvidersByName.put(cname,pr); } retHolder=pr.mHolder; }else{ ... }如果localProvider不等于null,则意味着是new一个实例的情况,这时候还是先去获取缓存,没有的话再真正地new一个ContentProviderHolder实例,并把通过installProviderAuthoritiesLocked方法把相关信息存入mProviderMap中,这个就是对应发布Provider提的那个方法。
IActivityManager.ContentProviderHolderretHolder; synchronized(mProviderMap){ ... }else{ ProviderRefCountprc=mProviderRefCountMap.get(jBinder); if(prc!=null){ if(!noReleaseNeeded){ incProviderRefLocked(prc,stable); try{ ActivityManagerNative.getDefault().removeContentProvider( holder.connection,stable); } } }else{ ProviderClientRecordclient=installProviderAuthoritiesLocked( provider,localProvider,holder); if(noReleaseNeeded){ prc=newProviderRefCount(holder,client,1000,1000); }else{ prc=stable ?newProviderRefCount(holder,client,1,0) :newProviderRefCount(holder,client,0,1); } mProviderRefCountMap.put(jBinder,prc); } retHolder=prc.holder; }如果localProvider等于空,也就意味着有holder缓存或者new时候出现的异常。那先从计数map中取缓存,如果缓存不为空(之前有过计数了),这时候如果设置了noReleaseNeeded,那就说明不需要计数。如果noReleaseNeeded为false,则把计数器数据转移到一个新引用上,同时销毁旧的。
如果缓存为空,说明之前没有计数过。那还是先通过installProviderAuthoritiesLocked把信息保存到mProviderMap中。这时候如果noReleaseNeeded为true,把stable和非stable的数据都瞎设置了一个1000,反正用不到。。。否则就相应的+1,并把计数器放入相应的缓存中。最后再把holder返回。
再回到ContentResolver方法中,我们拿到了Provider的binder引用,就可以执行相应的方法了。