Android 在線換膚方案總結分享

文章首發(fā)于imesong的個人網站

本文主要是在公司內部的一次分享會內容,基于 Android-Skin-Loader開源庫,實現(xiàn)一套完整的在線換膚方案。同時,也參考了很多網上總結的換膚思路,后面會給出主要的參考資料。

我們先看下Demo效果圖

Demo效果圖

為什么要做在線換膚?

皮膚模塊獨立,減小 Apk 包大小

資源文件在App大小中有很大的比重,特別是工具類的App,用戶有使用多套皮膚的需求,如果多套皮膚資源全部在 App 中,會極大的增加 App 的大小,不利于用戶下載以及渠道推廣。

技術方案通用,不局限于具體的 App

現(xiàn)在公司有十幾款移動端產品,但是還沒有App使用在線換膚或者使用類似插件式的換膚方案,如果能調研出一套完整的在線換膚方案,公司的各個 App 都可以使用。

服務端控制皮膚包,動態(tài)更新,滿足運營需求

皮膚包資源放在服務端,動態(tài)更新,可以滿足運營需求,發(fā)布不同主題皮膚資源包,避免通過發(fā)布版本迭代更新。

降低維護成本

通過把皮膚模塊獨立出來,減少主工程的邏輯,精簡主工程代碼,降低維護成本。

在線換膚的難點在哪里?

調研一個技術方案,有難點,有重點。當我們把這些重點難點攻克,方案的雛形基本就完成了。

如何加載皮膚資源文件

在線換膚,皮膚資源肯定不會在Apk內部,要怎么加載外部的皮膚資源呢?下載到本地,如何加載外部的資源文件呢?
我們先看下 Apk 的打包流程。

Android打包流程

這里流程中,有兩個關鍵點
1.R文件的生成
R文件是一個Java文件,通過R文件我們就可以找到對應的資源。R文件就像一張映射表,幫助我們找到資源文件。
2.資源文件的打包生成

資源文件經過壓縮打包,生成 resources 文件,通過R文件找到里面保存的對映的資源文件
在 App 內部,我們一般通過下面代碼,獲取資源

context.getResource.getString(R.string.hello);
context.getResource.getColor(R.color.black);
context.getResource.getDrawable(R.drawable.splash);

這個時獲取 App 內部的資源,能我們家在皮膚資源什么思路嗎?加載外部資源的 Resources 能通過類似的思路嗎?
我們查看下 Resources 類的源碼,發(fā)現(xiàn) Resources 的構造函數(shù)

/**
     * Create a new Resources object on top of an existing set of assets in an
     * AssetManager.
     *
     * @param assets Previously created AssetManager.
     * @param metrics Current display metrics to consider when
     *                selecting/computing resource values.
     * @param config Desired device configuration to consider when
     *               selecting/computing resource values (optional).
     */
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
    }

這里關鍵是第一個參數(shù)如何獲取,第二和第三個參數(shù)可以通過 Activity 獲取到。
我們再去看下 AssetManager 的代碼,同時會發(fā)現(xiàn)下面的這個

/**
     * Add an additional set of assets to the asset manager.  This can be
     * either a directory or ZIP file.  Not for use by applications.  Returns
     * the cookie of the added asset, or 0 on failure.
     * {@hide}
     */
    public final int addAssetPath(String path) {
        synchronized (this) {
            int res = addAssetPathNative(path);
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }

AssetManager 可以加載一個zip 格式的壓縮包,而 Apk 文件不就是一個 壓縮包嗎。我們通過反射的方法,拿到 AssetManager,加載 Apk 內部的資源,獲取到 Resources 對象,這樣再想辦法,把 R文件里面保存的ID獲取到,這樣既可以拿到對應的資源文件了。理論上我們的思路時成立的。
我們看下,如何通過代碼獲取 Resources 對象。

AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);

Resources superRes = context.getResources();
Resources skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());

標記需要換膚的 View

