換膚方案

背景需求

目前Android APP換膚大致可分爲兩大類:bash

  • 兩套主題的切換(好比白天/黑夜),使用一個開關按鈕進行切換。
  • 多套主題在線下載並更新。

第一種的實現基本上使用設置本地Theme來操做,即將全部的資源打包到APP中,而且根據主題進行切換。 第二種不可能使用第一種的實現方式,由於將全部資源都打包到APP中缺少靈活性,不利於活動的更新,而且也會使得apk包的體積變大。全部第二種的實現必須是支持線上下載的。網絡

方案選擇

配合產品的需求而且能實現換膚的靈動性,咱們選擇上述的第二種方案。通過以前的Android和IOS成員小組討論,統一以爲能夠採用下載壓縮包,並經過解析壓縮包讀取資源進行替換。app

壓縮包下載下來後怎麼讀取資源?這裏有兩種方式:動畫

  • 將下載的皮膚包進行解壓縮而且經過文件流的方式讀取裏面的圖片資源、文件資源。
  • 將下載的皮膚包加載到assetManager管理器中,並經過該管理器新建一個Resource對象,須要換膚的控件經過Resource對象進行讀取資源。

第一種方式須要手動開啓文件流,而且不一樣的文件流有不一樣的文件流方式,好比圖片、文本文件等,還有不一樣設備因爲分辨率加載的資源是不一樣的,如何合理地去選擇合適的資源去加載也是一個須要解決的問題。spa

第二種方式須要將皮膚包加載到assetManager管理器,assetManager管理器新生成的Resource對象和咱們主工程的Resource對象是相同類的不一樣對象,可使用咱們熟悉的方式去加載資源(如resource.getColor,resource.getDrawable等)。code

基於上述兩種加載資源的方式,這裏選擇第二種方式進行資源的加載與讀取。cdn

具體實施

一、將所需的皮膚包經過網絡下載到本地,這裏的皮膚包是一個apk文件,爲了讓apk包足夠小,裏面只包含資源文件。可能有多個皮膚包,好比theme1.skin,theme2.skin......對象

二、經過後臺獲取須要加載的皮膚包的名字,如theme1.skin,經過調用AssetManager對象的addAssetPath方法並生成一個新的Resource對象,以下代碼:blog

AssetManager assetManager = AssetManager.class.newInstance();
//因爲addAssetPath()這個方法被隱藏掉了,因此不能直接使用對象直接訪問,
//這裏使用了反射的方式,做用是將該皮膚包加入到asset管理器中
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath"
, String.class);
addAssetPath.invoke(assetManager, skinPath);

Resources skinResource = new Resources(
  assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
複製代碼

三、自定義一個InflaterFactory的子類,SkinInflaterFactory,重寫onCreateView(View, String, Context, AttributeSet) 方法,對於須要換膚的控件進行屬性的解析與存儲,而後對這些換膚的控件去第二步的Resource對象中加載資源並設置到這些控件中。圖片

四、在BaseActivity的onCreate方法新建SkinInflaterFactory對象,並將該SkinInflateFactory對象設置給Activity的LayoutInflater對象,以下代碼:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    mSkinInflaterFactory = new SkinInflaterFactory();
    LayoutInflaterCompat.setFactory(
      getLayoutInflater(), mSkinInflaterFactory);
    super.onCreate(savedInstanceState);
}
複製代碼

流程圖

換膚流程.png

其餘問題

一、如何支持控件點擊後觸發不一樣的業務流程? 能夠經過自定義一個屬性,如skin:click="@string/clickAction",主工程的clickAction="muapp://app/testDefault",皮膚包裏的clickAction="muapp://app/testClick",經過目前項目中的路由機制觸發不一樣的跳轉動做。好比說上述默認的跳轉是跳轉到主工程(app爲module名)的TestDefaultAction(註解actionName="testDefault")類的invoke方法中,而更改後會跳轉到主工程(app爲module名)的TestClickAction(註解actionName="testClick")類的invoke方法中。

二、如何支持控件的不一樣行爲方式?例如不一樣的動畫效果等 這個問題和第一個問題的處理方式的相似的,一樣能夠經過主工程和皮膚包不一樣的tag(String文案)處理不一樣的行爲方式。

三、如何處理自定義View的換膚需求? 能夠添加一個方法,將自定義View須要換膚的屬性名(如background),屬性值(如background對應的圖片的資源ID)傳遞到方法中,而後去皮膚包的Resource對象中尋找是否有相應的可替換的皮膚或者可替換的行爲。

相關文章
相關標籤/搜索