序:
1.本文是安卓插件化课程的第二篇,完整课程链接参见下面链接:
2.主要内容
本篇主要讲的是如何启动一个插件中的Activity(有前提:前提是activity没有引用插件中的资源,并且提前已经在宿主中注册了)。
一:原理简述
1.activity启动流程简述
启动流程不是本篇介绍的主要核心,所以这里简略的说一下启动流程。
1.Activity.startActivity -> Instrumetation.execStartActivity -> 通过binder机制通知AMS。
2.AMS收到通知后,进行一系列的检查,最后也通过binder通知APP去执行创建等一系列的操作。APP端的binder实体类就是ActivityThread中的ApplicationThread。
这一系列的检查中,就包含mainfest的声明检查,本篇中我们先不hook,下一篇再讲。
3.ApplicationThread收到通知后,调用ActivityThread中的方法去完成Activity的创建。
2.系统是如何生成activity的
1.而处理创建activity的方法是handleLaunchActivity,它交给performLaunchActivity来完成最终的创建。
2.我们看一下最终创建的代码,传入classLoader,交给Instrumetation完成Activity的创建。
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
3.newActivity方法
这里又通过Factory工厂类去完成创建
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null
? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
这里最终的工厂类是AppComponentFactory,对应的方法如下:
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
我可以看到,直接用传入的classloader加载类,然后进行初始化。
所以,我们可以这样认为,最终使用的classloader就是appContext.getClassloader返回的。
4.classLoader来源
@Override
public ClassLoader getClassLoader() {
return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}
我们可以看到,classLoader的来源其实是LoadedApk中的getClassLoader()方法。
我们接着看一下LoadedApk类中的getClassLoader方法。
@UnsupportedAppUsage
public ClassLoader getClassLoader() {
synchronized (mLock) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}
所以,最终返回的classLoader,就是LoadedApk类中的mClassLoader。
3.如何让系统加载插件的Activity
所以如果我们能替换掉LoadedApk类中的mClassLoader,这样在系统生成activity的时候,就可以通过我们自定义的ClassLoader去加载我们插件中的activity类,这样就可以实现加载插件中activity的目标了。
同时我们自定义的ClassLoader的parent指向PathClassLoader,这样自定义ClassLoader找不到仍会去PathClassLoader里面找,所以宿主的正常使用时也不会有问题。
有了这个理论基础,那我们就开始代码编写去尝试实现了。
4.如何hook类LoadedApk中的mClassLoader
LoaderApk是一个对象,我们如何拿到这个对象呢?
入口方法一定是先找一个静态对象进行hook。这里我们找到的这个静态对象就是ActivityThread中的sCurrentActivityThread,首先通过反射拿到这个ActivityThread对象,然后获取ActivityThread对象的成员变量mPackages的值。
mPackages是一个map,key为包名,value为LoaderApk对象。所以我们只要获取mPackages的对象,然后通过包名,就能拿到LoaderApk对象。而这个对象中的mClassLoader,就是最终加载Activity的。
二:代码编写
1.插件项目中创建Activity
因为没有实现资源的加载,所以这里的activity没有引用任何资源。
package com.xt.appplugin;
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
public class Plugin1Activity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
LinearLayout linearLayout = new LinearLayout(getApplicationContext());
TextView textView = new TextView(getApplicationContext());
textView.setText("这是插件类Plugin1Activity");
linearLayout.addView(textView);
setContentView(linearLayout);
}
}
2.在宿主中注册插件的Activity
这里声明就为了通过mainfest注册检查
3.编译项目,拷贝apk
4.编写Hook的代码
1.先获取sCurrentActivityThread对象
2.然后获取sCurrentActivityThread对象中mPackage的值。
3.通过包名,获取mPackage这个map中LoadedApk的对象值。
4.然后使用自定义生成的ClassLoader对象替换掉LoadedApk中的mClassLoader
5.通过包名去启动插件中的Activity
if (position == 2) {
try {
val classLoader = javaClass.classLoader
val loadedApkClass = classLoader.loadClass("android.app.LoadedApk")
val activityThreadClass = classLoader.loadClass("android.app.ActivityThread")
val activityThreadField =
activityThreadClass.getDeclaredField("sCurrentActivityThread")
activityThreadField.isAccessible = true
val activityThreadGet = activityThreadField.get(null)//1
val packageField = activityThreadClass.getDeclaredField("mPackages")
packageField.isAccessible = true
val arrayMap = packageField.get(activityThreadGet)//2
val loadedApkWeak = (arrayMap as ArrayMap<*, *>).get(context.packageName)
val loadedApkObject = (loadedApkWeak as WeakReference<*>).get()//3
// 替换classLoader
val mBaseClassLoaderField = loadedApkClass.getDeclaredField("mBaseClassLoader")
mBaseClassLoaderField.isAccessible = true
mBaseClassLoaderField.set(loadedApkObject, selfClassLoader)
val mDefaultClassLoaderField =
loadedApkClass.getDeclaredField("mClassLoader")
mDefaultClassLoaderField.isAccessible = true
mDefaultClassLoaderField.set(loadedApkObject, selfClassLoader)//4
//hook了之后启动插件中的activity
val intent = Intent()
intent.setClassName(context, "com.xt.appplugin.Plugin1Activity")
startActivity(intent)
Log.i("lxltest", "")
} catch (e: Exception) {
e.printStackTrace()
}
return
}
5.测试验证
首先点击加载插件,然后点击启动插件apk中的activity(需要mainfest注册)。
接下来我们就可以看到,可以正常启动插件中的activity了。
三:要点总结
1.每个进程都会对应一个LoadedApk。
2.ActivityThread的mPackage存放着所有LoadedApk。
3.加载activity的classLoader,就是LoadedApk中的mclassLoader
四。代码地址:
项目地址:
https://github.com/aa5279aa/android_all_demo
插件项目位置:https://github.com/aa5279aa/android_all_demo/tree/master/DemoClient/appplugin