大话插件化系列目录
插件化(一) 插件化思想与类加载
插件化(二) 插件化Activity的启动
插件化(三) 插件资源加载
常识回顾
raw文件夹和assets文件夹有什么区别
aw : Android会自动的为这目录中的所有资源文件生成一个ID,这意味着很容易就可以访问到这个资源,甚至在xml 中都是可以访问的,使用ID访问速度是最快的。
assets : 不会生成ID,只能通过AssetManager访问,xml中不能访问,访问速度会慢些,不过操作更加方便。
宿主的资源如何加载
在项目中,我们一般通过 Resources 去访问 res 中的资源,使用 AssetManager 访问 assets 里面的资源。
String appName = getResources().getString(R.string.app_name);
InputStream is = getAssets().open("icon.png");
实际上,Resources 类也是通过 AssetManager 类来访问那些被编译过的应用程序资源文件的,不过在访问之前,
AssertManager ---> Resource ---> Context
我们可以通过
Application
Activity
Service
里面去找资源的创建流程
为了方便,我们先拿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(插件的资源)--- 插件的资源添加到集合
使用资源 ---- 直接使用插件的资源
实现方式:
- 插件的资源和宿主的资源直接合并 -- 资源冲突 0x7f0a000a -- aapt 7f -- 70~7e ~ff
2.专门创建一个(Resource)AssetManger 加载插件的资源
问题:
资源冲突
无论宿主或者插件
7f: apk 包的id
0e: 资源类型的 id --- 从01 ++
000a : 同一类型下的 id ---- 从0000 ++
解决:宿主和插件同一个Resource。
宿主和插件分开,插件启动自己主动调用一下,宿主不操作
资源冲突解决思路
尝试思路 1
加载插件资源Resource
public static Resources loadResource(Context context){
// assets.addAssetPath(key.mResDir) 源码
try {
AssetManager assetManager = AssetManager.class.newInstance();
// 让assetManager 对象加载的资源是插件
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;
}
如何把资源放到插件中
利用之前系列文章的介绍
我们用双亲委派类加载的时候
Application --- BootClassLoader 加载
而我们运行的只有宿主的,插件的不会,除非我们自己再插件里面写的
我们是在宿主获取的插件资源
宿主---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.测试启动Activity 加载资源先注释
// TODO: 2020/12/1 启动资源
setContentView(R.layout.activity_main);
Log.e("zcw_plugin" , "onCreate(),启动插件的Activity");
Log.e("zcw_plugin" , "插件application:" + getApplication());
}
}
启动后的打印
2020-12-01 18:49:06.681 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 动态代理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(),启动插件的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是一个。
在宿主的MainActiity 中getRsource 拿到的资源是插件的
那面问题产生了
- 插件的资源加载影像了宿主的资源 ---- 影像了宿主
- 宿主和插件的Application 是同一个 ---- 插件自定义的Application不会执行
所以我们吧loadUtils 放在插件中。创建一个Resource
在插件中getResource 主动加载一下
插件:BaseActivity
public
class BaseActivity extends Activity {
@Override
public Resources getResources() {
// TODO: 2020/12/1 测试方案一
// if (getApplication() != null && getApplication().getResources() != null) {
// return getApplication().getResources();
// }
// return super.getResources();
// TODO: 2020/12/1 方案二
Resources resources = LoadUtils.getResources(getApplication());
// 如果插件 是单独的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 对象加载的资源是插件
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 放入 我们程序中的路径,验证是可以的,但是还是会有问题。接着分析
AAPT 打包流程
Application Module
Dependencies
compiles
签名
对齐
R.java ----> java Compiler ---> class es ----> dex ----> apkbuilder
aidl
sourcecode
AAPT---->Compile Resource---->apkbuilder---->jarsigner---->sign->zipalign(4K对齐)
.ap_文件
zipalign: 节约 RAM内存。 运行块----对齐后可以用mmap可以和读取内存一样。
官方的流程
可以framework 修改 aapt,但是我们一般不会这么做。
aapt 代码流程
回到问题分析思路。BaseActivity我们之前extends 的Activity,现在改成AppCompactActivity,发现报错
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.(Window.java:671)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err: at com.android.internal.policy.PhoneWindow.(PhoneWindow.java:304)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err: at com.android.internal.policy.PhoneWindow.(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 --- 单独给插件创建一个 Resource --- 都会产生
都是宿主的 context --- 插件自己创建一个 context -- 绑定 启动插件资源的 Resource
再次修改插件的BaseActivity
public
class BaseActivity extends AppCompatActivity {
// TODO: 2020/12/1 测试方案三
protected Context context;
// @Override
// public Resources getResources() {
// TODO: 2020/12/1 测试方案一
// if (getApplication() != null && getApplication().getResources() != null) {
// return getApplication().getResources();
// }
// return super.getResources();
// TODO: 2020/12/1 方案二
// Resources resources = LoadUtils.getResources(getApplication());
// // 如果插件 是单独的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 方案三 创建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();
}
}
}
使用的时候不再用setContentView(R.layout.main) 因为要是用我们自己的插件资源
插件MainActivity
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: 1.测试启动Activity 加载资源先注释
// TODO: 2020/12/1 启动资源
// setContentView(R.layout.activity_main);
Log.e("zcw_plugin" , "onCreate(),启动插件的Activity");
Log.e("zcw_plugin" , "插件application:" + getApplication());
View view = LayoutInflater.from(context).inflate(R.layout.activity_main, null);
setContentView(view);
}
}
----> context 是我们自己创建的
DroidPlugin 分析
因为很久没更新,我们只能在6.0上看,学习他的思想
替换系统IActivityManager流程
动态代理初始化流程
可以参考项目