http://www.cnblogs.com/punkisnotdead/p/4968851.htmlhtml
之前寫的這篇文章 能夠高仿出 知乎 新浪微博等 絕大多數app的換膚技術,可是遺漏了騰訊的效果,android
實際上騰訊的這方面比 上述app要稍微複雜一些,有一點像 如今流行的插件技術。架構
其實也能夠理解,騰訊畢竟是能夠靠 皮膚賺錢的公司,所謂 「沒錢玩你麻痹」 說的就是騰訊。app
靠這個賺錢固然作的會更好一點。今天就來看看騰訊是咋作的。咱們也來仿一仿!ide
就拿qq空間來講吧。函數
你看我使用了一個qq空間的 黑色主題。不使用別的是由於別的要開通什麼黃鑽綠鑽,可是顯然我沒有錢。 而後去命令行下看點東西:佈局
記住這個路徑,這個時候我要提一下,你只有使用了 特殊的主題之後 這個theme.xml纔有值的。字體
你若是不使用這個你下載的主題 用默認的主題的話就會這樣:優化
我一開始分析騰訊app的時候 這裏也卡過一會 後來發現是你得使用主題之後 這個theme 文件纔有變化~~spa
回到前面那個有內容的xml文件看一看。他指向了一個地址,咱們就去這個地址下面看看 究竟是什麼
一看到這個,相信你們 就都明白了,這不就是個apk麼?咱們打包出去的apk 解壓縮之後不就是這些內容麼?
因此這裏你看 騰訊的作法事 把新的皮膚apk 放在本身data data 包名 這個路徑下的某個文件夾內。
可是並無安裝他 對吧。
新浪微博 咱們那會分析的時候 他們的皮膚包就是得下載下來之後 再安裝一次的。從用戶體驗上來講,騰訊的
這個明顯更加優秀。
到這裏 應該不少人就明白了,騰訊的所謂換膚技術,無非就是 把新的皮膚包 下載到本身的安裝目錄下面,
而後本身的app 去加載這個皮膚包apk裏的 資源 便可(注意這裏要再強調如下,新浪的皮膚apk是安裝好了的,
而騰訊的這個根本沒讓你安裝)!這個就是騰訊旗下app 換膚的原理。你能夠打開你的設置---應用裏面看一下:
你看明顯微博的皮膚都已經在應用列表裏面了,可是騰訊的可沒有~~~
咱們下面就來仿照騰訊的 來實現如下這個效果。
這個效果的關鍵點 其實就在於 如何在咱們的主apk裏面 加載到 主題apk裏的資源。而且這個主題apk 是不能夠被安裝的。
就好像高德地圖sdk 裏提供的那些資源包同樣,也是不須要安裝 自動就可使用的。
這個資源包裏面 通常都包含 字體顏色啊 背景色啊 背景圖啊 複雜的甚至會包含佈局文件!
那我如今就作一個最簡單的效果,主apk裏 有一個tv 他有一個背景色,而後咱們點擊更換主題之後 這個tv就會 把這個背景色
更換成一個 背景圖(我這個背景圖是用的林熙蕾的照片)。固然了 咱們這個背景圖顯然是放在咱們的主題apk裏的。咱們的
主apk裏固然是不會有這張圖的,否則還作個毛啊!(若是能作出這個demo 那麼很顯然其餘的就所有都能通了)
咱們首先來作一下這個主題apk,
第一步,把咱們的背景圖片放到相映的路徑下:
第二步:定義主題apk裏的 一個類和一個方法:
1 package com.example.administrator.themeapk; 2 3 import android.content.res.Resources; 4 import android.graphics.drawable.Drawable; 5 6 /** 7 * Created by Administrator on 2015/12/24. 8 */ 9 //這裏咱們由於是demo演示 因此實際上就只有一個返回Drawable的方法 10 //實際上你能夠本身往下面寫,返回任何資源,好比theme,好比string,好比color,甚至資源文件等等 11 public class ResourceUtils { 12 13 public static Drawable getTextViewBackGroundDrawable(Resources resources) { 14 return resources.getDrawable(R.mipmap.lxl); 15 } 16 17 //能夠思考一下 爲何這個地方咱們不用這個context做爲參數的方法,把這個方法給註釋掉了。 18 //其實緣由也很簡單 一個Context對應着惟一的一個Recource,若是咱們想要在主apk裏調用 19 //咱們主題apk裏的資源,那這個context參數就沒法構造了,由於主apk裏只能拿到本身的context, 20 //確定是拿不到主題apk裏的context的。因此咱們要用上面的Resources這個參數,由於雖然咱們拿不到 21 //主題的context,可是咱們能夠把主題apk裏的resource 加入到主apk裏的resource。 22 // public static Drawable getTextViewBackGroundDrawable(Context context) 23 // { 24 // return context.getResources().getDrawable(R.mipmap.lxl); 25 // } 26 27 28 }
而後咱們的主題apk實際上就編寫完成了,而後咱們對這個工程進行打包,而且命名爲theme.apk
而後咱們把這個theme.apk 放到咱們主apk的 cache目錄下面:
最後咱們能夠先運行一下程序 看看效果:
最後咱們看下最關鍵的主apk裏的代碼 應該怎麼寫:
1 //這個changeTv: 一按就自動加載主題apk裏的資源 而且更換themetv 這個tv裏的背景色了 2 changeTv = (TextView) findViewById(R.id.changeTv); 3 //themeTv: 就是用於展示效果的textview 替換背景色 就是替換這個textview的 4 themeTv = (TextView) findViewById(R.id.themeTv); 5 changeTv.setOnClickListener(new View.OnClickListener() { 6 @Override 7 public void onClick(View v) { 8 //這個fileDir 通常都是返回/data/data/你程序的包名/cache/ 9 String fileDir = getCacheDir() + File.separator; 10 //咱們是把theme.apk這個文件push到/data/data/你程序的包名/cache/這個路徑下的 11 //注意若是你本身作的話,這些主題包 固然是從網上下載下來 注意下載下來之後放在/data/data/你程序的包名/ 12 //這個路徑下 任何一個目錄均可以 不必定非要是/cache/這個目錄 13 String filePath = fileDir + "theme.apk"; 14 //這個目錄是用來構建DexClassLoader對象的 ,用做構造函數裏的第二個參數 15 //是dex的輸出路徑(由於加載apk/jar的時候會解壓出dex文件,這個路徑就是保存dex文件的) 16 String optimizedDirectory = getCacheDir() + File.separator; 17 //DexClassLoader能夠加載任何路徑的apk/dex/jar 這裏要注意了PathClassLoader只能加載/data/app中的apk,也就是已經安裝到手機中的apk。 18 //這個也是PathClassLoader做爲默認的類加載器的緣由,由於通常程序都是安裝了,在打開,這時候PathClassLoader就去加載指定的apk(解壓成dex,而後在優化成odex)就能夠了。 19 ClassLoader classLoader = new DexClassLoader(filePath, optimizedDirectory, null, getClassLoader()); 20 //把咱們主題apk包裏的資源 加載到本apk本身的resouce裏 21 addOtherResourcesToMain(filePath); 22 try { 23 //DexClassLoader對象來 加載theme.apk包裏的ResourceUtils這個類的getTextViewBackGroundDrawable這個方法 24 Class clazz = classLoader.loadClass("com.example.administrator.themeapk.ResourceUtils"); 25 Method method = clazz.getMethod("getTextViewBackGroundDrawable", Resources.class); 26 //invoke 也就是執行方法的時候 能夠看到咱們傳的參數是mResource 而這個mResource是咱們本身新構造出來的 27 //裏面包含了theme.apk裏的資源。 28 Drawable drawable = (Drawable) method.invoke(null, mResource); 29 //成功獲取了 主題apk裏的圖片資源之後 剩下的事情就水稻渠成了. 30 themeTv.setBackgroundDrawable(drawable); 31 } catch (ClassNotFoundException e) { 32 e.printStackTrace(); 33 } catch (NoSuchMethodException e) { 34 e.printStackTrace(); 35 } catch (InvocationTargetException e) { 36 e.printStackTrace(); 37 } catch (IllegalAccessException e) { 38 e.printStackTrace(); 39 } 40 } 41 });
1 //這個方法把咱們主題apk裏的resource 加入到咱們本身的主apk裏的resource裏 2 //這個dexPath就是 咱們theme.apk在 咱們主apk 的存放路徑 3 private void addOtherResourcesToMain(String dexPath) { 4 try { 5 AssetManager assetManager = AssetManager.class.newInstance(); 6 //反射調用addAssetPath這個方法 就能夠 7 Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); 8 addAssetPath.invoke(assetManager, dexPath); 9 mAssetManager = assetManager; 10 } catch (InstantiationException e) { 11 e.printStackTrace(); 12 } catch (IllegalAccessException e) { 13 e.printStackTrace(); 14 } catch (NoSuchMethodException e) { 15 e.printStackTrace(); 16 } catch (InvocationTargetException e) { 17 e.printStackTrace(); 18 } 19 //把themeapk裏的資源 經過addAssetPath 這個方法增長到本apk本身的path裏面之後 就能夠從新構建出resource對象了 20 mResource = new Resources(mAssetManager, getResources().getDisplayMetrics(), getResources().getConfiguration()); 21 }
註釋應該寫的比較清楚了。相信你們應該能理解的比較好。其原理能夠參考老羅的博客:http://blog.csdn.net/luoshengyang/article/details/8791064
總結起來qq的皮膚加載技術 其實就下面幾步:
1.實例化 AssetManager 對象,並經過反射調用 addAssetPath(String) 方法加載目標 apk(或與 apk 文件架構一致的目錄)
2.經過第一步獲得的 AssetManager 實例化 Resource 對象
3.利用第二步獲得的 Resource 對象來動態加載資源(這個方案是比較簡單的方案 可是有必定侷限性 讀者能夠本身這樣寫一個,我這篇blog裏的方案是直接第四步)
4.經過dexclassloader 來反射調用 主題包裏的方法 來獲得資源。參數就用咱們第二步獲得的Resource對象。這樣作的好處是,咱們能夠定義一個規範的接口出來,
咱們的主apk 直接調用接口方法 便可,theme.apk裏 實現這個接口就好了。這樣你就算有100個主題包,咱們的主apk裏的代碼也只用寫一份便可!很是方便。