https://blog.youkuaiyun.com/adminyuqiao/article/details/110690030
上一篇文章说了Android中的普通类的加载方法。
这次说一说四大组件的加载----以activity为例。因为android版本不同,这里以26为准。至于其他版本,需要适配下。流程逻辑没有啥太大区别。
首先咱现在插件apk中添加一个MainActivity。当然先把setContentVIew注释掉,因为用到了资源文件,等下次再说。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// 就简单打个log吧
Log.e(TAG, "onCreate: 启动插件的activity" );
}
大家都知道activity需要在manifest中进行注册才能启动。
<activity android:name=".MainActivity"/>
但是在宿主的app中不会有插件activity的注册。这咋解决?
这里需要用到Hook技术,所谓hook说直白点就是反射和动态代理。知道这就行了。也许大家有更好的办法,这只是一个思路而已。
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test","com.winter.test.MainActivity"));
startActivity(intent);
直接这么写大家肯定知道无法启动,因为没有注册,那么我们可以在我们的宿主中加入一个ProxyActivity并在宿主的manifest中注册。在AMS去检查的时候将我们的intent中的内容换成ProxyActivity。而后再换成插件的activity就可以了。
proxyActivity如下:
public class ProxyActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("winter", "onCreate: 我是宿主代理Activity" );
}
}
这里需要一点activity启动流程和动态代理的基础。如果不太了解希望先去了解下。
画个图,这个思路就是这样
接下来的步骤就是找哪儿能够让我们做这些个步骤了。
一般找hook点的准则有2点
1.尽量找静态变量或者单利的
2.尽量找public的
知道了目标就开干!
修改启动的activity,其实就是修改intent,所以我们就去启动流程中找intent就好了。
按照启动流程,很容易找到下面代码
Instrumentation.ActivityResult ar = mInstrumentation.
execStartActivity(this,
mMainThread.getApplicationThread(),
mToken, this,
intent, requestCode, options);
我们点进去看看,看到这里
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
这个ActivityTaskManager.getService.startActivity()就是我要找的hook点,
可以动态代理IActivityTaskManager这个对象,在其startActivity的时候,做点小动作,就是替换intent。是不是就可以实现了。关于动态代理,这就不说了,如果不是很明白,那就先去了解下。
首先获取IActivityManger对象,根据源码
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;
}
};
我们需要先获取这个Singleton的对象,然后调用其get方法
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
还是一样通过反射获取
Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleTon = singletonField.get(null);
调用get方法
Class<?> singleTonClass = Class.forName("android.util.Singleton");
Field mInstanceFiled = singleTonClass.getDeclaredField("mInstance");
mInstanceFiled.setAccessible(true);
Method getMethod = singleTonClass.getMethod("get");
final Object mInstance = getMethod.invoke(singleTon);
这个mInstance就是我们想要的
而后就是通过动态代理,干点坏事情
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManager}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//过滤,只有当调用startActivity的时候才去动态代理
if ("startActivity".equals(method.getName())) {
int index = -1;
//获取Intent 对象
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
}
}
//修改intent
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.winter.myutils",
"com.winter.myutils.ProxyActivity");
//我们将原先的intent保存起来,方便之后获取
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});
最后别忘了最后一步
mInstanceFiled.set(singleTon, proxyInstance);
至此,我们就完成了在AMS检查注册之前替换intent的步骤。我们可以运行一下看看,当我们启动插件中的MainActivity
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test",
"com.winter.test.MainActivity"));
startActivity(intent);
可以看到日志
OK 接下来下一步就是替换回来了。
经过了AMS检查注册,而后我们只要关注经过AMS之后干了啥就可以了,在这中间找我们需要的hook点
我们进入ActivityThread中的ApplicationThread的scheduleLaunchActivity方法
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
可以看到这里发送了一个LAUNCH_ACTIVITY的消息。这个H就是一个Handler。接下来我们看handleMessage
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
...
}
然后就是一步步点进去看,正常的启动流程。不详细描述了
最后走到了这里
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ComponentName component = r.intent.getComponent();
...
}
我们找到了我们需要的intent,但是这个intent在ActivityClitentRecord中
static final class ActivityClientRecord {
...
Intent intent;
...
}
可以通过源码看到,intent并不是静态的,所以我们需要ActivityClientRecord对象。
细心的同学已经知道了,在上面给出的源码中已经给出了这个对象,
在handleMessage中已经有ActivityClientRecord对象r
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
如果我们能拿到msg拿到,是不是就能拿到ActivityClientRecord对象了。
接下来就是分析Handler的源码。emmmm。。这个应该是最基本的,不了解的话我也没办法了。
所以我们要Hook Handler
我们直接看HandleMessage
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
看源码可以知道实际上是通过dispatchMessage方法调用了handleMessage。
分析dispatchMessage方法
可以看到,如果mCallback不等于空,是不是就能把msg回调给我们。
再分析上面发送消息的H
final H mH = new H();
其中H类中并没有构造方法,所以其调用的是Handler中的构造方法
public Handler() {
//第一个参数就是callback
this(null, false);
}
所以可以看到在这里callback是空的。
所以我们可以写一个接口,实现callback。替换系统的callback。从而拿到msg,而msg的obj就是ActivityClientRecord,所以就能拿到intent了
剩下的事情就是撸码了
首先自己创建一个callback
Handler.Callback callback = msg -> {
switch (msg.what) {
// 通过msg 可以拿到intent
//100就是上面LAUNCH_ACTIVITY的值
case 100:
try {
//msg.obj 为ActivityClientRecord
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
//第一步保存的intent,替换
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
//必须return false
return false;
};
代码没啥好说的。一目了然。
接下来就是进行hook 替换了
要替换系统的callback,因为callback不是静态的,所以首先拿到handler对象就是ActivityThread中的mh,mh也不是静态的,所以要拿到ActivityThread对象。正好在ActivityThread中有这个
private static volatile ActivityThread sCurrentActivityThread;
正好是静态的,我们就获取这个玩意儿,就是通过反射api的调用,没啥好说的了
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
最后别忘了
mCallbackField.set(mH, callback);
至此第二部分的替换也完成了。
运行一下试试。
OK 大功告成。
PS:这里只是基于api26 。在android10上面不一样,要通过适配,IActivityManager好像变成了IActiivityTaskManager。下面的Handler发送消息的也不一样,应该是159.而不是100.详细代码不写了。这里只是提供一个思路给大家了解插件化这个东西。希望大家喜欢