private void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains(“lbe”)) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
// 用于处理替换 Activity 的名称
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
// Hook Instrumentation 替换 Activity 名称
ReflectUtil.setInstrumentation(activityThread, instrumentation);
// Hook handleLaunchActivity
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
上面我们已经成功的 Hook 了 Instrumentation ,接下来就是需要我们的李鬼上场了
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// 只有是插件中的Activity 才进行替换
if (intent.getComponent() != null) {
Log.i(TAG, String.format(“execStartActivity[%s : %s]”, intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// 使用"李鬼"进行替换
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
}
我们来看一看 markIntentIfNeeded(intent);
到底做了什么
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
// 保存我们原有数据
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
}
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
ActivityInfo info = loadedPlugin.getActivityInfo(component);
// 判断是否是插件中的Activity
if (info == null) {
throw new RuntimeException("can not find " + component);
}
int launchMode = info.launchMode;
// 并入主题
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
// 将插件中的 Activity 替换为占坑的 Activity
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
Log.i(TAG, String.format(“dispatchStubActivity,[%s -> %s]”, targetClassName, stubActivity));
intent.setClassName(mContext, stubActivity);
}
可以看到上面将我们原本的信息保存至 Intent 中,然后调用了 getStubActivity(targetClassName, launchMode, themeObj);
进行了替换
public static final String STUB_ACTIVITY_STANDARD = “%s.A
p
u
b
l
i
c
s
t
a
t
i
c
f
i
n
a
l
S
t
r
i
n
g
S
T
U
B
A
C
T
I
V
I
T
Y
S
I
N
G
L
E
T
O
P
=
"
%d"; public static final String STUB_ACTIVITY_SINGLETOP = "%s.B
publicstaticfinalStringSTUBACTIVITYSINGLETOP="%d”;
public static final String STUB_ACTIVITY_SINGLETASK = “%s.C
p
u
b
l
i
c
s
t
a
t
i
c
f
i
n
a
l
S
t
r
i
n
g
S
T
U
B
A
C
T
I
V
I
T
Y
S
I
N
G
L
E
I
N
S
T
A
N
C
E
=
"
%d"; public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D
publicstaticfinalStringSTUBACTIVITYSINGLEINSTANCE="%d”;
public String getStubActivity(String className, int launchMode, Theme theme) {
String stubActivity= mCachedStubActivity.get(className);
if (stubActivity != null) {
return stubActivity;
}
TypedArray array = theme.obtainStyledAttributes(new int[]{
android.R.attr.windowIsTranslucent,
android.R.attr.windowBackground
});
boolean windowIsTranslucent = array.getBoolean(0, false);
array.recycle();
if (Constants.DEBUG) {
Log.d(“StubActivityInfo”, "getStubActivity, is transparent theme ? " + windowIsTranslucent);
}
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
switch (launchMode) {
case ActivityInfo.LAUNCH_MULTIPLE: {
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
if (windowIsTranslucent) {
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);
}
break;
}
case ActivityInfo.LAUNCH_SINGLE_TOP: {
usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_TASK: {
usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {
usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);
break;
}
default:break;
}
mCachedStubActivity.put(className, stubActivity);
return stubActivity;
}
StubActivityInfo 根据同的 launchMode
启动相应的“李鬼” Activity 至此,我们已经成功的 欺骗了 AMS ,启动了我们占坑的 Activity 但是只成功了一半,为什么这么说呢?因为欺骗过了 AMS,AMS 执行完成后,最终要启动的并非是占坑的 Activity ,所以我们还要能正确的启动目标Activity。
我们在 Hook Instrumentation 的同时一并 Hook 了 handleLaunchActivity,所以我们之间到 Instrumentation 的 newActivity 方法查看启动 Activity 的流程。
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
// 是否能直接加载,如果能就是宿主中的 Activity
cl.loadClass(className);
} catch (ClassNotFoundException e) {
// 取得正确的 Activity
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
String targetClassName = PluginUtil.getTargetActivity(intent);
Log.i(TAG, String.format(“newActivity[%s : %s]”, className, targetClassName));
// 判断是否是 VirtualApk 启动的插件 Activity
if (targetClassName != null) {
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
// 启动插件 Activity
activity.setIntent(intent);
try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, “mResources”, plugin.getResources());
} catch (Exception ignored) {
// ignored.
}
return activity;
}
}
// 宿主的 Activity 直接启动
return mBase.newActivity(cl, className, intent);
}
好了,到此Activity就可以正常启动了。
小结
VritualApk 整理思路很清晰,在这里我们只介绍了 Activity 的启动方式,感兴趣的同学可以去网上了解下其余三大组建的代理方式。不论如何如果想使用插件化框架,一定要了解其中的实现原理,文档上描述的并不是所有的细节,很多一些属性什么的,以及由于其实现的方式造成一些特性的不支持。
引入插件化之痛
由于项目的宿主与插件需要进行较为紧密的交互,在插件化的同时需要对项目进行模块化,但是模块化并不能一蹴而就,在模块化的过程中经常出现,牵一发而动全身的问题,在经历过无数个通宵的夜晚后,我总结出了模块化的几项准则。
VirtualAPK 本身的使用并不困难,困难的是需要逐步整理项目的模块,在这期间问题百出,因为自身没有相关经验在网上看了很多关于模块化的文章,最终我找到有赞模块化的文章,对他们总结出来的经验深刻认同。
在项目模块化时应该遵循以下几个准则
- 确定业务逻辑边界
- 模块的更改上保持克制
- 公共资源及时抽取
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
- Android进阶学习全套手册
关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。
-
Android高级架构师进阶知识体系图
关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!
-
Android对标阿里P7学习视频
- BATJ大厂Android高频面试题
这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
RC-1712003566164)]
- Android对标阿里P7学习视频
[外链图片转存中…(img-RfnRBbrM-1712003566165)]
- BATJ大厂Android高频面试题
这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
[外链图片转存中…(img-Wz3GTI6x-1712003566165)]