上一篇說到了View的載入過程,並且對TextView進行了攔截,讓頁面上所有的TextView內容都變成了我們想要的helloworld,這次我們一起來研究一下網易雲音樂的動態換膚技術。說到換膚,有很多人選擇了更改樣式的方式,但是這種方式樣式是固定的,必須提前內建好打包到apk中,所以導致apk包增大,並且靈活性不夠強。今天我們要做的是打造一個動態下發+全屬性修改+流暢無閃爍的換膚方案。要實現該方案主要有3個難點,分別是:如何批量修改View的背景、如何標識需要換膚的控制元件、如何載入外掛皮膚資源到當前app中,接下來我們一個一個難題來攻破。
1.如何批量修改View的背景?
一個一個控制元件設定肯定是不現實的,這樣會產生很多的垃圾程式碼,靈活性也不夠強,所以必須想辦法批量處理。這裡可以通過view的hook技術,通過自定義Factory的方式來控制app中view的背景設定,從而實現不閃爍換膚的效果。既然我們攔截了系統生成view的過程,那麼首先我們必須要自己生成view,不然頁面就要空白了,生成view的具體程式碼如下:
這裡呼叫mDelegate.createView是考慮到AppCompatActivity本身對一些控制元件做了一些相容性問題,如果能在裡面找到就直接使用就好了,如果沒有找到的話,我們就要自己定義生成View的方式了。系統生成view時是使用反射的方式,這裡我們也來模仿一下。需要用到反射的話就必須知道控制元件的包名,但是有些控制元件已經自帶包名,比如V7下面的控制元件,這個時候我們通過判斷這裡是否包含"."來判斷出來,然後直接把整個路徑傳進去就好了。包名的話主要是3種,如下:
具體生成的View方式和系統的一模一樣,都是使用到反射來生成,這裡再貼一下程式碼:
2.如何找到需要換膚的控制元件?
這裡用的最多的方法是給控制元件設定一個屬性來標記,判斷哪個控制元件包含自定義的該屬性,給包含的進行背景圖片或顏色的替換。我們首先在values目錄下新建一個attr.xml,然後在裡面定義了一個叫做skinable的boolean屬性,然後當該屬性為true時就加入該控制元件到一個map中,準備進行換膚。其中attrs是前面初始化view時傳過來的,代表該view的所有屬性。判斷出該控制元件需要換膚時,遍歷出該控制元件所有屬性加入到一個屬性map中備用,具體程式碼如下:
3.如何實現資源的替換?
我們平時獲取專案中的資源一般通過context.getResource().getXXX來獲取,其實Resource只是一個皮包類,本質還是通過AssetManager來獲取資原始檔的,所以我們只需要獲取到外部外掛包的AssetManager物件即可,這裡可以通過反射來獲取到,並且呼叫addAssetPath方法來載入外部資源包。最後我們也將外部生成的AssetManager進行包一層Resource,為的是保留resid和資源的對應關係,這樣才能實現資源的替換。
獲取到了外部外掛的Resource物件接下來就好辦了,直接將當前app中控制元件的屬性設定成外部的資源就大功告成了。這裡attrsMap是我們自定義生成View時儲存的某個View的所有屬性,找出其中需要更換的屬性,比如background、drawable什麼的。
問題解答
1.如果同一控制元件不光要換背景,同時還要換字型怎麼辦?
答:這裡可以再自定義一個屬性代表字型需要替換
2.皮膚外掛該怎麼生成呢?
答:直接將當前專案更換一下資原始檔,然後打包成apk,最後改個名字就好了,最好不要以apk結尾,以防使用者去安裝
3.現有app中的資源id是怎麼和外掛中的資源id進行對應的呢?
答:現有app中的Resource配置和外掛Resource的配置是同一個,所以可以通過當前資源id=外部資源id的特殊性,根據當前資源id直接找到外部資源進行載入