提高Bitmap解析速度( Resources , BitmapFactory , AssetManager ,主題定製 )

圖片是應用裏面最常用的資源,在canvas裏面使用需要將其解析成Bitmap的方式(例如倒影效果)。這裏給出幾個可能會有的場景,探討一下如何使用:
  • 應用主題轉化:當某個應用希望換掉整個生命週期中的一些背景圖片,例如從卡通主題轉化到草原主題,這樣會涉及到Button、Tab、Layout等各式各樣圖片的替換。
  • 動畫播放:例如關機動畫、鬧鈴提醒(當然簡單的動作除外)

      轉載請註明http://ishelf.iteye.com/blog/1032563

      場景一中,可以通過apk升級來實現,也可以通過調用外部資源來實現。這兩種方式比較簡單,問題也就顯而易見。第一種apk升級很煩人,至少我自己都懶得去。第二種,外部資源是簡單,不過圖片的解析速度一定會比從apk中調用drawable資源要慢好幾倍,對於配置很好的機器這個速度不是問題,可是中下的機器可就夠受了(最近試了試中興的平板,和三星的對比太鮮明瞭)。這兩個的前提都是對圖片顯示要求不是非常的很高,例如圖片預覽

      在場景二中,需要考慮到定製的問題。你給國內和國外的關機動畫不能一樣吧,給聯通和給電信定製的不能一樣吧(這個也根據需求而定,不排除都一樣的可能)。這樣當需求變化時,難道你的代碼也要跟着變化麼? 這樣的話真給Java顯眼了,O(∩_∩)O。 其實在場景一中的主題替換也是一個定製的問題。

      如何能在保證即不修改代碼又能保證圖片解析速度儘可能快的前提下,做到圖片資源的定製是接下來要討論的問題。首先看代碼

 

orginalbm = BitmapFactory.decodeFile(Path);
//解析源圖片文件,根據需要可以放在任何地方。

orginalbm = BitmapFactory.decodeResource(resources,source_id);
//解析apk中的資源圖片。可以放置到一個不包含任何Java文件的apk包中去。


第二種方式一定是比第一種方式速度快(大家有什麼異議可以討論一下)。接下來就是如何用apk解析的方式來動態的加載外部的圖片資源(也可以包括Layout,anim和xml資源)。

     第一步就是解決如何解析apk包,並將資源加載進來。這要分兩種情況:一是在應用中使用,而是在framework框架中。

這樣也就有兩種方式,首先介紹第一種,直接看代碼

		try {
			// apk包的文件路徑
			// 這是一個Package 解釋器, 是隱藏的
			// 構造函數的參數只有一個, apk文件的路徑
			// PackageParser packageParser = new PackageParser(apkPath);
			Class pkgParserCls = Class.forName(PATH_PackageParser);
			Class[] typeArgs = new Class[1];
			typeArgs[0] = String.class;
			Constructor pkgParserCt = pkgParserCls.getConstructor(typeArgs);
			Object[] valueArgs = new Object[1];
			valueArgs[0] = apkPath;
			Object pkgParser = pkgParserCt.newInstance(valueArgs);
			Log.d("ANDROID_LAB", "pkgParser:" + pkgParser.toString());
			// 這個是與顯示有關的, 裏面涉及到一些像素顯示等等, 我們使用默認的情況
			DisplayMetrics metrics = new DisplayMetrics();
			metrics.setToDefaults();
			// PackageParser.Package mPkgInfo = packageParser.parsePackage(new
			// File(apkPath), apkPath,
			// metrics, 0);
			typeArgs = new Class[4];
			typeArgs[0] = File.class;
			typeArgs[1] = String.class;
			typeArgs[2] = DisplayMetrics.class;
			typeArgs[3] = Integer.TYPE;
			Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod(
					"parsePackage", typeArgs);
			valueArgs = new Object[4];
			valueArgs[0] = new File(apkPath);
			valueArgs[1] = apkPath;
			valueArgs[2] = metrics;
			valueArgs[3] = 0;
			Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser,
					valueArgs);
			// 應用程序信息包, 這個公開的, 不過有些函數, 變量沒公開
			// ApplicationInfo info = mPkgInfo.applicationInfo;
			Field appInfoFld = pkgParserPkg.getClass().getDeclaredField(
					"applicationInfo");
			ApplicationInfo info = (ApplicationInfo) appInfoFld
					.get(pkgParserPkg);
			// uid 輸出爲"-1",原因是未安裝,系統未分配其Uid。
			Log.d("ANDROID_LAB", "pkg:" + info.packageName + " uid=" + info.uid);
			Class assetMagCls = Class.forName(PATH_AssetManager);
			Constructor assetMagCt = assetMagCls.getConstructor((Class[]) null);
			Object assetMag = assetMagCt.newInstance((Object[]) null);
			typeArgs = new Class[1];
			typeArgs[0] = String.class;
			Method assetMag_addAssetPathMtd = assetMagCls.getDeclaredMethod(
					"addAssetPath", typeArgs);
			valueArgs = new Object[1];
			valueArgs[0] = apkPath;
			assetMag_addAssetPathMtd.invoke(assetMag, valueArgs);
			Resources res = getResources();
			typeArgs = new Class[3];
			typeArgs[0] = assetMag.getClass();
			typeArgs[1] = res.getDisplayMetrics().getClass();
			typeArgs[2] = res.getConfiguration().getClass();
			Constructor resCt = Resources.class.getConstructor(typeArgs);
			valueArgs = new Object[3];
			valueArgs[0] = assetMag;
			valueArgs[1] = res.getDisplayMetrics();
			valueArgs[2] = res.getConfiguration();
			<span style="color: rgb(255, 0, 0);">res = (Resources) resCt.newInstance(valueArgs);</span>


