Android 原始资源文件的使用详解

背景知识介绍
与其他平台的应用程序一样,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的文件类似于这样:


代码如下:

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */
package com.android.explorer;
public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int action=0x7f060004;
        public static final int description_panel=0x7f060001;
        public static final int fileinfo=0x7f060003;
        public static final int filename=0x7f060002;
        public static final int linearlayout_test_1=0x7f060005;
        public static final int linearlayout_test_2=0x7f060006;
        public static final int linearlayout_test_3=0x7f060007;
        public static final int thumbnail=0x7f060000;
    }
    public static final class layout {
        public static final int fileant_list_item=0x7f030000;
        public static final int linearlayout_test=0x7f030001;
    }
    public static final class raw {
        public static final int androidmanifest=0x7f040000;
    }
    public static final class string {
        public static final int app_name=0x7f050001;
        public static final int hello=0x7f050000;
    }
}

从这个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


代码如下:

InputStream in = Context.getResources().openRawResource(R.id.filename);

所以为什么子文件夹无法访问,因为没有Id啊。
而/assets则要通过AssetManager来访问。下面着重讲解如何访问/assets下面的资源文件。
通过AssetManager来访问/assets下面的原始资源文件
1. 文件的读取方式
用AssetManager.open(String filename)来打开一个文件,这是一组重载方法,还有其他参数可以设置打开模式等,可以参考文档
这里的filename是相对于/assets的路径,比如:


代码如下:

InputStream in = mAssetManager.open("hello.txt"); // '/assets/hello.txt'
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
path  A 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,所以尝试了以下:


代码如下:

mAssetManager.list(".");  // returns array size is 0
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
mAssetManager.list("/assets");  // returns array size is 0
//Google了一下,找到了正解:
mAssetManager.list("");  // returns stuff in /assets

然后再根据所列出的子项去递归遍历子文件,直到找到所有的文件为止。
常见的问题
1. 资源文件只能以InputStream方式来获取
如果想操作文件怎么办,如果想要用文件Uri怎么办。光靠API当然不行,它只给你InputStream,也就是说它是只读的。可行的办法就是读取文件然后写入一个临时文件中,再对临时文件进行想要的文件操作。可以在内部存储或外部存储上面用Context提供的接口来创建文件,详细的请参考<Android开发笔记之: 数据存储方式详解>。Java牛人可能想要用Java本身的能力:


代码如下:

File File.createTempFile(String prefix, String suffix); 
File File.createTempFile(String prefix, String suffix, File path);

这也是可以的,但要考虑Android系统的特性,也就是说所写的路径是否有权限。比如对于第一个方法,用的是"java.io.tmpdir"这个在Android当中就是"/sdcard",所以当没有SD卡时这个方法必抛异常。
2. 所有资源文件都是只读的,运行时无法更改
因为,程序运行时是把Apk动态解析加载到内存中,也就是说,Apk是不会有变化的,它是无法被改变的。
3. 所有的资源文件夹/res和/assets也都是只读的,不可写入
如上面所说,Apk是在编译后是无法再改变的了。
实例
下面是一个实例,可以递归式的遍历/assets下面所有的文件夹和文件


代码如下:

package com.android.explorer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
/*
 * Explore all stuff in /assets and perform actions specified by users.
 */
