插件化(三) 插件資源加載

大話插件化系列目錄
插件化(一) 插件化思想與類加載
插件化(二) 插件化Activity的啟動(dòng)
插件化(三) 插件資源加載

常識(shí)回顧

raw文件夾和assets文件夾有什么區(qū)別

aw : Android會(huì)自動(dòng)的為這目錄中的所有資源文件生成一個(gè)ID,這意味著很容易就可以訪問到這個(gè)資源袍榆,甚至在xml 中都是可以訪問的兔魂,使用ID訪問速度是最快的奔则。

assets : 不會(huì)生成ID,只能通過AssetManager訪問展父,xml中不能訪問,訪問速度會(huì)慢些幽告,不過操作更加方便槽唾。

宿主的資源如何加載

在項(xiàng)目中,我們一般通過 Resources 去訪問 res 中的資源之斯,使用 AssetManager 訪問 assets 里面的資源。

       String appName = getResources().getString(R.string.app_name);
        InputStream is = getAssets().open("icon.png");

實(shí)際上遣铝,Resources 類也是通過 AssetManager 類來訪問那些被編譯過的應(yīng)用程序資源文件的佑刷,不過在訪問之前,

AssertManager ---> Resource ---> Context
我們可以通過
Application
Activity
Service
里面去找資源的創(chuàng)建流程

為了方便酿炸,我們先拿API 26 開刀

ActivityThread#handleLaunchActivity ---> ActivityThread#performLaunchActivity --> ActivityThread#createBaseContextForActivity 

---> ContextImpl#createActivityContext

--->ResourcesManager#createBaseActivityResources --> ResourcesManager#getOrCreateResources --> ResourcesManager#createResourcesImpl --> ResourcesManager#createAssetManager-->ResourcesManager#assets.addAssetPath

--->Instrumentation
加載資源.png

思路

final ResourcesKey key = new ResourcesKey(
resDir, --- 資源文件

assets.addAssetPath(key.mResDir) --- 把宿主的資源 添加到集合
宿主的代碼 --- dexElements

assets.addAssetPath(插件的資源)--- 插件的資源添加到集合
使用資源 ---- 直接使用插件的資源

實(shí)現(xiàn)方式:

  1. 插件的資源和宿主的資源直接合并 -- 資源沖突 0x7f0a000a -- aapt 7f -- 70~7e ~ff

2.專門創(chuàng)建一個(gè)(Resource)AssetManger 加載插件的資源

問題:

資源沖突

resources.arsc.png

無論宿主或者插件
7f: apk 包的id
0e: 資源類型的 id --- 從01 ++
000a : 同一類型下的 id ---- 從0000 ++

解決:宿主和插件同一個(gè)Resource瘫絮。

宿主和插件分開,插件啟動(dòng)自己主動(dòng)調(diào)用一下填硕,宿主不操作

資源沖突解決思路

嘗試思路 1

加載插件資源Resource

public static Resources loadResource(Context context){
        // assets.addAssetPath(key.mResDir) 源碼
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            // 讓assetManager 對(duì)象加載的資源是插件
            Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager, apkPath);

            Resources resources = context.getResources();

            return new Resources(assetManager,resources.getDisplayMetrics(),resources.getConfiguration());
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

如何把資源放到插件中

利用之前系列文章的介紹

我們用雙親委派類加載的時(shí)候

Application --- BootClassLoader 加載

而我們運(yùn)行的只有宿主的麦萤,插件的不會(huì)鹿鳖,除非我們自己再插件里面寫的

沖突原因.png

我們是在宿主獲取的插件資源

宿主---Application 重寫getResources 拿資源

public
class MyApplication extends Application {

    private Resources mResources;

    @Override
    public void onCreate() {
        super.onCreate();
        LoadUtil.load(this);


        mResources = LoadUtil.loadResource(this);


        HookUtils.hookAMS();
        HookUtils.hookHandler();
    }

    // TODO: 2020/12/1 嘗試插件資源的加載
    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }
}

插件 BaseActivity extends Activity

public
class BaseActivity extends Activity {

    @Override
    public Resources getResources() {
        if (getApplication() != null && getApplication().getResources() != null) {
            return getApplication().getResources();
        }
        return super.getResources();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

-------
使用它,打開我們之前注釋的setContentView


//public class MainActivity extends AppCompatActivity {
public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
// TODO: 1.測(cè)試啟動(dòng)Activity 加載資源先注釋

        // TODO: 2020/12/1 啟動(dòng)資源
                setContentView(R.layout.activity_main);



        Log.e("zcw_plugin" , "onCreate()壮莹,啟動(dòng)插件的Activity");

        Log.e("zcw_plugin" , "插件application:" + getApplication());
    }
}

啟動(dòng)后的打印

