背景需求
目前Android APP換膚大體可分為兩大類:
兩套主題的切換(比如白天/黑夜),使用一個開關按鈕進行切換。多套主題線上下載並更新。第一種的實現基本上使用設定本地Theme來操作,即將所有的資源打包到APP中,並且根據主題進行切換。 第二種不可能使用第一種的實現方式,因為將所有資源都打包到APP中缺乏靈活性,不利於活動的更新,並且也會使得apk包的體積變大。所有第二種的實現必須是支援線上下載的。
方案選擇配合產品的需求並且能實現換膚的靈動性,我們選擇上述的第二種方案。經過之前的Android和IOS成員小組討論,統一覺得可以採用下載壓縮包,並通過解析壓縮包讀取資源進行替換。
壓縮包下載下來後怎麼讀取資源?這裡有兩種方式:
將下載的面板包進行解壓縮並且通過檔案流的方式讀取裡面的圖片資源、檔案資源。將下載的面板包載入到assetManager管理器中,並通過該管理器新建一個Resource物件,需要換膚的控制元件通過Resource物件進行讀取資源。第一種方式需要手動開啟檔案流,並且不同的檔案流有不同的檔案流方式,比如圖片、文字檔案等,還有不同裝置由於解析度載入的資源是不同的,如何合理地去選擇合適的資源去載入也是一個需要解決的問題。
第二種方式需要將面板包載入到assetManager管理器,assetManager管理器新生成的Resource物件和我們主工程的Resource物件是相同類的不同物件,可以使用我們熟悉的方式去載入資源(如resource.getColor,resource.getDrawable等)。
基於上述兩種載入資源的方式,這裡選擇第二種方式進行資源的載入與讀取。
具體實施1、將所需的面板包通過網路下載到本地,這裡的面板包是一個apk檔案,為了讓apk包足夠小,裡面只包含資原始檔。可能有多個面板包,比如theme1.skin,theme2.skin......
2、通過後臺獲取需要載入的面板包的名字,如theme1.skin,通過呼叫AssetManager物件的addAssetPath方法並生成一個新的Resource物件,如下程式碼:
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());
3、自定義一個InflaterFactory的子類,SkinInflaterFactory,重寫onCreateView(View, String, Context, AttributeSet)方法,對於需要換膚的控制元件進行屬性的解析與儲存,然後對這些換膚的控制元件去第二步的Resource物件中載入資源並設定到這些控制元件中。
4、在BaseActivity的onCreate方法新建SkinInflaterFactory物件,並將該SkinInflateFactory物件設定給Activity的LayoutInflater物件,如下程式碼:
其他問題1、如何支援控制元件點選後觸發不同的業務流程?可以通過自定義一個屬性,如skin:click="@string/clickAction",主工程的clickAction="muapp://app/testDefault",面板包裡的clickAction="muapp://app/testClick",通過目前專案中的路由機制觸發不同的跳轉動作。比如說上述預設的跳轉是跳轉到主工程(app為module名)的TestDefaultAction(註解actionName="testDefault")類的invoke方法中,而更改後會跳轉到主工程(app為module名)的TestClickAction(註解actionName="testClick")類的invoke方法中。
2、如何支援控制元件的不同行為方式?例如不同的動畫效果等這個問題和第一個問題的處理方式的類似的,同樣可以通過主工程和面板包不同的tag(String文案)處理不同的行為方式。
3、如何處理自定義View的換膚需求?可以新增一個方法,將自定義View需要換膚的屬性名(如background),屬性值(如background對應的圖片的資源ID)傳遞到方法中,然後去面板包的Resource物件中尋找是否有相應的可替換的面板或者可替換的行為。
總結看完之後有沒有掌握呢?作為一個程式設計師,要學的東西有很多,而學到的知識點,都是錢(因為技術人員大部分情況是根據你的能力來定級、來發薪水的),技多不壓身。
為了很好的生活,我們要多多學習,增加我們手裡的金錢。尤其經歷了這一疫情,我深深的感受到金錢的重要,所以,我們一定不能停下學習的腳步!
附上我的Android核心技術學習大綱,獲取相關內容來GitHub:https://github.com/Meng997998/AndroidJX