找到資源文件之后,我們要接著標記需要換膚的 View 。
找到需要換膚的 View
怎么尋找哪些是我們要關注的 View 呢? 我們還是重 View 的創(chuàng)建時機尋找機會。我們添加一個布局文件時,會使用 LayoutInflater的 Inflater方法,我們看下這個方法是怎么講一個View添加到Activity 中的。
LayoutInflater 中有個接口

 public interface Factory {
        /**
         * Hook you can supply that is called when inflating from a LayoutInflater.
         * You can use this to customize the tag names available in your XML
         * layout files.
         * 
         * <p>
         * Note that it is good practice to prefix these custom names with your
         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
         * names.
         * 
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         * 
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

根據(jù)這里的注釋描述,我們可以自己實現(xiàn)這個接口,在 onCreateView 方法中選擇我們需要標記的View,根據(jù) AttributeSet 值,過濾不需要關注的View。
標記 View 與對應的資源
我們在 View 創(chuàng)建時,通過過濾 Attribute 屬性,找到我們要標記的 View ,下面我們就把這些View的屬性記下來

for (int i = 0; i < attrs.getAttributeCount(); i++){
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(i);
            if(!AttrFactory.isSupportedAttr(attrName)){
                continue;
            }  
            if(attrValue.startsWith("@")){
                try {
                    int id = Integer.parseInt(attrValue.substring(1));
                    String entryName = context.getResources().getResourceEntryName(id);
                    String typeName = context.getResources().getResourceTypeName(id);
                    SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName);
                    if (mSkinAttr != null) {
                        viewAttrs.add(mSkinAttr);
                    }
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }
            }
        }

然后把這些 View 和屬性值,一起封裝保存起來

if(!ListUtils.isEmpty(viewAttrs)){
            SkinItem skinItem = new SkinItem();
            skinItem.view = view;
            skinItem.attrs = viewAttrs;
            mSkinItems.add(skinItem);
            if(SkinManager.getInstance().isExternalSkin()){
                skinItem.apply();
            }
    }

及時更新 UI

由于我們把需要更新的View 以及屬性值都保存起來了,更新的時候只要把他們取出來遍歷一遍即可。

@Override
    public void onThemeUpdate() {
        if(!isResponseOnSkinChanging){
            return;
        }
        mSkinInflaterFactory.applySkin();
    }
//applySkin 的具體實現(xiàn)

public void applySkin(){
        if(ListUtils.isEmpty(mSkinItems)){
            return;
        }   
        for(SkinItem si : mSkinItems){
            if(si.view == null){
                continue;
            }
            si.apply();
        }
    }

制作皮膚包

皮膚包制作相對簡單
1.創(chuàng)建獨立 model,包名自定義
2.添加資源文件到 model 中,不需要 java 代碼
3.運行 build.gradle 腳本,生成 xxx.skin 皮膚包

制定一套完整的在線換膚方案

到這里之后,還是沒看到在線換膚方案啊~說好的在線換膚方案呢?
1.將制作好的皮膚包上傳到服務端后臺
2.客戶端根據(jù)接口數(shù)據(jù),處理皮膚加載邏輯

模塊依賴關系

三個模塊的依賴關系

換膚方案的介紹基本完成,下面是一些參考資料和資源

分享ppt文件

Demo源碼

參考開源資料xmind文件下載

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,176評論 25 708
  • 今天再給大家?guī)硪黄韶洝?Android的主題換膚 ,可插件化提供皮膚包,無需Activity的重啟直接實現(xiàn)無縫...
    _SOLID閱讀 100,520評論 147 1,119
  • 前言: 本文主要講述如何在項目中,在不重啟應用的情況下,實現(xiàn)動態(tài)換膚的效果。換膚這塊做的比較好的,有網易云音樂,q...
    Yagami3zZ閱讀 13,850評論 5 51
  • 情不敢至深,恐大夢一場
    念念水墨閱讀 271評論 0 0
  • 曾經 那信以為真的愛情 化作泡沫紛飛曾經 以為你就是全世界因為愛小草匍匐大地 與烈日為伴狂風中 張揚搖擺風姿清冷卓...
    秀逗茉莉閱讀 297評論 0 9

友情鏈接更多精彩內容