以前一直很好奇,启动一个新的Activity,为什么非要在清单文件里注册,到底是哪里地方进行了校验,整个启动的流程是什么样子的。如果想实现插件化机制,启动一个插件中新的Activity的话有什么其它方法去做到。这篇文章本来是想写在Acytivity的启动流程分析之后的,但是里面确实涉及的类,逻辑很多,写起来可能会有些漏缺,而且比较无聊,所以先写一下android的hook技术,先大概讲一下Activity的启动流程,里面会涉及到一些进程交互,
启动一个Activity大致会经历一下几个方法:
具体方法本文就不详细说了,免得篇幅太长,引用一张图来表述整个的交互过程:
从上图我们可以看出整个通信过程是涉及到2次Binder通信过程的,APP进程和system_server进程分别作为了一次client和server端。APP进程也就是我们自己的应用进程,system_server进程是系统进程,javaframework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy),ActivityManagerService,结合图,启动流程大致如下:
这里主要看下Instrumentation.execStartActivity这个方法,比较关键,跳过去可能有的朋友比较模糊,主要代码如下:
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介面类型对象,继续看:
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中,那我们可以在之前做一些骚操作,来个狸猫换太子,具体思路如下:
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就可以起很大的作用了。