public class FileAntActivity extends ListActivity {
    private static final String TAG = "FileAntActivity";
    private AssetManager mAssetManager;
    private static final String EXTRA_CURRENT_DIRECTORY = "current_directory";
    private static final String EXTRA_PARENT = "parent_directory";
    public static final String FILEANT_VIEW = "com.android.fileant.VIEW";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        String current = null;
        String parent = null;
        if (intent != null && intent.hasExtra(EXTRA_CURRENT_DIRECTORY)) {
            current = intent.getStringExtra(EXTRA_CURRENT_DIRECTORY);
        }
        if (current == null) {
            current = "";
        }
        if (intent != null && intent.hasExtra(EXTRA_PARENT)) {
            parent = intent.getStringExtra(EXTRA_PARENT);
        }
        if (parent == null) {
            parent = "";
        }
        mAssetManager = getAssets();
        if (TextUtils.isEmpty(parent)) {
            setTitle("/assets");
        } else {
            setTitle(parent);
        }
        try {
            // List all the stuff in /assets
            if (!TextUtils.isEmpty(parent)) {
                current = parent + File.separator + current;
            }
            Log.e(TAG, "current: '" + current + "'");
            String[] stuff = mAssetManager.list(current);
            setListAdapter(new FileAntAdapter(this, stuff, current));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

private class FileAntAdapter extends BaseAdapter {
        private Context mContext;
        private String[] mEntries;
        private String mParentDirectory;

public FileAntAdapter(Context context, String[] data, String parent) {
            mContext = context;
            this.mEntries = data;
            mParentDirectory = parent;
        }

public int getCount() {
            return mEntries.length;
        }
        public Object getItem(int position) {
            return mEntries[position];
        }
        public long getItemId(int position) {
            return (long) position;
        }
        public View getView(final int position, View item, ViewGroup parent) {
            LayoutInflater factory = LayoutInflater.from(mContext);
            if (item == null) {
                item = factory.inflate(R.layout.fileant_list_item, null);
                TextView filename = (TextView) item.findViewById(R.id.filename);
                TextView fileinfo = (TextView) item.findViewById(R.id.fileinfo);
                ImageButton action = (ImageButton) item.findViewById(R.id.action);
                final String entry = mEntries[position];
                filename.setText(entry);
                boolean isDir = isDirectory(entry);
                if (isDir) {
                    fileinfo.setText("Click to view folder");
                    action.setVisibility(View.GONE);
                    item.setClickable(true);
                    item.setOnClickListener(new View.OnClickListener() {
                        public void onClick(View view) {
                            Intent intent = new Intent(FILEANT_VIEW);
                            intent.putExtra(EXTRA_CURRENT_DIRECTORY, entry);
                            intent.putExtra(EXTRA_PARENT, mParentDirectory);
                            startActivity(intent);
                        }
                    });
                } else {
                    final String type =
                        MimeTypeMap.getSingleton().getMimeTypeFromExtension(getExtension(entry));
                    fileinfo.setText(type);
                    item.setClickable(false);
                    action.setOnClickListener(new View.OnClickListener() {
                        public void onClick(View view) {
                             String filepath = entry;
                             if (!TextUtils.isEmpty(mParentDirectory)) {
                                 filepath = mParentDirectory + File.separator + filepath;
                             }
                             BufferedInputStream in = new BufferedInputStream(mManager.open(filepath));
                             // Do whatever you like with this input stream
                        }
                    });
                }
            }
            return item;
        }
     }

/**
     * Test Whether an entry is a file or directory based on the rule:
     * File: has extension *.*, or starts with ".", which is a hidden files in Unix/Linux,
     * otherwise, it is a directory
     * @param filename
     * @return
     */
    private boolean isDirectory(String filename) {
        return !(filename.startsWith(".") || (filename.lastIndexOf(".") != -1));
    }

private String getExtension(String filename) {
        int index = filename.lastIndexOf(".");
        if (index == -1) {
            return "";
        }
        return filename.substring(index + 1, filename.length()).toLowerCase();
    }
}

(0)

相关推荐

  • Android字符串资源文件format方法使用实例

    很多时候我们感性Google在设计Android时遵守了大量MVC架构方式,可以让写公共代码.美工和具体逻辑开发人员独立出来.有关Android 的资源文件values/strings.xml中如何实现格式化字符串呢? 这里Android123举个简单的例子,以及最终可能会用到哪些地方. 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <resources> <string name=

  • Android中主要资源文件及文件夹介绍

    在Android项目文件夹里面,主要的资源文件是放在res文件夹里面的 1:assets文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像xml,java文件被预编译,可以存放一些图片,html,js, css等文件. 2:res文件夹里面的多个文件夹的各自介绍 res/anim/ XML文件,它们被编译进逐帧动画(frame by frame animation)或补间动画(tweened animation)对象 res/drawable/ .png..9.png..jpg文件

  • 基于将Android工程做成jar包和资源文件的解决方法

