Android实战技巧:使用原始资源文件

来源:互联网 发布:sql express安装 编辑:程序博客网 时间:2024/05/21 07:59

背景知识介绍

与其他平台的应用程序一样,Android中的应用程序也会使用各种资源,比如图片,字串等,会把它们放入源码的相应文件夹下面,如/res/drawable, /res/xml, /res/values/, /res/raw, /res/layout和/assets。Android也支持并鼓励开发者把UI相关的布局和元素,用XML资源来实现。总结起来,Android中支持的资源有:
  • 颜色值                 /res/values               以resources为Root的XML文件,定义形式为<color name>value</color>
  • 字串                    /res/values                以resources为Root的XML文件<string name>value</string>
  • 图片                    /res/drawable            直接放入,支持9 Patch可自由拉伸
  • 图片的颜色        /res/values               以resources为Root的XML文件,定义形式为<drawable name>value</drawable>
  • 单位资源            /res/values               以resources为Root的XML文件<dimen name>value</dimen>
  • 菜单                    /res/menu                以menuo为root的XML文件
  • 布局                    /res/layout                 这个就是GUI的布局和元素
  • 风格和主题        /res/values                以resources为Root的XML文件<style name>value</style>
  • 动画                    /res/anim                 有二种:一个是帧动画(frame animation),也就是连续变换图片以animation-list为Root的XML文件;另外一种就是补间动画(tweened animation),它对应于API中的Animation和AnimationSet,有translate、scale、rotate、alpha四种,以set为root来定义,这个set就相当于AnimationSet
再说下目录:
  • /res/anim                  用于存放动画
  • /res/drawable          存放图片,或等同于图片的资源如shape,或selector
  • /res/menu                 存放Menu
  • /res/values               存放修饰性资源:字串,颜色,单位,风格和主题
  • /res/layout                存放UI布局和元素
  • /res/raw                    存放运行时想使用的原始文件
  • /assets                     存放运行时想使用的原始文件
除了原始文件目录/res/raw和/assets以外,其他的资源在编译的时候都会被第三方软件aapt进行处理,一个是把图片和XML文件进行处理,例如把XML编译成为二进制形式;另外处理的目的就是生成R.java文件,这个文件是访问资源时必须要用到的。
/res目录下面的所有文件都会映射到R.java文件中,以整数Id的形式被标识,相同类型的资源被一个内部类来封装,一个R.java的文件类似于这样:

[java] view plain copy
 print?
  1. /* AUTO-GENERATED FILE.  DO NOT MODIFY. 
  2.  * 
  3.  * This class was automatically generated by the 
  4.  * aapt tool from the resource data it found.  It 
  5.  * should not be modified by hand. 
  6.  */  
  7.   
  8. package com.android.explorer;  
  9.   
  10. public final class R {  
  11.     public static final class attr {  
  12.     }  
  13.     public static final class drawable {  
  14.         public static final int icon=0x7f020000;  
  15.     }  
  16.     public static final class id {  
  17.         public static final int action=0x7f060004;  
  18.         public static final int description_panel=0x7f060001;  
  19.         public static final int fileinfo=0x7f060003;  
  20.         public static final int filename=0x7f060002;  
  21.         public static final int linearlayout_test_1=0x7f060005;  
  22.         public static final int linearlayout_test_2=0x7f060006;  
  23.         public static final int linearlayout_test_3=0x7f060007;  
  24.         public static final int thumbnail=0x7f060000;  
  25.     }  
  26.     public static final class layout {  
  27.         public static final int fileant_list_item=0x7f030000;  
  28.         public static final int linearlayout_test=0x7f030001;  
  29.     }  
  30.     public static final class raw {  
  31.         public static final int androidmanifest=0x7f040000;  
  32.     }  
  33.     public static final class string {  
  34.         public static final int app_name=0x7f050001;  
  35.         public static final int hello=0x7f050000;  
  36.     }  
  37. }  
