HookAMS 使用的是动态代理,Hook Handler 使用的是反射。
Android 10 的 AMS 变成 ATMS ,Singleton 貌似也变了,需要注意适配。Handler 的话,由于原理没变,一般不需要另行适配
二、资源加载
我们加载资源就关注2个地方:
资源替换有2种可选方案:
2.1 创建 Resources 对象
首先通过 class.newInstance (这是因为 AssetManager 不能直接去new ,hide 的)的方式创建一个 AssetManager 对象 myAssetManager,然后通过 addAssetPath 方法将插件的路径给加进去。之后new 出来 Resources 对象,传入这个创建的 myAssetManager 即可。
那么,怎么让插件的 Activity 去用上这个 Resources 对象呢?我们可以借助 Application 去实现这个事情,分为以下几步:
在宿主中定义 Application ,并且其 getResource 返回我们自己创建的 Resource 对象
插件 Activity 都继承 BaseActivity
在插件的 BaseActivity 中,getResource 返回 getApplication.getResource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyApplication extends Application {
private Resources mResources;
@Override public void onCreate() { super.onCreate(); mResources = LoadUtil.loadResource(this); }
@Override public Resources getResources() { return mResources == null ? super.getResources() : mResources; } }
|
1 2 3 4 5 6 7 8 9 10 11
| public class BaseActivity extends AppCompatActivity {
@Override public Resources getResources() { if (getApplication() != null && getApplication().getResources() != null) { return getApplication().getResources(); } return super.getResources(); } }
|
但是,这样对 宿主 App 是会有影响的,我们能不能插件自己的资源插件自己去加载?并且我们知道,插件的 Application 是不会被执行的。为了不影响宿主,我们可以在 BaseActivity 中去做 Resource 的加载:
1 2 3 4 5 6 7 8 9
| public class BaseActivity extends AppCompatActivity {
@Override public Resources getResources() { Resources resources = LoadUtil.getResources(getApplication()); return resources == null ? super.getResources() : resources; } }
|
这里要尤其注意的是,LoadUtil.getResouces(context) 的时候一定要传入 Application 这个Context, 如果传入this 也就是当前 Activity 的Context ,就会导致循环调用了。 看下 LoadUtil 的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class LoadUtil {
private final static String apkPath = "/sdcard/plugin-debug.apk";
private static Resources mResources;
public static Resources getResources(Context context) { if (mResources == null) { mResources = loadResource(context); } return mResources; }
private static Resources loadResource(Context context) { try { AssetManager assetManager = AssetManager.class.newInstance(); 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; } }
|
2.2 冲突导致崩溃
不过,这里要提一嘴,我们通过插件的 BaseActivity 这种方式能够完成插件资源加载并且不报错,是因为我们的 BaseActivity 继承了 Activity ,如果我们继承了 AppcompatActivity 的话,就会产生崩溃了,会提示这段代码:
1 2 3
| mDecorContentParent = (DecorContentParent) subDecor .findViewById(R.id.decor_content_parent); mDecorContentParent.setWindowCallback(getWindowCallback());
|
其中的 mDecorContentParent 为 null ,为什么会这样呢?我们可以看打出的包中,我们知道,R.id.xxx 最终在打包好的 Apk 中是 数值存在的,一般是 0x7fxxxxx之类的,我们可以看下宿主APk 和 插件 APk 中 R.id.decor_content_parent 最终的数值:
// 宿主的
0x7f07004e decor_content_parent false
// 插件的
0x7f07004d decor_content_parent false
所以,在 宿主中,编译后应该是这样的:
1 2
| mDecorContentParent = (DecorContentParent) subDecor .findViewById(0x7f07004e);
|
在插件中应该是这样的:
1 2
| mDecorContentParent = (DecorContentParent) subDecor .findViewById(0x7f07004d);
|
说明二者是不一样的值,一个是 4e ,一个是 4d 。而由于双亲委派的原因,这个AppcompatActivity 用的是宿主的,所以就会产生问题。注意,这个问题不论是aapt 修改还是new 出来 Resourse(AssetManger)的方式,都会出现。
这里还是没怎么搞懂,讲道理因为宿主有 0x7f07004e ,那么插件在运行的时候也能找到这个 0x7f07004e 这个资源。
老师说是因为 插件用的还是宿主那个 Context 导致的,所以需要在插件 BaseActivity 中创建自己的 Context (注意,这个操作不会影响宿主的,指挥对插件有影响):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class BaseActivity extends AppCompatActivity {
protected Context mContext; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Resources resources = LoadUtil.getResources(getApplication());
mContext = new ContextThemeWrapper(getBaseContext(), 0);
Class<? extends Context> clazz = mContext.getClass(); try { Field mResourcesField = clazz.getDeclaredField("mResources"); mResourcesField.setAccessible(true); mResourcesField.set(mContext, resources); } catch (Exception e) { e.printStackTrace(); } } }
|
然后,我们在插件的 Activity 中都要用这个创建出来的 mContext 去加载资源:
1 2 3 4 5 6 7 8 9 10 11
| public class MainActivity extends BaseActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("leo", "onCreate: 启动插件的Activity");
View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null); setContentView(view); } }
|
这个看起来很麻烦,还需要自己创建Context ,那么为什么步通过反射替换 Activity 的Context 呢?这还是因为双亲委派操作,如果你替换了 Activity 的,根据双亲委派原则,宿主也会受影响了。
2.3 宿主与插件的资源合并
只要不与系统的 id 冲突,可以自己去设置id ,你都可以 0x10 开头,不过我们约定一般是 0x70 等开始,到 0x7e
插件化,建议宿主的 dex 文件在前面,不然如果插件的代码有问题,就把宿主也带崩了。所以,宿主先加载会好点。