插件化知識梳理(9) 資源的動態加載示例及源碼分析

1、前言

當須要設計一個插件化的框架,首先須要解決的是如下三個問題:緩存

  • Activity的動態註冊
  • 類的動態加載
  • 資源的動態加載

若是你們有閱讀過前面一系列的文章,那麼對於如何解決前兩個問題應該能夠有一個大概的思路了。不清楚的能夠重點看一下 插件化知識梳理(6) - Small 源碼分析之 Hook 原理插件化知識梳理(8) - 類的動態加載源碼分析。今天這篇,我就來先了解一下在Android當中資源是如何加載的。bash

2、示例

爲了讓你們有一個直觀的認識,咱們先不講源碼,而是來看一個簡單的示例,該示例演示瞭如何以插件的形式加載外部資源。app

2.1 編譯插件

這裏,咱們須要將所須要的插件資源放在一個.apk文件中,所以,咱們建立一個新的Phone & Tablet Module框架

在其中放入三個資源文件:

  • drawable 函數

  • string源碼分析

<string name="resource_str">Plug Resources String</string>
複製代碼
  • color
<color name="resource_color">#FF4081</color>
複製代碼

咱們將該Module編譯成爲resource-debug.apk文件,經過adb命令將它push到根目錄的Plugin/目錄下,至此,一個包含資源的插件就準備好了。 spa

2.2 讀取插件中資源

如今,咱們進入到宿主模塊當中,讀取這三個資源並進行展現。代碼很短,只有下面幾行:插件

private void loadResource() {
        try {
            //添加資源路徑,並建立對應的Resources對象。
            String resourcePath = Environment.getExternalStorageDirectory().toString() + "/Plugin/resource-debug.apk";
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, resourcePath);
            Resources resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
            //獲取包名信息。
            PackageInfo mInfo = getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
            //獲取到資源的ID。
            int drawableId = resources.getIdentifier("icon_book", "drawable", mInfo.packageName);
            int strId = resources.getIdentifier("resource_str", "string", mInfo.packageName);
            int colorId = resources.getIdentifier("resource_color", "color", mInfo.packageName);
            //經過資源ID獲取到對應的資源,並進行顯示。
            mImageView.setImageDrawable(resources.getDrawable(drawableId));
            mTextView.setText(resources.getText(strId));
            mTextView.setTextColor(resources.getColor(colorId));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
複製代碼

上面的邏輯爲如下幾步:debug

  • 獲取插件的路徑,也就是在2.1中所push進去的resource-debug.apk所在的路徑。
  • 經過反射建立一個AssetManager對象,調用它的addAssetPath方法,該方法的實參爲第一步中的插件路徑。
  • 利用該AssetManager對象做爲構造函數,建立一個訪問該插件資源的代理對象resources,用於插件資源的訪問。
  • 經過插件的路徑,得到插件的包名信息。
  • 經過resourcesgetIdentifier方法,根據插件資源的名字以及插件的包名獲取對應的資源Id
  • 經過resourcesgetXXX方法,傳入前一步中獲取到的資源Id,最終獲取資源,並經過控件進行展現。

最終的展現結果爲: 設計

3、源碼解析

在第二節中,咱們用一個簡單的例子,演示瞭如何以插件的形式加載外部的資源,其實,不管是加載外部資源,仍是加載宿主自己的資源,它們的原理都是相同的,只要咱們弄懂了宿主自身的資源是如何加載的,那麼對於上面的過程天然也就理解了。

Android中,當咱們須要加載一個資源時,通常都會先經過getResources()方法,獲得一個Resources對象,再經過它提供的getXXX方法獲取到對應的資源,這一過程能夠用下面這張圖來表示:

2.1 函數調用路徑

在上圖中,咱們看到一共通過了四條線路調用到ResourcesManager,下面,咱們就對這一過程進行分析:

第一步

當咱們調用在Activity/Service/Application中調用getResources()時,因爲它們都繼承於ContextWrapper,該方法就會調用到ContextWrappergetResources()方法,而該方法又會調用它內部的mBase變量的對應方法:

第二步

mBase的類型爲ContextImpl,它的getResources()方法,返回的是其內部的成員變量mResources

mResources變量是在 ContextImpl的構造函數中經過下面賦值的:
其中 packageInfo的類型爲 LoadedApkLoadedApk是對於 .apk解析的結果,它內部包含了所關聯的 ActivityThread,安裝後拷貝到的目錄,咱們在 ContextImpl中賦值的其實就是它內部的 mResources對象:

第三步

LoadedApk中會經過調用ActivityThreadgetTopLevelResources方法來爲mResources變量賦值,在調用的時候會傳入LoadedApk中的一些信息:

這裏最關鍵的是第一個變量 mRes,它是應用安裝時拷貝到 data/app/{package_name}下的完整路徑,其它的變量,你們能夠參考斷點中的截圖,這裏就很少分析是怎麼得到的了。

第四步

通過上面的步驟,最終會調用到ActivityThread中的getTopResources方法,在該方法中會經過ResourcesManager去尋找訪問資源的對應代理對象Resources

以上就是從 Activity調用 getResources()方法,到 ResourcesManager根據應用的信息去查找訪問資源的代理對象的調用過程,下面,咱們就來看一下 ResourcesManager是如何管理這些 Resources對象的。

2.2 ResourceManager

2.2.1 ResourceManager 對於 Resources 對象的管理

ResourceManager採用了單例的模式,所以一個進程當中只會有一個對象,它主要負責管理應用程序當中的Resources對象,在它的內部有如下兩個關鍵的成員變量:

當咱們調用 ResourcesManagergetResources方法以後,他就會根據傳入的參數建立一個 ResourcesKey,並經過該對象中的屬性做爲索引值,首先查找在上面的緩存中是否已經有對應的 Resources對象了,若是有,那麼就直接返回,不然再建立一個 Resources對象。

2.2.2 ResourcesKey && ResourcesImpl && Resources && AssetManager

Resources其實只是一個代理對象,它內部涉及到的類包括:

  • ResourcesKey:做爲緩存的Key,也就是說對於一個應用程序,能夠保存不一樣的Resource,是否返回以前的Resources對象,取決於ResourcesKeyequals方法是否相等:
  • ResourcesImpl:資源訪問的實現類,其內部包含了一個AssetManager,全部資源的訪問都是經過它的Native方法來實現的。
  • ResourcesResourcesImpl的代理類,對於資源的使用者來講,看到的是Resources接口,其實在構建Resources對象時,同時也會建立一個ResourcesImpl對象做爲它的成員變量,Resources會調用它來去獲取資源。
  • AssetManager:做爲資源獲取的執行者,它是ResourcesImpl的內部成員變量。

也就是說,資源的訪問最終是由AssetManager來完成,在AssetManager的建立過程當中咱們首先告訴它資源所在的路徑,以後它就會去如下的幾個地方查看資源,這裏面咱們看到了第二節中經過反射調用的addAssetPath。動態加載資源的關鍵,就是如何把包含資源的插件路徑添加到AssetManager當中。


更多文章,歡迎訪問個人 Android 知識梳理系列:

相關文章
相關標籤/搜索