从这个R.java就可看出在/res中定义或提供资源时的注意事项:
1. 同一个类型,或同一文件夹下面的资源不可以使用相同的文件名,也就是说不能用文件扩展名来区别不同的文件,因为R.java中只保留资源的文件名而不管扩展名,所以如果有二个图片一个是icon.png另一个是icon.jpg,那么在R.java中只会有一个R.drawable.icon。另外一个则会无法访问到。
2. 资源文件的名字必须符合Java变量的命名规则,且不能有大写,只能是'[a-z][0-9]._',否则会有编译错误,因为R.java中的变量Id要与资源中的文件一一对应,也就是说用资源文件名来作为Id的变量名,所以一定要符合Java变量的命名规则,另外它还不能有大写。
3. 除了SDK支持的folder外,不能再有子Folder,虽不会有编译错误,但是子Folder会被完全忽略,如在/res/layout下在建一个子Folder activity(/res/layout/acitivity/, 那么你在生成的R.java中是看不到activity和其内的内容的。
4. 对于资源文件的大小有限制,最好不要让单个文件大于1M,这是SDK文档说明的限制,但具体的我没有进行试验(据说2.2版本以后的可支持到10M,不知道是真的还是假的)
5. 所有/res下面的资源都能通过Resources()并提供Id来访问。

使用原始资源

对于大多数资源在编译时会对文件内容进行特殊处理,以方便Apk在运行时访问。 如果想要运行时使用未经处理的原始资源,可以把资源文件放在/res/raw和/assets目录下面,这二个目录的主要区别在于:
1. /res/raw中的文件会被映射到R.java中
虽然/res/raw中的文件不会被aapt处理成为二进制,但是它的文件还是被映射到R.java中,以方便以资源Id形式来访问
2. 子目录结构
如上面所述,/res/raw中虽可以有子目录,但是在程序运行时是无法访问到的,因为/res下面的所有非法子目录在R.java中都是看不到的。而且这些子目录和文件都不会被编译进入Apk中,解压Apk文件后也看不到/res/raw下面去找了。
而/assets是允许有子目录的,并且完全可以访问到,并且会被打包进Apk,解压Apk后,这些文件仍然存在并且与源码包中的一样。
3. 访问方式
/res/raw下面的文件(子文件夹是访问不到的了)的访问方式是通过Resources,并且必须提供资源的Id
[java] view plain copy
 print?
  1. InputStream in = Context.getResources().openRawResource(R.id.filename);  
所以为什么子文件夹无法访问,因为没有Id啊。
而/assets则要通过AssetManager来访问。下面着重讲解如何访问/assets下面的资源文件。

通过AssetManager来访问/assets下面的原始资源文件

1. 文件的读取方式
用AssetManager.open(String filename)来打开一个文件,这是一组重载方法,还有其他参数可以设置打开模式等,可以参考文档
这里的filename是相对于/assets的路径,比如:

[java] view plain copy
 print?
  1. InputStream in = mAssetManager.open("hello.txt"); // '/assets/hello.txt'  
  2. InputStream in2 = mAssetManager.open("config/ui.txt"); // '/assets/config/ui.txt'  
2. 文件夹处理 --- 如何遍历/assets
可以看到如果想要访问/assets下面的文件,必须要知道文件名和相对于/assets的路径。所以,如果你不预先知道其下面有什么的时候又该如何处理呢?那就需要列出它下面所有的文件,然后再选取我们需要的,所以新的问题来了,如何列出/assets下面所有的文件呢?
AssetManager提供了一个列出/assets下某个路径下面的方法:

public finalString[]list(String path)

Since: API Level 1

Return a String array of all the assets at the given path.

Parameters
pathA relative path within the assets, i.e., "docs/home.html".
Returns
  • String[] Array of strings, one for each asset. These file names are relative to 'path'. You can open the file by concatenating 'path' and a name in the returned string (via File) and passing that to open().
其实这个文档写的有问题,list()是列出一个文件夹下面的文件,所以应该传入一个文件夹路径而非文档中的"docs/home.html"。
还有一个最大的问题就是如何列出根目录/assets下面的内容,因为只有知道了根目录下面的东西,才能去相对的子目录去找东西,所以这是个必须最先解决的问题。
其实文档没有说的太明白这个方法到底如何使用,也就是说这个String参数到底如何传。猜想着根目录为/assets,所以尝试了以下:

[java] view plain copy
 print?
  1. mAssetManager.list(".");  // returns array size is 0  
  2. mAssetManager.list("/");  // returns [AndroidManifest.xml, META-INF, assets, classes.dex, res, resources.arsc] // don't worry, u can see these files though, no way to access them  
  3. mAssetManager.list("/assets");  // returns array size is 0  
  4. //Google了一下,找到了正解:  
  5. mAssetManager.list("");  // returns stuff in /assets  
然后再根据所列出的子项去递归遍历子文件,直到找到所有的文件为止。

常见的问题

1. 资源文件只能以InputStream方式来获取
如果想操作文件怎么办,如果想要用文件Uri怎么办。光靠API当然不行,它只给你InputStream,也就是说它是只读的。可行的办法就是读取文件然后写入一个临时文件中,再对临时文件进行想要的文件操作。可以在内部存储或外部存储上面用Context提供的接口来创建文件,详细的请参考这里。Java牛人可能想要用Java本身的能力:

[java] view plain copy
 print?
  1. File File.createTempFile(String prefix, String suffix);  
  2. File File.createTempFile(String prefix, String suffix, File path);  
这也是可以的,但要考虑Android系统的特性,也就是说所写的路径是否有权限。比如对于第一个方法,用的是"java.io.tmpdir"这个在Android当中就是"/sdcard",所以当没有SD卡时这个方法必抛异常。
2. 所有资源文件都是只读的,运行时无法更改
因为,程序运行时是把Apk动态解析加载到内存中,也就是说,Apk是不会有变化的,它是无法被改变的(至于逆向工程来修改那是另外一回事,请参考这里)。
3. 所有的资源文件夹/res和/assets也都是只读的,不可写入

如上面所说,Apk是在编译后是无法再改变的了。

实例

下面是一个实例,可以递归式的遍历/assets下面所有的文件夹和文件

[java] view plain copy
 print?
  1. package com.android.explorer;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.BufferedOutputStream;  
  5. import java.io.File;  
  6. import java.io.FileNotFoundException;  
  7. import java.io.FileOutputStream;  
  8. import java.io.IOException;  
  9. import java.io.InputStream;  
  10.   
  11. import android.app.ListActivity;  
  12. import android.content.Context;  
  13. import android.content.Intent;  
  14. import android.content.res.AssetManager;  
  15. import android.net.Uri;  
  16. import android.os.Bundle;  
  17. import android.os.Environment;  
  18. import android.text.TextUtils;  
  19. import android.util.Log;  
  20. import android.view.LayoutInflater;  
  21. import android.view.View;  
  22. import android.view.ViewGroup;  
  23. import android.webkit.MimeTypeMap;  
  24. import android.widget.BaseAdapter;  
  25. import android.widget.ImageButton;  
  26. import android.widget.LinearLayout;  
  27. import android.widget.TextView;  
  28.   
  29. /* 
  30.  * Explore all stuff in /assets and perform actions specified by users. 
  31.  */  
  32. public class FileAntActivity extends ListActivity {  
  33.     private static final String TAG = "FileAntActivity";  
  34.     private AssetManager mAssetManager;  
  35.     private static final String EXTRA_CURRENT_DIRECTORY = "current_directory";  
  36.     private static final String EXTRA_PARENT = "parent_directory";  
  37.     public static final String FILEANT_VIEW = "com.android.fileant.VIEW";  
  38.   
  39.     @Override  
  40.     public void onCreate(Bundle savedInstanceState) {  
  41.         super.onCreate(savedInstanceState);  
  42.         Intent intent = getIntent();  
  43.         String current = null;  
  44.         String parent = null;  
  45.         if (intent != null && intent.hasExtra(EXTRA_CURRENT_DIRECTORY)) {  
  46.             current = intent.getStringExtra(EXTRA_CURRENT_DIRECTORY);  
  47.         }  
  48.         if (current == null) {  
  49.             current = "";  
  50.         }  
  51.         if (intent != null && intent.hasExtra(EXTRA_PARENT)) {  
  52.             parent = intent.getStringExtra(EXTRA_PARENT);  
  53.         }  
  54.         if (parent == null) {  
  55.             parent = "";  
  56.         }  
  57.         mAssetManager = getAssets();  
  58.         if (TextUtils.isEmpty(parent)) {  
  59.             setTitle("/assets");  
  60.         } else {  
  61.             setTitle(parent);  
  62.         }  
  63.         try {  
  64.             // List all the stuff in /assets  
  65.             if (!TextUtils.isEmpty(parent)) {  
  66.                 current = parent + File.separator + current;  
  67.             }  
  68.             Log.e(TAG, "current: '" + current + "'");  
  69.             String[] stuff = mAssetManager.list(current);  
  70.             setListAdapter(new FileAntAdapter(this, stuff, current));  
  71.         } catch (IOException e) {  
  72.             e.printStackTrace();  
  73.         }  
  74.     }  
  75.       
  76.     private class FileAntAdapter extends BaseAdapter {  
  77.         private Context mContext;  
  78.         private String[] mEntries;  
  79.         private String mParentDirectory;  
  80.           
  81.         public FileAntAdapter(Context context, String[] data, String parent) {  
  82.             mContext = context;  
  83.             this.mEntries = data;  
  84.             mParentDirectory = parent;  
  85.         }  
  86.           
  87.         public int getCount() {  
  88.             return mEntries.length;  
  89.         }  
  90.   
  91.         public Object getItem(int position) {  
  92.             return mEntries[position];  
  93.         }  
  94.   
  95.         public long getItemId(int position) {  
  96.             return (long) position;  
  97.         }  
  98.   
  99.         public View getView(final int position, View item, ViewGroup parent) {  
  100.             LayoutInflater factory = LayoutInflater.from(mContext);  
  101.             if (item == null) {  
  102.                 item = factory.inflate(R.layout.fileant_list_item, null);  
  103.                 TextView filename = (TextView) item.findViewById(R.id.filename);  
  104.                 TextView fileinfo = (TextView) item.findViewById(R.id.fileinfo);  
  105.                 ImageButton action = (ImageButton) item.findViewById(R.id.action);  
  106.                 final String entry = mEntries[position];  
  107.                 filename.setText(entry);  
  108.                 boolean isDir = isDirectory(entry);  
  109.                 if (isDir) {  
  110.                     fileinfo.setText("Click to view folder");  
  111.                     action.setVisibility(View.GONE);  
  112.                     item.setClickable(true);  
  113.                     item.setOnClickListener(new View.OnClickListener() {  
  114.                         public void onClick(View view) {  
  115.                             Intent intent = new Intent(FILEANT_VIEW);  
  116.                             intent.putExtra(EXTRA_CURRENT_DIRECTORY, entry);  
  117.                             intent.putExtra(EXTRA_PARENT, mParentDirectory);  
  118.                             startActivity(intent);  
  119.                         }  
  120.                     });  
  121.                 } else {  
  122.                     final String type =   
  123.                         MimeTypeMap.getSingleton().getMimeTypeFromExtension(getExtension(entry));  
  124.                     fileinfo.setText(type);  
  125.                     item.setClickable(false);  
  126.                     action.setOnClickListener(new View.OnClickListener() {  
  127.                         public void onClick(View view) {  
  128.                              String filepath = entry;  
  129.                              if (!TextUtils.isEmpty(mParentDirectory)) {  
  130.                                  filepath = mParentDirectory + File.separator + filepath;  
  131.                              }  
  132.                              BufferedInputStream in = new BufferedInputStream(mManager.open(filepath));  
  133.                              // Do whatever you like with this input stream  
  134.                         }  
  135.                     });  
  136.                 }  
  137.             }  
  138.             return item;  
  139.         }  
  140.      }  
  141.       
  142.     /** 
  143.      * Test Whether an entry is a file or directory based on the rule: 
  144.      * File: has extension *.*, or starts with ".", which is a hidden files in Unix/Linux, 
  145.      * otherwise, it is a directory 
  146.      * @param filename 
  147.      * @return 
  148.      */  
  149.     private boolean isDirectory(String filename) {  
  150.         return !(filename.startsWith(".") || (filename.lastIndexOf(".") != -1));  
  151.     }  
  152.       
  153.     private String getExtension(String filename) {  
  154.         int index = filename.lastIndexOf(".");  
  155.         if (index == -1) {  
  156.             return "";  
  157.         }  
  158.         return filename.substring(index + 1, filename.length()).toLowerCase();  
  159.     }  
  160. }  
原创粉丝点击