2020-12-01 18:49:06.681 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 動(dòng)態(tài)代理hookAMS
2020-12-01 18:49:06.692 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 反射hookHandler
2020-12-01 18:49:06.695 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:49:07.116 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 宿主application:top.zcwfeng.zcwplugin.MyApplication@65c3245
2020-12-01 18:53:04.936 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:04.989 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:05.053 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:05.259 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: onCreate()翅帜,啟動(dòng)插件的Activity
2020-12-01 18:53:05.259 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 插件application:top.zcwfeng.zcwplugin.MyApplication@65c3245
2020-12-01 18:53:06.869 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0

證明我們的插件和宿主Application是一個(gè)。

在宿主的MainActiity 中g(shù)etRsource 拿到的資源是插件的

那面問題產(chǎn)生了

  1. 插件的資源加載影像了宿主的資源 ---- 影像了宿主
  2. 宿主和插件的Application 是同一個(gè) ---- 插件自定義的Application不會(huì)執(zhí)行

所以我們吧loadUtils 放在插件中命满。創(chuàng)建一個(gè)Resource
在插件中g(shù)etResource 主動(dòng)加載一下

插件:BaseActivity

public
class BaseActivity extends Activity {


    @Override
    public Resources getResources() {
        // TODO: 2020/12/1 測(cè)試方案一
//        if (getApplication() != null && getApplication().getResources() != null) {
//            return getApplication().getResources();
//        }
//        return super.getResources();


        // TODO: 2020/12/1 方案二
        Resources resources = LoadUtils.getResources(getApplication());
        // 如果插件 是單獨(dú)的app 那么 super.getResources()
        return resources == null ? super.getResources() : resources;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

插件加載資源

public
class LoadUtils {

    private final static String apkPath = "/sdcard/plugin-debug.apk";

    private static Resources mResource;

    public static Resources getResources(Context context) {
        if (mResource == null) {
            mResource = loadResource(context);
        }
        return mResource;
    }

    public static Resources loadResource(Context context) {
        // assets.addAssetPath(key.mResDir) 源碼
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            // 讓assetManager 對(duì)象加載的資源是插件
            Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager, apkPath);

            Resources resources = context.getResources();
            // 加載插件資源Resource
            return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

將插件 apk 放入 我們程序中的路徑涝滴,驗(yàn)證是可以的,但是還是會(huì)有問題胶台。接著分析

AAPT 打包流程

apk打包流程.png

Application Module
Dependencies
compiles
簽名
對(duì)齊

R.java ----> java Compiler ---> class es ----> dex ----> apkbuilder
aidl
sourcecode

AAPT---->Compile Resource---->apkbuilder---->jarsigner---->sign->zipalign(4K對(duì)齊)

.ap_文件
zipalign: 節(jié)約 RAM內(nèi)存歼疮。 運(yùn)行塊----對(duì)齊后可以用mmap可以和讀取內(nèi)存一樣。

官方的流程

apk打包流程2.png

可以framework 修改 aapt诈唬,但是我們一般不會(huì)這么做韩脏。

aapt 代碼流程

aapt代碼流程.png

回到問題分析思路。BaseActivity我們之前extends 的Activity铸磅,現(xiàn)在改成AppCompactActivity赡矢,發(fā)現(xiàn)報(bào)錯(cuò)

2020-12-01 22:48:23.865 13708-13708/top.zcwfeng.zcwplugin W/wfeng.zcwplugi: Accessing hidden method Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I (light greylist, reflection)
2020-12-01 22:48:23.869 13708-13708/top.zcwfeng.zcwplugin W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
2020-12-01 22:48:23.876 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.LoadUtils.loadResource(LoadUtils.java:31)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.LoadUtils.getResources(LoadUtils.java:18)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.BaseActivity.getResources(BaseActivity.java:23)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.view.Window.getDefaultFeatures(Window.java:1704)
2020-12-01 22:48:23.880 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.view.Window.<init>(Window.java:671)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.<init>(PhoneWindow.java:304)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.<init>(PhoneWindow.java:313)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.Activity.attach(Activity.java:7055)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2873)
2020-12-01 22:48:23.890 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
2020-12-01 22:48:23.890 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
2020-12-01 22:48:23.891 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
2020-12-01 22:48:23.891 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
2020-12-01 22:48:23.896 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
2020-12-01 22:48:23.900 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.os.Looper.loop(Looper.java:193)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6669)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2020-12-01 22:48:23.902 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
2020-12-01 22:48:23.902 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
-----------------------------------------------------------

2020-12-01 22:48:24.078 13708-13708/top.zcwfeng.zcwplugin D/AppCompatDelegate: Exception while getting ActivityInfo
    android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{top.zcwfeng.zcwplugin/top.zcwfeng.plugin.MainActivity}
        at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
        at androidx.appcompat.app.AppCompatDelegateImpl.isActivityManifestHandlingUiMode(AppCompatDelegateImpl.java:2649)
        at androidx.appcompat.app.AppCompatDelegateImpl.updateForNightMode(AppCompatDelegateImpl.java:2499)
        at androidx.appcompat.app.AppCompatDelegateImpl.applyDayNight(AppCompatDelegateImpl.java:2374)
        at androidx.appcompat.app.AppCompatDelegateImpl.onCreate(AppCompatDelegateImpl.java:494)
        at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:114)
        at top.zcwfeng.plugin.BaseActivity.onCreate(BaseActivity.java:30)
        at top.zcwfeng.plugin.MainActivity.onCreate(MainActivity.java:11)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