代碼是參考來源:http://blog.csdn.net/sodino/archive/2011/03/01/6215224.aspx    。這裏主要使用了Java反射機制,得到了res(Resources)(這個博主挺nb的,O(∩_∩)O)。 Resources類是實現apk資源解碼的核心,具體類的作用這裏就不介紹了(不懂得看源碼去)。

       如果是在framework中使用就簡單了,看代碼

        AssetManager assets = new AssetManager();
        String resDir = "/sdcard/extern-sd/xxx.apk";
        if (assets.addAssetPath(resDir) == 0) {
            Log.e(TAG, "parse failed");
            return;
        }
        resources = new Resources(assets, null, null, null);


這樣很簡單就得到了Resources類。

     第二步是解決如何得到drawble資源的id值。這裏我們根據需要爲每個drawable添加相應的參數,例如在哪個類裏面使用、名稱等各種信息。

       先看代碼:

		int all_view_id = resources.getIdentifier("image_ids", "xml",
				"com.xxx.resource");
		XmlResourceParser parser = resources.getXml(all_view_id);
		try {
			int type;
			while ((type = parser.next()) != XmlPullParser.START_TAG
					&& type != XmlPullParser.END_DOCUMENT) {;}

			if (type != XmlPullParser.START_TAG) {
				Log.e(TAG, "No start tag found in package manager 	settings");
			}
			int outerDepth = parser.getDepth();
			while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
					&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
				if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
					continue;
				}
				String tagName = parser.getName();
			}
		} catch (XmlPullParserException e) {
			Log.e(TAG, "Error reading package manager settings", e);
		} catch (java.io.IOException e) {
			Log.e(TAG, "Error reading package manager settings", e);
		}


通過getIdentifier可以得到需要的xml文件id值。

 resources.getIdentifier("image_ids", "layout","com.xxx.resource");


//字段“layout”,表示資源文件在layout文件夾中

     //字段“com.xxx.resource”表示該apk的包名(packagename)

     需要注意一點在自己配置的xml文件中,要使用android:drawable這樣的屬性名定義,否則不會編譯成int類型的id號。


     最後一步就是如何調用通過id調用圖片。

BitmapFactory.decodeResource(resources, source_id);


resources就是第一步得到的Resources類,id則是第二部得到的id號。需要注意一點就是變量的作用域。

經過上面三步可以很容易的實現使用apk的方式調用外部的圖片資源。

 

 

轉自:http://ishelf.iteye.com/blog/1032599

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章