Android 高仿騰訊旗下app的 皮膚加載技術

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裏的代碼也只用寫一份便可!很是方便。

相關文章
相關標籤/搜索