天天看點

Android換膚方案分析

0x0 背景

無論是出于使用者個性化的考慮,或者是不同場景下的氛圍渲染,用戶端應用存在着換膚的需求。本文舉出三種常見的換膚方案,并加以對比,以作後續參考。無論何種方案,換膚的核心都包含皮膚的管理,皮膚的加載,以及皮膚的生效。不同的方案在解決這些問題上有不同的思路。

0x1 手動重新設定UI資源

這種方式最簡單,在業務代碼裡面手動寫設定新皮膚的邏輯,當新皮膚下發時,回調該邏輯重新設定UI資源,就達到了換膚的邏輯。這種方案思路簡單,但是業務入侵的,需要手動寫代碼,有維護成本。而且由于需要手動寫重新設定UI的邏輯,是以一般不會對所有的控件都更換UI資源,是以換膚的範圍存在局限性。

public void onSkinChanged(JSONObject newSkin) {         int newTextColor = newSkin.optInt("my_text_color");         TextView myTextView = (TextView)findViewById("R.id.mytext");         myTextView.setTextColor(newTextColor);         String newImageUrl = newSkin.optString("my_img_url");         UrlImageView myImageView = (UrlImageView)findViewById("R.id.myimage");         myImageView.setImageUrl(newImageUrl);     }           

0x2 自定義資源架構

Android原生的資源都是通過

Resources

加載定義在res目錄下的xml中的資源。由于res目錄下的資源是随打包釋出的,是以無法做到動态替換。是以這裡的思路是自定義一個CustomResources,并自定義resources目錄,裡面區分default,night, custom等等。default目錄下是預設資源,其他目錄下是特定場景的皮膚資源,其id和default目錄保持一緻,就可以實作替換。

resources         |--- default                 |--- res                 |--- drawable         |--- night                 |--- res                 |--- drawable           

CustomResources可以加載APK包裡面的預設資源,也可以加載從服務端下發的資源。在應用開發過程中,不能再使用系統的Resources,必須使用CustomResources,否則無法實作換膚。

TextView myTextView = (TextView)findViewById("R.id.mytext");         myTextView.setTextColor(getCustomResrouce().getColor(R.color.mytextcolor));           

當然至此為止還不能cover布局xml中直接使用資源id的場景,如果要cover,需要hook系統resources。這種方式需要在應用開發之初就有換膚的設計,開發團隊内部約定統一用CustomResources而不用系統的資源。

0x3 Hook系統LayoutInflater

這種思路是hook系統的LayoutInflater,在解析布局xml生成View的時候,替換成繼承了換膚邏輯的子類。

ximsfei/Android-skin-support

是這種思路的代表。替換LayoutInflater的方式是:

private void installLayoutFactory(Context context) {             LayoutInflater layoutInflater = LayoutInflater.from(context);             try {                 Field field = LayoutInflater.class.getDeclaredField("mFactorySet");                 field.setAccessible(true);                 field.setBoolean(layoutInflater, false);                 LayoutInflaterCompat.setFactory(layoutInflater, getSkinDelegate(context));             } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {                 e.printStackTrace();             }         }           

的實作方案中,下發皮膚其實是在一個隻包含資源檔案的APK檔案中,保證資源APK中的資源命名和預設的保持一緻就能實作替換。讀取APK檔案中的資源的方式是:

public Resources getSkinResources(String skinPkgPath) {             try {                 AssetManager assetManager = AssetManager.class.newInstance();                 Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);                 addAssetPath.invoke(assetManager, skinPkgPath);                 Resources superRes = mAppContext.getResources();                 return new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());             } catch (Exception e) {                 e.printStackTrace();             }             return null;         }           

這種方式對于應用開發者來說幾乎是透明的,對于業務也沒有侵入性,尤其是已有的應用代碼上疊代增加換膚功能,這是一種比較好的方式。

繼續閱讀