大話插件化系列目錄
插件化(一) 插件化思想與類加載
插件化(二) 插件化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
思路
final ResourcesKey key = new ResourcesKey(
resDir, --- 資源文件
assets.addAssetPath(key.mResDir) --- 把宿主的資源 添加到集合
宿主的代碼 --- dexElements
assets.addAssetPath(插件的資源)--- 插件的資源添加到集合
使用資源 ---- 直接使用插件的資源
實(shí)現(xiàn)方式:
- 插件的資源和宿主的資源直接合并 -- 資源沖突 0x7f0a000a -- aapt 7f -- 70~7e ~ff
2.專門創(chuàng)建一個(gè)(Resource)AssetManger 加載插件的資源
問題:
資源沖突
無論宿主或者插件
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ì)鹿鳖,除非我們自己再插件里面寫的
我們是在宿主獲取的插件資源
宿主---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)生了
- 插件的資源加載影像了宿主的資源 ---- 影像了宿主
- 宿主和插件的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 打包流程
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)存一樣。
官方的流程
可以framework 修改 aapt诈唬,但是我們一般不會(huì)這么做韩脏。
aapt 代碼流程
回到問題分析思路。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流程
動(dòng)態(tài)代理初始化流程