1. The introduction

I’ve always wondered why it is necessary to register a new Activity in the manifest file, where the validation is performed, and what the process of starting it looks like. What other ways to start a new Activity in a plug-in if you want to implement the plug-in mechanism? This article was originally intended to be written after the analysis of the Activity startup process, but it does involve a lot of classes and logic, which may be a little missing and boring to write, so first write the Android hook technology, and first talk about the Activity startup process, which will involve some process interaction. If you’re not familiar with the Binder mechanics in Android, you can read my previous article for a 3 minute guide to android’s Binder mechanics

2.Activity Basically starts the process

There are a few ways to start an Activity:

  • Activity.startActivity()
  • Activity.startActivityForResult()
  • Instrumentation.execStartActivity()
  • ActivityManagerService.startActivity()
  • ApplicationThread.scheduleLaunchActivity()
  • ActivityThread.Handler.handleMessage()

The specific method is not detailed in this paper, so as not to be too long, a picture is quoted to describe the whole interaction process:

From the figure above, we can see that the entire communication process involves two Binder communication processes. APP process and system_server process act as the client and server respectively once. APP process is our own application process, system_server process is the system process, javaframeWork framework of the core carrier, which runs a large number of system services, such as provided here ApplicationThreadProxy), ActivityManagerService, combined with the diagram, starts as follows:

  • An APP process entry is an ActivityThread, which initializes a Binder thread (ApplicationThread, a Binder server that receives events from AMS, a system service). ApplicationThread serves two purposes: 1. ApplicationThreadProxy is a Binder server that receives messages from ApplicationThreadProxy, a client of system_Server. 2. As a message relay station, the MSG is passed through the handler to the ActivityThread, which is passed to the last method mentioned above.
  • The ActivityManagerService in the system_server process, acting as the Binder’s server, accepts messages sent by the ActivityManagerProxy as the client. Namely Instrumentation. ExecStartActivity () — — — — > ActivityManagerService. StartActivity ().

Here mainly to see Instrumentation. ExecStartActivity this method, more critical, jump in the past may be some friends are vague, mainly as follows:

 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;
    }
Copy the code

Here contextThread ApplicationThread object is described above, basically see below ActivityManager. GetService (), returns a IActivityManager interface type object, continue to look at:

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); // Notice this lineif (false) {
                Log.v("ActivityManager"."default service = " + am);
            }
            returnam; }}; 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);
    }

Copy the code

API25,API26 and above, but the code is different. Instead of ActivityManagerProxy, we use AIDL to communicate with each other. To make the Binder more understood, we use the same API. So the logic should be pretty clear: you get the IBinder object from the ServiceManager, you look it up locally, and if it’s not in the same process, you return the ActivityManagerProxy object, so it’s pretty clear, Instrumentation. ExecStartActivity () is actually the last call to ActivityManagerProxy.

3. The Hook

3.1 Hook implementation idea

Ahem!!!!! Our topic is hook, how to start an Activity that has not been registered? Let’s think about it first. Since the final check is in AMS, we can do some SAO operations before to change the prince by a cat.

  • Start an unregistered TargetActivity with startActivity ()

  • Since before AMS, messages were sent from ActivityManagerProxy, we can dynamically proxy a class, proxy the ActivityManagerProxy object, Intercept the startActivity() method and get the Intent object. We replace the actual intent with one of our registered ProxyActivity, pass the AMS check, and store the actual intent as an object in the Intent of the ProxyActivity.

  • If the intent is replaced and the AMS is passed, you must get it back, or you can open the ProxyActivity. . We know that message back to the best ActivityThread Handler. The handleMessage (), hey hey, be familiar with the Handler mechanism of friends should know that before this would go first dispatchMessage method, If you are not familiar with it, you can see my previous article Android source code Learning Handler,

public void dispatchMessage(Message msg) {
        if(msg.callback ! = null) { handleCallback(msg); }else {
            if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                    return; } } handleMessage(msg); }}Copy the code

HandleCallback (MSG)– > McAllback.handlemessage (MSG)– >handleMessage(MSG) We can hook the mCallback to replace our intent when the last message is executed

3.2 on the code!!

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 {// find the hook point, preferably static or singletons, not easy to change, because it is static, so null, so separateif (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); } // Get a singleton that implements the IActivityManager interface Field mFieldInstance = class.forname ()"android.util.Singleton").getDeclaredField("mInstance");
            mFieldInstance.setAccessible(true); Object mInstance = mFieldInstance.get(object); 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 voidhookHanlder(){ try { Class<? > aClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = aClass.getDeclaredMethod("currentActivityThread");
            currentActivityThread.setAccessible(true); / / Object Object ActivityThread itself invoke = currentActivityThread. Invoke (null); Field mH = aClass.getDeclaredField("mH");
            mH.setAccessible(true); Object handler = mh.get (invoke); / / get the handler mCallback Field mCallback = handler. Class. GetDeclaredField ("mCallback");
            mCallback.setAccessible(true); mCallback.set(handler,new HookCallBack((Handler) handler)); } catch (Exception mE) { mE.printStackTrace(); }}}Copy the code

One is to reflect the object that implements the IActivityManager interface and generate a proxy object that represents the object. The other is to reflect the mH Handler object in the ActivityThread. We then pass in an object that implements the handler. callback interface, so that the McAllback in Handler is not empty, thus achieving our purpose

Then there’s our proxy:

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"){// intercept method 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 mIntent = new intent (); ComponentName componentName = new ComponentName(mContext,ProxyActivity.class); MIntent. SetComponent (componentName) mIntent. SetComponent (componentName); mIntent.putExtra("realObj",intent); Args [I] = mIntent; }}}returnmethod.invoke(mObject,args); }}Copy the code

We intercept the startActivity, pass the ComponentName of the ProxyActivity to it, replace it with the actual intent, and then process the message:

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.setaccessible (intent.setaccessible (intent.setaccessible (intent.setaccessible));true);
            Intent proxyIntent = (Intent) intent.get(obj);
            Intent realIntent = proxyIntent.getParcelableExtra("realObj"); proxyIntent.setComponent(realIntent.getComponent()); } catch (Exception mE) { mE.printStackTrace(); }}}Copy the code

What? Why block MSG. What = 100?

private class H extends Handler { .... public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; . }Copy the code

After intercepting the message, set the intent Component back

Main page 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); }}); }}Copy the code

Here we open TargetActivity, but there is no declaration in the manifest:

<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>
Copy the code

If you want to open an Activity that is not registered in the original host, it is not possible to transfer the Activity to the original host without registering it in the manifest file. This is where our proxy activity can play a big role.

Interested friends can follow together to achieve, thanks for watching, slipped away ~~

Project address: github.com/weirdLin/ho…