企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
**ContentResolver.java::openAssetFileDescriptor** ~~~ public final AssetFileDescriptoropenAssetFileDescriptor(Uri uri, String mode) throws FileNotFoundException { //openAssetFileDescriptor是一个通用函数,它支持三种scheme类型的URI。见 //下文的解释 Stringscheme = uri.getScheme(); if(SCHEME_ANDROID_RESOURCE.equals(scheme)) { if(!"r".equals(mode)) { throw new FileNotFoundException("Can't write resources: " +uri); } //创建资源文件输入流 OpenResourceIdResult r = getResourceId(uri); try{ return r.r.openRawResourceFd(r.id); } ...... }else if (SCHEME_FILE.equals(scheme)) { //创建普通文件输入流 ParcelFileDescriptor pfd = ParcelFileDescriptor.open( new File(uri.getPath()), modeToMode(uri, mode)); return new AssetFileDescriptor(pfd, 0, -1); }else { if ("r".equals(mode)) { //我们重点分析这个函数,用于读取目标ContentProvider的数据 return openTypedAssetFileDescriptor(uri, "*/*", null); } ......//其他模式的支持,请读者学完本章后自行研究这部分内容 } } ~~~ 如以上代码所述,openAssetFileDescriptor是一个通用函数,它支持三种sheme类型的URI。 - SCHEME_ANDROID_RESOURCE:字符串表达为“android.resource”。通过它可以读取APK包(其实就是一个压缩文件)中封装的资源。假设在应用进程的res/raw目录下存放一个test.ogg文件,最终生成的资源id由R.raw.tet来表达,那么如果应用进程想要读取这个资源,创建的URI就是“android.resource://com.package.name/R.raw.test”。 读者不妨试一试。 - SCHEME_FILE:字符串表达为“file”。通过它可以读取普通文件。 - 除上述两种scheme之外的URI:这种资源背后到底对应的是什么数据需要由目标CP来解释。 接下来分析在第三种sheme类型下调用的openTypedAssetFileDescriptor函数。 1. openTypedAssetFileDescriptor函数分析 **ContentResolver.java::openTypedAssetFileDescriptor** ~~~ public final AssetFileDescriptoropenTypedAssetFileDescriptor(Uri uri, String mimeType, Bundle opts) throws FileNotFoundException { //建立和目标CP进程交互的通道。读者还记得此处provider的真实类型吗? //其真实类型是ContentProviderProxy IContentProvider provider = acquireProvider(uri); try { //①调用远端CP的openTypedAssetFile函数,返回值的 //类型是AssetFileDescriptor。此处传递参数的值为:mimeType="*/*" //opts=null AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); ...... //②创建一个ParcelFileDescriptor类型的变量 ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( fd.getParcelFileDescriptor(), provider); provider = null; //③又创建一个AssetFileDescriptor类型的变量作为返回值 return new AssetFileDescriptor(pfd, fd.getStartOffset(), fd.getDeclaredLength()); }...... finally { if (provider != null) { releaseProvider(provider); } } } ~~~ 在以上代码中,阻碍我们思维的依然是新冒出来的类。先应解决它们,然后再分析代码中的关键调用。 2. FileDescriptor家族介绍 本节涉及的类家族图谱如图7-7所示。 :-: ![](http://img.blog.csdn.net/20150803130950821?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-7 FileDescriptor家族图谱 图7-7中的内容比较简单,只需稍作介绍。 - FileDescriptor类是Java的标准类,它是对文件描述符的封装。进程打开的每一个文件都有一个对应的文件描述符。在Native语言开发中,它用一个int型变量来表示。 - 文件描述符作为进程的本地资源,如想越过进程边界将其传递给其他进程,则需借助进程间共享技术。在Android平台上,设计者封装了一个ParcelFileDescriptor类。此类实现了Parcel接口,自然就支持了序列化和反序列化的功能。从图7-7可知,一个ParcelFileDescriptor通过mFileDescritpor指向一个文件描述符。 - AssetFileDescriptor也实现了Parcel接口,其内部通过mFd成员变量指向一个ParcelFileDescriptor对象。从这里可看出,AssetFileDescritpor是对ParcelFileDescriptor类的进一步封装和扩展。实际上,根据SDK文档中对AssetFileDescritpor的描述可知,其作用在于从AssetManager(后续分析资源管理的时候会介绍它)中读取指定的资源数据。 * * * * * **提示**:简单向读者介绍一下与AssetFileDescriptor相关的知识。它用于读取APK包中指定的资源数据。以前面提到的test.ogg为例,如果通过AssetFileDescriptor读取它,那么其mFd成员则指向一个ParcelFileDescriptor对象。且不管这个对象是否跨越了进程边界,它毕竟代表一个文件。假设这个文件是一个APK包,AssetFileDescriptor的mStartOffset变量用于指明test.ogg在这个APK包中的起始位置,比如100字节。而mLength用于指明test.ogg的长度,假设是1000字节。通过上面的介绍可知,该APK文件从100字节到1100字节这一段空间中存储的就是test.ogg的数据。这样,AssetFileDescriptor就能将test.ogg数据从APK包中读取出来了。 * * * * * 下面来看ContentProvider的openTypedAssetFile函数。