口號:Android只是個Demo。
智能手機何其多,Symbian、WP、Android...,問題是原生的Android系統不支持主題定制。
於是我等看着花哨的主題資源包在市場上泛濫,前提:你先下載一個運行這些主題資源的應用程序APK包先。
但是...... 原生Android系統是不願意還是不能夠支持主題呢?以后會不會支持呢?
不管了,既然看Android原生主題支持功能不夠,本文就來嘗試一下如何通過修改Android原生代碼來實現主題支持。
============================= 環境與步驟=================================
軟件版本:Android JellyBean
步驟:
I.原生Android主題支持需要涉及的部分
II.涉及部分在原生系統中的邏輯及修改
III.對周邊模塊的影響和支持
=============================原生Android主題支持需要涉及的部分=================================
既然是要支持主題,也就是說系統需要支持和管理多套主題資源包,並且應用可以用相同的資源ID來訪問不同資源包中對應的資源。
因此我們首先分析一下Android系統的資源訪問流程。
對應用來說,資源訪問主要有下面三種方式:
第一、比較普遍的方式是使用xml定義,並且通過AAPT工具生成一個R文件,列出資源的索引來讓Android系統自己去遍歷整個資源樹的方式來訪問。
第二、通過Resources接口來訪問,使用Resources類的getDrawable、getString等接口來獲取資源。
第三、通過AssetManager類的接口去訪問,使用這個類的open方法來返回一個InputStream對象得到資源。
其實這三個訪問方式只是Android資源訪問中整個流程中在不同層次對外提供的三個接口,到底層的實現都是殊途同歸的。
第一種的xml定義的資源解析的邏輯在Resources.java文件的loadXmlResourceParser函數中,實際上也是調用的第三類接口,如下圖:
因此我們實際上需要修改的部分主干是在訪問資源具體路徑前,按照當前系統主題設置訪問不同資源APK下的文件
即是把原生Android中資源ID和資源文件路徑之間一對一的關系改為一對多的關系。
例子如下:如果原有資源ID和資源文件路徑關系為:
R.drawable.image01 = 0x7F020001
通過系統的資源查找之后找到文件路徑為 /system/app/frameworks-res.apk下的res/drawable_hdpi/icon.png
在AssetManager native中去讀取資源並上傳。
那么我們需要做的是在傳入路徑去讀取資源時把文件路徑替換為/data/app/SystemTheme01.apk下的res/drawable_hdpi/icon.png
=============================涉及部分在原生系統中的邏輯及修改=================================
為了完成上篇所提到的修改,我們依上圖分析一下原生系統中從傳入資源ID到生成需要訪問的資源文件的路徑的過程:
Java層:
Resources類:
Android在Java層為應用層訪問不同類型的資源提供了一系列接口,這些接口被封裝在Resources類中,例如訪問字符串資源的接口getString(),訪問Drawable類型資源的接口getDrawable()等等。該類還管理着另外兩個重要的類:Configuration和AssetManager。
Configuration類:
Configuration類中主要保存了當前的系統配置信息,例如字體、語言等,在應用調用Resources類的接口去獲取資源的時候,Resources類會通過Configuration類來讀取當前的系統配置信息,再結合接口中收到的應用請求獲取的資源ID來獲取對應的資源。
AssetManager類:
AssetManager類提供了以數據流的方式訪問應用程序資源的方法。它主要是通過Native層的方法來實現訪問資源的。AssetManager管理的資源主要有兩個來源:一是應用程序的資源,通過訪問應用程序的APK文件來得到;另外一個是系統資源,是通過訪問包含系統資源的APK文件來獲得。
Native層:
AssetManager Native類:
AssetManager Native類提供了對資源文件路徑以及資源文件的操作支持,提供了諸如掃描APK的res路徑下的文件夾和文件、對APK進行解壓縮、維護ResTable類以及部分Cache加速文件的功能。
Asset類:
Asset類是所有類型資源類的基類,它提供了絕大部分對資源的操作實現和定義,是Android資源訪問和文件訪問層之間的橋梁,也是需要修改主干部分的核心。它封裝了整個Android資源訪問中對資源文件的路徑查找以及資源從文件到數據流的轉換過程。在應用傳入資源ID來請求資源的時候,AssetManager會使用該類的create函數去生成一個新的資源對象,並使用讀出的資源數據流賦給該對象,最后返回給上層應用。
下圖列出了系統原有邏輯及需要修改的部分:
上面的部分已經分析了資源訪問過程中需要修改的部分,但是除了訪問流程的主干之外,
系統在以下幾個部分對資源訪問效率做的優化同樣會受到我們修改資源ID對應關系的影響。
系統的預加載資源流程
原有邏輯:
系統在啟動的過程中會把系統常用的資源進行預載入,
此邏輯位於frameworks/base/core/java/com/android/internal/os/ZygoteInit.java -- preloadResources函數
在此函數中會調用Resources.java中函數來對com.android.internal.R.array.preloaded_XXX的資源數組中對應的資源進行預載入,
這個流程中會通過for循環預加載三類資源Drawable、ColorStateList、ColorDrawable,
並且存在對應的sPreloadedDrawables、sPreloadedColorStateLists、sPreloadedColorDrawables三個LongSparseArray的數組中。
修改邏輯:
因為主干部分已經對系統資源即framework-res.apk的資源進行了分別的處理,所以開機預載入的部分可以不做處理,
但是在主題變化之后,預載入的資源還是系統啟動時候的數據,所以需要對三個數組進行清除或者啟動后台服務重新進行預載入流程。
修改文件:Resources.java
實現功能:在系統使用的主題包變化之后清除預載入資源數組即上面提到的三個LongSparseArray數組(可以考慮后台重新載入新主題資源數據到數組中)。
系統訪問中的資源緩存機制(清除cache)
原有邏輯:
在系統每次訪問資源的過程中,查找資源的順序是 預載入資源->Cache資源->資源文件
並且在每次通過資源文件的方式查找到資源之后會把此資源加入到Cache中,加速下次訪問速度。
修改邏輯:
在主題設置被用戶修改之后,原有緩存中的數據是上個主題包的文件內容,需要清除。
============================= 對周邊模塊的影響和支持=======================================
另:需要提供用戶可以管理主題包的應用程序
原有邏輯:(無)
修改邏輯:
用戶在安裝了主題包之后,需要提供一個應用來維護當前系統上已經存在的資源主題包。
應用需要實現功能:掃描、預覽、應用、卸載當前手機上已經安裝的主題資源包。
主要涉及:PackageManager接口、systemProperty讀寫。
主題改變之后通知應用的廣播
原有邏輯:
系統原有通知機制是ACTION_CONFIGURATION_CHANGE的Intent來通知應用系統配置改變,
如果應用不處理,系統會默認為應用更新系統配置(Activity資源、布局等的重新載入)。
修改邏輯:
在系統判斷是否需要發送CONFIGURATION_CHANGE的邏輯中加入如下邏輯:
如果當前系統主題資源包發生變化,需要發送CONFIGURATION_CHANGE。
草草完成Demo設計和實現,僅僅算是把功能實現了,歡迎大家繼續優化、討論。