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