Android Resources 資原始檔的使用詳解
本篇文章是對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提供的介面來建立檔案。
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(); } }
相關文章
- Android資原始檔Android
- Android ArrayMap 原始碼詳解Android原始碼
- Android TypedArray 原始碼詳解Android原始碼
- Android SparseArray 原始碼詳解Android原始碼
- Android TypedArray原始碼詳解Android原始碼
- 詳解Android RxJava的使用AndroidRxJava
- Android Retrofit原始碼解析:都能看懂的Retrofit使用詳解Android原始碼
- ApkTool專案解析resources.arsc詳解APK
- android NDK的android.mk檔案的詳解Android
- Android shape的使用詳解Android
- 詳解Android中AsyncTask的使用Android
- Android WebView Resources$NotFoundExceptionAndroidWebViewException
- Android Gson使用詳解Android
- Android Paint 使用詳解AndroidAI
- Android AsyncTask使用詳解Android
- Android webview使用詳解AndroidWebView
- Android ViewPager使用詳解AndroidViewpager
- Android原始碼分析--CircleImageView 原始碼詳解Android原始碼View
- Android的.so檔案詳細解讀Android
- Android中音樂檔案的資訊詳解Android
- android WebView詳解,常見漏洞詳解和安全原始碼AndroidWebView原始碼
- Android shape 的詳解及使用Android
- Android BroadcastReceiver使用詳解AndroidAST
- Android AIDL使用詳解AndroidAI
- Android Support Annotations 使用詳解Android
- Android中PopupWindow使用詳解Android
- Android中AsyncTask使用詳解Android
- 使用webpack打包ThinkPHP的資原始檔WebPHP
- C#使用資原始檔的方法C#
- Android資源知識(一)之Resources概覽Android
- maven的test使用main的resourcesMavenAI
- Android原始碼目錄結構詳解Android原始碼
- Android EventBus3.x的使用詳解AndroidS3
- Android-SharedPreferences 使用詳解Android
- Android FragmentTabHost 使用方法詳解AndroidFragment
- Android taskAffinity屬性使用詳解Android
- Android 中 HttpURLConnection 使用詳解AndroidHTTP
- Android中HttpURLConnection使用詳解AndroidHTTP