轻读一下 Android 应用开发中的 assets 目录
<p>2019-08-07</p><p>关键字:APK预置文件、预置配置文件、res,raw与assets的区别</p>
<hr>
<p> </p>
<p>在Android的应用开发中,难免会遇到外部文件的预置需求。例如图像、音视频、配置文件、字体等等。对于图像,我们很容易会想到将它们存放在 res/drawable 目录或者是 res/mipmap 目录下。但对于其它类型的文件,就得另寻它法了。</p>
<p> </p>
<p>比较常见的可以保存任意类型文件的地方主要有两个:</p>
<p style="margin-left: 30px">1、res/raw 目录;</p>
<p style="margin-left: 30px">2、assets 目录。</p>
<p> </p>
<p>drawable/mipmap、raw、assets 三者都可以用来存储一些外部资源文件,那它们之间各自有什么优缺点呢?</p>
<p> </p>
<p>首先,在 drawable 或 mipmap 下只能保存图像文件或图像描述文件,但在这两个目录下保存的图像文件在编译时会建立一张“索引表”。这个索引信息会被统一保存在一个名称为 R.java 的文件中。在程序的任何地方都可以直接通过 R.drawable.xxx 的形式来使用图片资源。</p>
<p> </p>
<p>res/raw 目录算是一个比较特殊的资源文件目录。它被设计用于保存一些二进制文件,即在这个目录下的所有文件都会被记录到“索引表”中,但是编译系统不会去动里面的文件。raw 目录下的文件放进去时是什么样的,编译成 APK 以后还是什么样。这个目录比较适合保存一些音视频等二进制文件。</p>
<p> </p>
<p>res 目录下的资源文件夹,不管是 raw 还是 drawable 或 mipmap,都不能自由地设计子目录层级关系。不管你有多少文件,都只能放在同一级目录中。</p>
<p> </p>
<p>assets 目录是一个非常自由的目录。它就像是Android应用中的“三不管”地带,不会为里面的文件建立索引、不会限制目录层级关系、不会处理里面的文件。如果你想更好地管理自己的外部资源文件,建议使用 assets 目录。</p>
<p> </p>
<p>本篇文章,我们重点来讲解一下 Android 的 assets 目录。</p>
<p> </p>
<h2>1、assets 简介</h2>
<p> </p>
<p>assets 在 APK 工程中就是一个普通的目录而已。不管是 Eclipse 创建的工程还是 Android Studio 创建的工程,在它的工程根目录下都可以发现(或者自己创建)一个 assets 目录,如下图所示</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190806205500901-1118638066.png"></p>
<p>assets 目录是专门用于保存各种外部文件的。常见的有:图像、音视频、配置文件、字体、自带数据库等。之所以说它适合用来管理这些文件,是因为应用程序在编译时不会去处理这个目录下的文件,但是却会将它们打包进 APK 中。而其它你随便创建的目录在编译时就会被直接忽略掉。同时,你可以在 assets 目录内任意创建目录层级关系,这对于有大量外部文件需要集成的应用来说,就能很方便地分类管理了。</p>
<p> </p>
<p>在 APK 开发中,有一种管理配置信息的做法比较常见:直接将配置信息文件放入 assets 目录中管理,程序首次运行时将这里面的配置信息拷贝到外部的可操作的目录下,后续程序的运行均靠这份保存在外部的配置信息为准,assets 中的信息仅作为原始配置信息的备份。</p>
<p> </p>
<p>但是,assets 目录在使用上也还是有一点小缺憾的。</p>
<p> </p>
<p>assets 目录内的文件在程序打包发布以后就是只读的。就是你只能读取那里面的文件,而无法修改或增加文件。这条特性其实也可以理解,因为应用程序一旦打包发布了,它就应该是只读的。而 assets 目录又是直接保存在 APK 内部的,所以它自然也不能修改或增加内容了。实在要想增加内容,通过 Database 或者 SharedPreferences 往 /data/data 目录下保存就好了嘛。再或者这两者不能满足你的要求,你也可以直接将它们保存在 sdcard 下面嘛。反正现在市面上的 APK 在 sdcard 里创建自己的数据文件夹的可不少。</p>
<p> </p>
<h2>2、assets 开发</h2>
<p> </p>
<p>关于读取 assets 目录下的文件,Android 提供了一个 <strong>android.content.res.AssetManager</strong> 类来实现。这个类的签名体如下图所示</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190806212715693-208638066.png"></p>
<p>这里我们需要关注的方法有以下几个:</p>
<p style="margin-left: 30px">1、构造方法</p>
<p style="margin-left: 30px">2、open() 方法</p>
<p style="margin-left: 30px">3、openFd() 方法</p>
<p style="margin-left: 30px">4、openNonAssetFd() 方法</p>
<p style="margin-left: 30px">5、openXmlResourceParser() 方法</p>
<p> </p>
<p><span style="font-size: 18px; font-family: "Microsoft YaHei""><strong>构造方法</strong></span></p>
<p><span style="text-decoration: line-through">这里我们注意到 AssetManager 的构造方法的权限是 default,这意味着我们无法在我们的程序中通过 new 的方式来实例化它</span>(在Android4.4 中它是 public 修饰的)。通常,我们可以通过两种方式来得到 AssetManager 的实例:1、<strong>通过 Context 实例的 getAssets() 方法</strong>;2、<strong>通过 Resources 实例的 getAssets() 方法</strong>。</p>
<p> </p>
<p><span style="font-size: 18px"><strong><span style="font-family: arial, helvetica, sans-serif">open(string) & open(string,int)</span></strong></span></p>
<p>这两个方法的作用是一样的。都是将 assets 目录下的某个文件封装成 InputStream 的形式以供使用。说白了就是让我们读文件用的。</p>
<p> </p>
<p>两个方法中的 string 参数都指的是“文件名”,其实应该说是文件的相对路径更合适,它需要的是某个文件在 assets 目录下的相对路径。例如:dir1/file1.png , dir2/dir3/dir4/file2.avi。</p>
<p> </p>
<p>第二个方法中还有一个 int 型参数,它是“访问模式”,就是将 assets 目录下的文件以什么模式来打开的意思。它一共有以下 4 种模式可供选择:</p>
<p style="margin-left: 30px">1、ACCESS_UNKNOW</p>
<p style="margin-left: 60px">无模式。其代表的值是 0。</p>
<p style="margin-left: 30px">2、ACCESS_RANDOM</p>
<p style="margin-left: 60px">这个不应该翻译成随机访问模式,无序访问模式会更适合一点。这种模式下文件的访问只会打开其中一段内容,然后再根据你的需要向流的前方或后方移动读取指针。其代表的值是 1。</p>
<p style="margin-left: 30px">3、ACCESS_STREAMING</p>
<p style="margin-left: 60px">顺序读取模式。文件将会被从头部打开,然后按顺序向后面移动读取数据。其代表的值是 2。</p>
<p style="margin-left: 30px">4、ACCESS_BUFFER </p>
<p style="margin-left: 60px">缓存读取模式。读取时会将整个文件直接读取到内存中,这种模式适合小文件的读取。其代表的值是 3。</p>
<p> </p>
<p>在 open(string) 中,它使用的文件读取模式是 ACCESS_STREAMING 模式。</p>
<p> </p>
<p><span style="font-family: arial, helvetica, sans-serif"><strong><span style="font-size: 18px">openFd(string)</span></strong></span></p>
<p>将 assets 目录中的文件以 FileDescriptor 的形式打开,返回一个 AssetFileDescriptor 实例。</p>
<p> </p>
<p><span style="font-family: arial, helvetica, sans-serif"><strong><span style="font-size: 18px">openNonAssetFd(string) & openNonAssetFd(int,string)</span></strong></span></p>
<p>这个其实和上面的 openFd() 是一样的。只不过它是跳出了 assets 目录的范围限定,它是站在工程根目录的视角来打开文件的 FileDescriptor 的。换句话说,它允许打开 APK 中任意位置的文件的 AssetFileDescriptor 实例。</p>
<p> </p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 18px"><strong>openXmlResourceParser(int,string)</strong></span></p>
<p>打开 assets 目录下的 xml 形式的文件,直接返回 XmlResourceParser 实例。其实就是官方替我们做了从 InputStream 到 XML 解析器之间的转换,有助于增加一些开发效率而已。</p>
<p> </p>
<p> </p>
<p>那接下来,我们通过实例来演示一下 assets 目录下的文件的读取方法。首先第一个是直接读取最普通的文件的方法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
InputStream is </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.getAssets().open(<span style="color: rgba(255, 0, 0, 1)"><strong>"moutain.png"</strong></span><span style="color: rgba(0, 0, 0, 1)">);
Log.d(</span>"type1", "File available:" +<span style="color: rgba(0, 0, 0, 1)"> is.available());
InputStream is2 </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.getResources().getAssets().open("river.png"<span style="color: rgba(0, 0, 0, 1)">);
Log.d(</span>"type1", "File available2:" +<span style="color: rgba(0, 0, 0, 1)"> is2.available());
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
e.printStackTrace();
}</span></pre>
</div>
<p>执行的结果如下所示:</p>
<div class="cnblogs_code">
<pre> D/type1: File available:3<span style="color: rgba(0, 0, 0, 1)">
D</span>/type1: File available2:11416</pre>
</div>
<p>当然,最后一定不要忘记将用完的 InputStream 资源关掉!!!</p>
<p> </p>
<p>第二个是读取自定义目录层级的文件的方法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
InputStream is </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.getAssets().open(<strong><span style="color: rgba(255, 0, 0, 1)">"sences/nature/forest.png"</span></strong><span style="color: rgba(0, 0, 0, 1)">);
Log.d(</span>"type2", "File available:" +<span style="color: rgba(0, 0, 0, 1)"> is.available());
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
e.printStackTrace();
}</span></pre>
</div>
<p>执行的结果如下所示:</p>
<div class="cnblogs_code">
<pre> D/type2: File available:32114</pre>
</div>
<p>同样,不要忘记调用 InputStream 的 close() 方法哦。</p>
<p> </p>
<p>第三个是以文件描述符形式读取的方法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
AssetFileDescriptor afd </span>= <span style="color: rgba(0, 0, 255, 1)">this</span>.getAssets().openFd("river.png"<span style="color: rgba(0, 0, 0, 1)">);
Log.d(</span>"type3", "File available:" +<span style="color: rgba(0, 0, 0, 1)"> afd.getLength());
InputStream is </span>=<span style="color: rgba(0, 0, 0, 1)"> afd.createInputStream();
Bitmap bm </span>=<span style="color: rgba(0, 0, 0, 1)"> BitmapFactory.decodeStream(is);
is.close();
afd.close();
iv01.setImageBitmap(bm);
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
e.printStackTrace();
}</span></pre>
</div>
<p>这里将一张图片以 AssetFileDescriptor 的形式读取出来,并转换成 Bitmap 显示在 ImageView 上,这段代码的执行结果如下图所示</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807105212688-941879510.png"></p>
<p> </p>
<p> </p>
<h2>3、assets 深度剖析</h2>
<p> </p>
<p>这一小节我们来探究一下 AssetManager 的“前世今生”。</p>
<p> </p>
<p>首先来看看 AssetManager 实例是怎么来的。通过前面的介绍,我们已经知道了可以直接通过 Activity 实例来调用 getAssets() 方法以取得 AssetManager 实例,或者是通过 Activity 实例里的 getResources() 得到 Resources 的实例以后再调用 getAssets() 来得到 AssetManager 实例。那我们应该都知道,所谓的 Activity 里提供的 getAssets() 方法或者是 getResources() 方法,都是被定义在 android.app.Context 类中的方法,如下图所示</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807110659188-1023965163.png"></p>
<p>但是它们在 Context 类中都是抽象方法。那这两个方法的具体实现在哪呢?在 android.app.ContextImpl 类上。如下图所示</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807110934506-1567156863.png"></p>
<p>通过上图,我们很意外地发现,原来通过 Context 的 getAssets() 和通过 Resources 的 getAssets() 走的路线竟然是完全一样的!!!</p>
<p> </p>
<p>好吧,来不及在这感慨了,我们得接着看看 mResources 又是怎么来的。</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807120104446-1758485033.png"></p>
<p>这个 mResource 是通过 packageInfo 得到的。而 packageInfo 又是在 ContextImpl 构造的时候传进来的。</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807120223315-705825084.png"></p>
<p>我们这里且不管是谁 new 了 ContextImpl 的对象,我们只需要知道这个 packageInfo 是 LoadedApk 类的实例就好了。我们下面直接去看看 LoadedApk 类里的 getResources() 方法。</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807120552123-2115708361.png"></p>
<p>好嘛,又跳到 ActivityThread 类里去了,跟过去</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807120629681-1641605234.png"></p>
<p>牛皮,又跳到 ResourcesManager 里去了,没办法,只能再跟。不过在跟踪之前这里必须说一下,这个方法中的第一个参数 resDir 指的是这个 APK 在文件系统下的路径,例如,你是通过 install 方式安装的,它就会在 /data/app 下,你是系统应用它就会在 /system/app 或 /system/priv-app 下。总之这个参数的作用就是把当前正在运行的 APK 的地址传进去,因为后面要去解析它里面的资源的。</p>
<p> </p>
<p>ResourcesManager 里的 getTopLevelResources 方法比较长,这里只贴需要我们关注的代码。我们在这个方法里终于发现有人实例化了 AssetManager 类。</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807120821210-1151068173.png"></p>
<p>同时下面一点的地方还发现了这样一段代码</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807134507392-454342741.png"></p>
<p>将刚创建出来的 AssetManager 实例传给 Resources 类实例化去了。传它的作用想也不用想就知道是将这个 assets 对象保存起来,以便后面调用 Resources 类的 getAssets() 方法时好返回了。而事实上,Resources 类中的 getAssets() 方法所干的事也确实就是简单地返回这个 assets 对象的引用而已。下图是 Resources 类中的代码实现</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1146198/201908/1146198-20190807134734697-281514700.png"></p>
<p> </p>
<p>前面我们简单地了解了 AssetManager 的由来过程。简单总结一下就是:</p>
<p style="margin-left: 30px">1、每个 APK 在启动时都会实例化属于自己的 Context 对象;</p>
<p style="margin-left: 30px">2、APK 里的资源文件统一具化为 Resources 实例,并由 ResourceManager 管理;</p>
<p style="margin-left: 30px">3、assets 目录统一由一个 AssetManager 实例管理;</p>
<p style="margin-left: 30px">4、AssetManager 实例在 APK 启动时创建;</p>
<p> </p>
<p> </p>
<p>上面是 AssetManager 在应用层的流程。当然其实 AssetManager 还有一个在 Java 以下的流程,这个流程挺复杂的,我这边也不是很有时间,而且感觉了解的太过深入的价值不是很高,就不再继续分析了。不过不排除以后我有兴趣时会继续跟踪下去。</p>
<p> </p>
<hr>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
+++<br><br>
来源:https://www.cnblogs.com/chorm590/p/11308107.html
頁:
[1]