如何解決呢沖突?

aapt --- 單獨(dú)給插件創(chuàng)建一個(gè) Resource --- 都會(huì)產(chǎn)生

都是宿主的 context --- 插件自己創(chuàng)建一個(gè) context -- 綁定 啟動(dòng)插件資源的 Resource

再次修改插件的BaseActivity

public
class BaseActivity extends AppCompatActivity {
    // TODO: 2020/12/1 測(cè)試方案三
    protected Context context;

//    @Override
//    public Resources getResources() {
        // TODO: 2020/12/1 測(cè)試方案一
//        if (getApplication() != null && getApplication().getResources() != null) {
//            return getApplication().getResources();
//        }
//        return super.getResources();


        // TODO: 2020/12/1 方案二
//        Resources resources = LoadUtils.getResources(getApplication());
//        // 如果插件 是單獨(dú)的app 那么 super.getResources()
//        return resources == null ? super.getResources() : resources;
//    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Resources resources = LoadUtils.getResources(getApplication());
        // TODO: 2020/12/1 方案三 創(chuàng)建context愚屁,替換resource
        context = new ContextThemeWrapper(getBaseContext(),0);
        Class<? extends Context> clazz = context.getClass();
        try {
            Field mResourcesField = clazz.getDeclaredField("mResources");
            mResourcesField.setAccessible(true);
            mResourcesField.set(context, resources);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

使用的時(shí)候不再用setContentView(R.layout.main) 因?yàn)橐怯梦覀冏约旱牟寮Y源
插件MainActivity

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
// TODO: 1.測(cè)試啟動(dòng)Activity 加載資源先注釋

        // TODO: 2020/12/1 啟動(dòng)資源
//                setContentView(R.layout.activity_main);



        Log.e("zcw_plugin" , "onCreate()济竹,啟動(dòng)插件的Activity");

        Log.e("zcw_plugin" , "插件application:" + getApplication());

        View view = LayoutInflater.from(context).inflate(R.layout.activity_main, null);
        setContentView(view);

    }
}

----> context 是我們自己創(chuàng)建的

DroidPlugin 分析

因?yàn)楹芫脹]更新,我們只能在6.0上看霎槐,學(xué)習(xí)他的思想

替換系統(tǒng)IActivityManager流程

替換系統(tǒng)IActivityManager流程.png

動(dòng)態(tài)代理初始化流程

動(dòng)態(tài)代理初始化流程.png

可以參考項(xiàng)目

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載送浊,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末丘跌,一起剝皮案震驚了整個(gè)濱河市袭景,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闭树,老刑警劉巖耸棒,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異报辱,居然都是意外死亡与殃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門碍现,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幅疼,“玉大人,你說我怎么就攤上這事昼接∷瘢” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵慢睡,是天一觀的道長逐工。 經(jīng)常有香客問我铡溪,道長,這世上最難降的妖魔是什么泪喊? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任棕硫,我火速辦了婚禮,結(jié)果婚禮上窘俺,老公的妹妹穿的比我還像新娘饲帅。我一直安慰自己,他們只是感情好瘤泪,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布灶泵。 她就那樣靜靜地躺著,像睡著了一般对途。 火紅的嫁衣襯著肌膚如雪赦邻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天实檀,我揣著相機(jī)與錄音惶洲,去河邊找鬼。 笑死膳犹,一個(gè)胖子當(dāng)著我的面吹牛恬吕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播须床,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼铐料,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了豺旬?” 一聲冷哼從身側(cè)響起钠惩,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎族阅,沒想到半個(gè)月后篓跛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坦刀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年愧沟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲤遥。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡央渣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渴频,到底是詐尸還是另有隱情,我是刑警寧澤北启,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布卜朗,位于F島的核電站拔第,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏场钉。R本人自食惡果不足惜蚊俺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逛万。 院中可真熱鬧泳猬,春花似錦、人聲如沸宇植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽指郁。三九已至忙上,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闲坎,已是汗流浹背疫粥。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腰懂,地道東北人梗逮。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像绣溜,于是被迫代替她去往敵國和親慷彤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容