    需要特别注意的是,以jar包和资源包方式提供给第三方开发者,我们的工程的代码中就不能使用类似于R.layout.main.R.string.name等等这样的方式来引用资源了. 为此,我们就不能直接使用R文件,而是要通过字段名称来动态的获取资源的id,再来使用. 以下封装了一个类,可以通过字段名称动态获取id. 复制代码 代码如下: package com.arui.util; import android.content.Context; public class ResourceUtil {

  • Android 资源 id详解及的动态获取

    Android 资源 id详解 我们平时获取资源是通过 findViewById 方法进行的,比如我们常在onCreate方法中使用这样的语句: btnChecked=(ImageView)findViewById(R.id.imgCheck); findViewById是我们获取layout中各种View 对象比如按钮.标签.ListView和ImageView的便利方法.顾名思义,它需要一个int参数:资源id. 资源id非常有用.Android回自动为每个位于res目录下的资源分配id,包

  • android图像绘制(六)获取本地图片或拍照图片等图片资源

    从SD卡中获取图片资源,或者拍一张新的图片. 先贴代码 获取图片: 注释:拍照获取的话,可以指定图片的保存地址,在此不说明. 复制代码 代码如下: CharSequence[] items = {"相册", "相机"}; new AlertDialog.Builder(this) .setTitle("选择图片来源") .setItems(items, new OnClickListener() { public void onClick(Dia

  • Android中实现根据资源名获取资源ID

    接触过Android开发的同学们都知道在Android中访问程序资源基本都是通过资源ID来访问.这样开发起来很简单,并且可以不去考虑各种分辨率,语言等不同资源显式指定. 痛点 但是,有时候也会有一些问题,比如我们根据服务器端的值取图片,但是服务器端绝对不会返回给我们的是资源id,最多是一种和文件名相关联的值,操作资源少的时候,可以维护一个容器进行值与资源ID的映射,但是多的话,就需要另想办法了. 便捷的方法 在这种情况下,使用文件名来得到资源ID显得事半功倍. 通过调用Resources的get

  • Android中加载网络资源时的优化可使用(线程+缓存)解决

    网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决.下面提出一些优化: 1.采用线程池 2.内存缓存+文件缓存 3.内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4.对下载的图片进行按比例缩放,以减少内存的消耗 具体的代码里面说明.先放上内存缓存类的代码MemoryCache.java: 复制代码 代码如下: <SPAN style="FONT-SIZE: 18px"><STRONG>publ

  • android开发教程之系统资源的使用方法 android资源文件

    一.颜色资源1.颜色XML文件格式 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><resources> //resources根元素   <color name="" >#000000</color>//color子元素</resouces> 2.引用格式: java代码中:R.color.color_name  (这是一个int型的

  • Android 往Framework中添加新资源的方法详解

    有时候我们想在标准的Framework中添加自己的新的资源怎么办呢?办法就是我们来尝试下.通过Eclipse的联系,我们可以联想到是否就是简单的把字符串放在res的各个文件夹里面.先来试试看,编译,系统立即报错.为什么呢?它提示你利用make update-api这个命令来更新public.xml文件或者把这个声明称hide类型.这个肯定不是我们想要的.所以方法有二:方法1:正常添加完资源后,执行make update-api函数.系统更新res/values/public.xml文件.方法2:

  • 解析Android资源文件及他们的读取方法详解

    Sam在Android开发中,有两种处理资源文件的方式.其一,是将所有资源文件以及JNI程序放置于一个单独的资源包.使用到他们时,使用文件方式读取.或者直接使用C++层代码读取. 其二,则是将资源文件加入到APK内部.使用各种不同的办法去得到其内容.方法一:适合于移植较大的C++程序时使用,因为C++代码数量众多,不太可能修改为JAVA代码.所以将其与资源文件以一定方式存放,并让他们自称体系是个好办法.但这造成软件的发布必须以APK+资源包的方式发布.方法二:则比较适合代码量不是非常大,且资源数

  • 在Android开发中替换资源图片不起作用的解决方法

    现象 在android开发中,经常会需要替换res\drawable中的图片,打开res\layout下的文件预览布局页面发现图片已经被替换,但在模拟器或者真实机器上运行时发现该图片并没有被替换,还是使用的是原来的资源图片. 原因 在开发过程中,由于使用模拟器测试了程序,在首次运行后会将res文件夹下的图片资源文件(如drawable-hdpi.drawable-ldpi和drawable-mdpi)拷贝到bin文件夹下.在替换资源图片后,eclipse并不清楚是否有图片改变,所以会使用原来bi

随机推荐