上一篇讲了插件化的概念和类加载机制,并实现了从插件apk中合并,并加载一个类。不知道大家还记不记得,实现插件化,只需解决三个问题即可:
如何加载插件中的类?- 如何加载插件中的资源?
- 当然还有最重要的一个问题,四大组件如何调用呢?四大组件是需要注册的,而插件apk中的组件显然不会在宿主提前注册,那么如何去调用它呢?
第一个问题在上一篇文章中已经实现,今天我们就来讲一下插件中的四大组件 - Activity的启动。本篇讲的内容比较多,小伙伴们做好准备啦!
有同学说:既然已经合并了插件的所有的类,那我直接在宿主中直接启动activity不就可以了吗?
// 跳转插件 Activity
val intent = Intent()
// 简单的科普一下 , setComponent()和setClassName()是一样的
// setClassName最终会调用setComponent方法
intent.component = ComponentName("com.kangf.plugin", "com.kangf.plugin.MainActivity")
startActivity(intent)
答案是当然不行,因为Activity是需要注册的,启动的时候会在AMS中进行验证。我们只是合并了类,manifest是不会合并的,直接启动就会抛出没有注册的异常。这个问题怎么解决呢,有没有办法绕过这个验证呢?注意:以下内容都是拿API28举例,每个版本的API都有可能不同
Activity的启动流程
首先来看一下Activity的启动流程:
当调用了startActivity方法的时候,会调用到Instrumentation的execStartActivity,然后会通过一个服务(AMS, 也就是ActivityManagerService)去验证,验证通过之后回到主线程正常启动。我们要做的,就是在AMS验证之前偷偷把要启动的Activity替换成宿主中的一个代理Activity,验证通过之后,回到主线程,再将替换掉的Activity替换回来,起到了一个瞒天过海的作用(这里下面会详细介绍)。
Hook
先来了解一下Hook的概念,Hook 中文意思就是 钩子。简单说,它的作用就是改变代码的正常执行流程
比如对象A与对象B互相调用,我们加入一个钩子,这时候A调用B的时候 ,必须要经过Hook层,通过Hook再调用B,同理,B调用A也是一样。就像上面提到的,我们通过Hook去修改AMS中的Activity,验证完成后又通过Hook修改要启动的Activity。这样思路就出来了。
Hook实现方式
那么,通过什么技术来实现Hook呢?有两种方式
- 反射
- 动态代理
Hook点
-
什么是Hook点?
挂钩子的地方就是Hook点
-
查找Hook点的原则?
尽量静态变量或者单例对象。
尽量 Hook public 的对象和方法。
寻找Hook点
有了思路,下面就开始从源码来寻找Hook点。首先来看Instrumentation的跳转方法
// android.app.Instrumentation
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
// 调用AMS来启动Activity
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
可以看到最终调用了ActivityManager.getService()来启动Activity,它返回的是一个什么对象呢?继续往下看:
// android.app.ActivityManager
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
// android.util.Singleton
public abstract class Singleton<T> {
private T mInstance;
public Singleton() {
}
protected abstract T create();
public T get() {
synchronized(this) {
if (this.mInstance == null) {
this.mInstance = this.create();
}
return this.mInstance;
}
}
}
可以看到,是调用了Singleton的get()方法,返回了一个IActivityManager,那么我们就可以从这里下手,Hook住IActiviyManager的startActivity方法(简单来说就是替换掉Singleton的mInstanse实例)。这里使用动态代理和反射,都很简单,就不多做介绍了。
撸码阶段
找到了Hook点,我们就开始撸码:
代理对象
因为要替换IActivityManager中的startActivity方法,所以就代理这个类:
首先获取到IActivityManager的class对象,再通过Proxy.newProxyInstance创建一个代理对象,其实这里就返回了一个新的IActivityManager。可以看到有3个参数:
- 传一个ClassLoader对象,这里直接使用当前线程的ClassLoader就可以了
- 要代理的类,这里是一个 数组,我们只需要代理IActivityManager的类就可以了
- 回调函数,每执行要代理的类中的方法的时候,都会走到这个回调函数,所以我们只需要判断method.getName()如果是startActivity的话,就修改它的参数
具体的步骤都在注释里面
// 获取IActivityManager的Class
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
// 代理IActivityManager
Object newInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass},
new InvocationHandler() {
// 没执行一个方法,都会调用到这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("kangf", "old args === " + args.length);
// 如果是startActivity方法的话
if (method.getName().equals("startActivity")) {
// startActivity方法有多个参数
// 这里是索引,记录Intent参数的索引
int index = 0;
// 遍历所有的参数,获取Intent参数的索引
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 重新创建一个Intent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.kangf.dynamic",
"com.kangf.dynamic.ProxyActivity");
// 并将原来的intent记录下来
proxyIntent.putExtra("oldIntent", (Intent) args[index]);
// 给Intent重新赋值,让它变成我们的代理Activity,这样验证就通过了
args[index] = proxyIntent;
}
Log.e("kangf", "change args === " + args.length);
// 在这里还需要继续执行这个方法
return method.invoke(mInstance, args);
}
});
注意,最后执行方法的时候,有个mInstance对象,说明执行的是mInstance对象里面的方法,为什么这么写呢?
偷天换日,替换原来的Intent
上面我们已经分析了,要替换的是Singleton对象里面的实例,所以这个mInstance其实就是IActivityManager实例,那么继续:
// 获取ActivityManager的Class
Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
// 根据Class获取IActivityManagerSingleton私有字段
Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
iActivityManagerSingletonField.setAccessible(true);
// 根据iActivityManagerSingletonField字段获取Singleton对象
Object singleTon = iActivityManagerSingletonField.get(null);
Class<?> singleTonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singleTonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 获取到真正的IActivityManger的实例对象
final Object mInstance = mInstanceField.get(singleTon);
这样就获取到了IActivityManger的实例对象。好,那我们来运行一下,看看是不是跳转到了代理的Activity呢?(注意:此方法只能运行在android8.0和android9.0的手机上,其他版本需要单独适配,下一篇文章会讲到)
<img src="https://img-blog.csdnimg.cn/20200120142810920.gif" style="zoom:30%;" />
ActivityThread
上一步是在AMS检测之前,将原来的Intent替换掉了,让它检测宿主中的Activity,可我们要做的是跳转到插件,这样我们就需要在AMS检测完成之后,再讲Intent替换插件的intent。
在AMS检测完成之后,会走到ActivityStackSupervisor,这个类中的realStartActivityLocked方法。先来看一下API 28的启动时序图:
那我们就从源码看起:
// com.android.server.am.ActivityStackSupervisor.java
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
// 省略不重要的代码 ...
try {
// 省略不重要的代码 ...
try {
// 省略不重要的代码 ...
// Create activity launch transaction.
final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
r.appToken);
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, mService.isNextTransitionForward(),
profilerInfo));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);
// Schedule transaction.
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
// ...
} catch (RemoteException e) {
// ...
throw e;
}
} finally {
endDeferResume();
}
// ...
return true;
}
代码片段太长,把与我们用到的无关的代码省略掉了,有兴趣的可以自己查看源码:com.android.server.am.ActivityStackSupervisor.java
需要注意的是,ClientTransaction加入了一个callback: LaunchActivityItem,记住,这里是重点!
可以看到,最终走到了
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
继续跟踪到ClientLifecycleManager.java的scheduleTransaction方法:
// ClientLifecycleManager.java
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
final IApplicationThread client = transaction.getClient();
transaction.schedule();
if (!(client instanceof Binder)) {
transaction.recycle();
}
}
发现调用了ClientTransaction的schedule()方法:
// ClientTransaction.java
public void schedule() throws RemoteException {
mClient.scheduleTransaction(this);
}
mClient是IApplicationThread,调用了它的scheduleTransaction,这个IApplicationThread是不是很熟悉?没错,其实就是ActivityThread,继续往下看:
// ActivityThread.java
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
点进去看,好乱~
// ClientTransactionHandler
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
诶,发送了一个消息:ActivityThread.H.EXECUTE_TRANSACTION
,这个消息在ActivityThread中接收:
public static final int EXECUTE_TRANSACTION = 159;
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
// ...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
// ...
}
// ...
}
现在回到了主线程 ,它又调用了mTransactionExecutor的excute方法,并传入了一个transaction:
// TransactionExecutor
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
executeCallbacks(transaction);
executeLifecycleState(transaction);
mPendingActions.clear();
log("End resolving transaction");
}
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
// 。。。
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
// 。。。
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
// 。。。
}
}
最后调用了item.execute方法,这个item是ClientTransactionItem对象,ClientTransactionItem又是从callbacks集合中获取的,那这个callbacks又是什么呢?从上面可以看到,是transaction里面拿到的!还记得上面的addCallback吗?其实就是调用了LaunchActivityItem中的execute方法!
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
// 调用了handleLaunchActivity
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
到这里一目了然了, 跳转Activity时构造了一个ActivityClientRecord对象,这个对象里面传入了mIntent, 这是个私有的成员变量,是不是思路就出来了?我们只需要拿到这个intent并修改它就可以了!
既然时个私有变量,那Hook的时候,就需要得到LaunchActivityItem的实例对象,怎么获得呢?首先要知道LaunchActivityItem时怎么来的?我们还是从上面的addCallback方法入手, 继续往上跟源码:
// ClientTransaction.java
public void addCallback(ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
}
mActivityCallbacks.add(activityCallback);
}
到这里发现其实是把LaunchActivityItem放进了mActivityCallbacks集合里面,那么我们只需要获取到这个集合,遍历如果是LaunchActivityItem就可以了,ClientTransaction是什么就不用多说了吧?不就是ActvityThread中 msg接收的对象吗!
有了思路,接下来就正式撸码~
Hook Handler
还有一步,首先要拦截到ActivityThread中的handleMessage方法,接收到159(public static final int EXECUTE_TRANSACTION = 159;
)这个消息才进行替换intent的操作。那我们先来看一下Handler:
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
// .....
}
只看构造方法就可以了,在Handler构造的时候,传入了要给Callback,这个有什么用呢?搜一下mCallback看看:
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里不难理解,接收消息首先会走dispatchMessage方法,如果callback不为空,就会走mCallback.handleMessage,并返回一个布尔值,如果返回true,那么拦截消息,否则继续走handleMessage方法。这样,我们就可以Hook住ActivityThread中的Handler,给它加上一个callback!
ActivityThread中的Handler怎么拿到呢?
final H mH = new H();
private static volatile ActivityThread sCurrentActivityThread;
H继承自Handler,它直接在全局定义了一个Handler,还有它本身的一个实例,这样拿到它就简单很多了~接下来看一下完整的hook代码!
将代理的intent替换回来
public static void hookHandler() {
try {
// 获取ActivityThread类
final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
// 拿到ActivityThread对象
Field activityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
final Object activityThread = activityThreadField.get(null);
// 通过activityThread对象获取Handler实例
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 给它的handler设置一个callback,并返回false,这里要让它继续往下走 ,不然就拦截掉了
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("kangf", "handling code = " + msg.what);
switch (msg.what) {
// 1. 接收到159的消息
case 159:
try {
// 2. 获取mActivityCallbacks字段
Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
// 3. 通过字段获取mActivityCallbacks集合
List<Object> mActivityCallbacks = (List<Object>) mActivityCallbacksField.get(msg.obj);
// 4. 遍历这个集合,如果是LaunchActivityItem的话,进行hook
for (int i = 0; i < mActivityCallbacks.size(); i++) {
Class<?> itemClass = mActivityCallbacks.get(i).getClass();
if (itemClass.getName().equals("android.app.servertransaction.LaunchActivityItem")) {
// 开始hook
// 5. 获取LaunchActivityItem中的intent字段
Field intentField = itemClass.getDeclaredField("mIntent");
intentField.setAccessible(true);
// 6. 根据字段获取到代理的intent
Intent proxyIntent = (Intent) intentField.get(mActivityCallbacks.get(i));
// 7. 拿到保存过的intent
Intent intent = proxyIntent.getParcelableExtra("oldIntent");
// 8. 把原来的替换回来
proxyIntent.setComponent(intent.getComponent());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 这里必须返回false
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
上面每一步都在注释里写得很清楚了,这样就完成了插件Activity的跳转
插件中的Activity这里先亮出来:
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
Toast.makeText(this, "插件的MainActivity", Toast.LENGTH_SHORT).show()
}
}
加载资源需单独适配 ,这个留到下一节再讲,这里先土司一下.
多说无益,我们来看效果:
<img src="https://img-blog.csdnimg.cn/20200120160415320.gif" alt="在这里插入图片描述" style="zoom:50%;" />
最后提一下,为什么以前用的kt, 现在用java呢? kt在hook IActivityManager时,会报一个错误,它的可变长度参数被java当成了一个array处理了,结果报以下错误,意思就是本来又10个参数,你只传了1个:
java.lang.IllegalArgumentException: Wrong number of arguments; expected 10, got 1
注意
以上都是拿API28来举例,只能运行在Android8.0 和 Android9.0的手机上,每个版本的API都有可能是不一样的,适配会在下一篇提一下,github中已经做了处理,有兴趣的可以看一下: