之前一直很好奇,启动一个新的Activity,为何非要在清单文件里注册,究竟是哪里地方进行了校验,整个启动的流程是什么样子的。若是想实现插件化机制,启动一个插件中新的Activity的话有什么其它方法去作到。这篇文章原本是想写在Activity的启动流程分析以后的,可是里面确实涉及的类,逻辑不少,写起来可能会有些漏缺,并且比较无聊,因此先写一下android的hook技术,先大概讲一下Activity的启动流程,里面会涉及到一些进程交互,若是对android中的Binder机制不熟悉的朋友能够看我上篇文章3分钟带你看懂android的Binder机制java
启动一个Activity大体会经历一下几个方法:android
具体方法本文就不详细说了,省得篇幅太长,引用一张图来表述整个的交互过程: git
从上图咱们能够看出整个通讯过程是涉及到2次Binder通讯过程的,APP进程和system_server进程分别做为了一次client和server端。APP进程也就是咱们本身的应用进程,system_server进程是系统进程,javaframework框架的核心载体,里面运行了大量的系统服务,好比这里提供ApplicationThreadProxy),ActivityManagerService,结合图,启动流程大体以下:github
这里主要看下Instrumentation.execStartActivity这个方法,比较关键,跳过去可能有的朋友比较模糊,主要代码以下:bash
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
....
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
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;
}
复制代码
这里的contextThread也就是上面讲的ApplicationThread对象,主要看下面ActivityManager.getService(),返回的是一个IActivityManager接口类型对象,继续看:app
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);//注意这一行
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
复制代码
这里我用的API25,API26及以上,实现的代码不太同样,废弃了ActivityManagerProxy,改用了AIDL来实现通讯,为了让你们伙更理解Binder,这里就用以前的API了,逻辑应该很清晰经过ServiceManager拿到IBinder对象,再在本地进行查找,若是不在同一个进程,就返回ActivityManagerProxy代理对象,因此很清晰,Instrumentation.execStartActivity()实际上最后就调用到了ActivityManagerProxy中。框架
咳咳!!咱们回到正题,上面只是铺垫,咱们的主题是hook,怎么启动一个没注册的Activity呢,先将下思路,既然最终检查是在AMS中,那咱们能够在以前作一些骚操做,来个狸猫换太子,具体思路以下:ide
直接startActivity()开启一个未注册的TargetActivitypost
既然在AMS以前,消息是从ActivityManagerProxy中发出去的,咱们能够动态代理生成一个类(不熟悉动态代理的朋友只能自行google了~),代理ActivityManagerProxy对象,拦截其中的startActivity()方法,拿到其中的intent参数对象。咱们把真正的intent替换成咱们一个已经注册过的ProxyActivity,先把AMS的check这一关给过了,再把真正的intent当作对象存在ProxyActivity的intent中。学习
既然intent替换了,AMS是过了,那确定得给它搞回来,要否则不就直接打开了ProxyActivity?咱们知道最好消息时回到了ActivityThread.Handler.handleMessage()中的,嘿嘿,熟悉Handler机制的朋友应该知道,在这以前是会先走dispatchMessage方法的,不熟悉的能够看下我以前的文章Android源码学习之handler,
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
是分别会前后执行handleCallback(msg)--->mCallback.handleMessage(msg)--->handleMessage(msg),而Activity中正好是最好一个,那咱们能够hook一下这个mCallback,让咱们在最后消息执行时,把咱们的intent给替换回去
public class HookActivityUtils {
private static final String TAG = "HookActivityUtils";
private volatile static HookActivityUtils sHookActivityUtils;
public static HookActivityUtils getInstance(){
if (sHookActivityUtils==null){
synchronized (HookActivityUtils.class){
if (sHookActivityUtils==null){
sHookActivityUtils = new HookActivityUtils();
}
}
}
return sHookActivityUtils;
}
private HookActivityUtils(){
}
public void hooks(Context mContext){
Object object;
try {
//寻找hook点,最好是静态或者单例,不容易发生改变,由于是静态,因此传入null便可
//由于版本差别,因此要分开处理
if (Build.VERSION.SDK_INT>=26){
Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
iActivityManagerSingleton.setAccessible(true);
object = iActivityManagerSingleton.get(null);
}else{
Field gDefault = Class.forName("android.app.ActivityManagerNative").getDeclaredField("gDefault");
gDefault.setAccessible(true);
object = gDefault.get(null);
}
//获取单例对象,实现IActivityManager接口的实现类
Field mFieldInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance");
mFieldInstance.setAccessible(true);
Object mInstance = mFieldInstance.get(object);
//寻找到hook点后,新建一个代理对象
ActivityManagerDelegate managerDelegate = new ActivityManagerDelegate(mInstance,mContext);
Class<?> aClass = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass}, managerDelegate);
//替换动态代理对象
mFieldInstance.set(object,proxy);
} catch (Exception mE) {
mE.printStackTrace();
}
}
public void hookHanlder(){
try {
Class<?> aClass = Class.forName("android.app.ActivityThread");
Method currentActivityThread = aClass.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
//ActivityThread 自己对象
Object invoke = currentActivityThread.invoke(null);
Field mH = aClass.getDeclaredField("mH");
mH.setAccessible(true);
//获取handler对象
Object handler = mH.get(invoke);
//获取handler中的mCallback
Field mCallback = Handler.class.getDeclaredField("mCallback");
mCallback.setAccessible(true);
mCallback.set(handler,new HookCallBack((Handler) handler));
} catch (Exception mE) {
mE.printStackTrace();
}
}
}
复制代码
主要也就是对应的两个方法,一个经过反射拿到实现IActivityManager接口的对象,并生成一个代理此对象的代理对象,另一个是反射拿到ActivityThread中的mH Handler对象,而后传入一个实现Handler.callback接口的对象,这样Handler中的mcallback就不为空了,也就达到了咱们的目的
而后是咱们的代理对象:
public class ActivityManagerDelegate implements InvocationHandler {
private static final String TAG = "ActivityManagerDelegate";
private Object mObject;
private Context mContext;
public ActivityManagerDelegate(Object mObject,Context mContext) {
this.mObject = mObject;
this.mContext = mContext;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("startActivity")){
//拦截方法
Log.e(TAG,"i got you");
Intent intent =null;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent){
intent = (Intent) args[i];
//找到了intent参数
Intent mIntent = new Intent();
ComponentName componentName = new ComponentName(mContext,ProxyActivity.class);
//将真正的intent带上,后续替换
mIntent.setComponent(componentName);
mIntent.putExtra("realObj",intent);
//修改成已注册Activity的intent,先让AMS检查经过
args[i] = mIntent;
}
}
}
return method.invoke(mObject,args);
}
}
复制代码
咱们拦截startActivity,而后将ProxyActivity的ComponentName传递进去,狸猫换太子,同时将真正的intent带过去,接下来就是处理消息了:
public class HookCallBack implements Handler.Callback {
private static final String TAG = "HookCallBack";
private Handler mHandler;
public HookCallBack(Handler mHandler) {
this.mHandler = mHandler;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what==100){
handleHookMsg(msg);
}
mHandler.handleMessage(msg);
return false;
}
private void handleHookMsg(Message mMsg) {
Object obj = mMsg.obj;
try {
Field intent = obj.getClass().getDeclaredField("intent");
//这时候拿出以前存进来真正的intent
intent.setAccessible(true);
Intent proxyIntent = (Intent) intent.get(obj);
Intent realIntent = proxyIntent.getParcelableExtra("realObj");
proxyIntent.setComponent(realIntent.getComponent());
} catch (Exception mE) {
mE.printStackTrace();
}
}
}
复制代码
什么?为何要拦截msg.what等于100的消息?
private class H extends Handler {
....
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
....
}
复制代码
这下明白了吧,拦截到这个消息后,把事先存进去的intent的Component再set回去就完美了~~
主页面MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HookActivityUtils.getInstance().hooks(this);
HookActivityUtils.getInstance().hookHanlder();
findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,TargetActivity.class);
startActivity(intent);
}
});
}
}
复制代码
这里咱们打开的是TargetActivity,可是清单文件中并无声明:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ProxyActivity"></activity>
</application>
复制代码
这样的话,就实现打开一个未注册的Activity了,是否是也是挺easy的,在实现插件化机制的时候,要打开插件中的activity的话,由于没有在原宿主中的清单文件注册,是没法直接调转的,这时候咱们这个代理activity就能够起很大的做用了。
有兴趣的朋友能够跟着一块儿实现一下,感谢观看,溜了溜了~~