Activity不用註冊?那就來Hook吧

1.引言

之前一直很好奇,啓動一個新的Activity,爲何非要在清單文件裏註冊,究竟是哪裏地方進行了校驗,整個啓動的流程是什麼樣子的。若是想實現插件化機制,啓動一個插件中新的Activity的話有什麼其它方法去作到。這篇文章原本是想寫在Activity的啓動流程分析以後的,可是裏面確實涉及的類,邏輯不少,寫起來可能會有些漏缺,並且比較無聊,因此先寫一下android的hook技術,先大概講一下Activity的啓動流程,裏面會涉及到一些進程交互,若是對android中的Binder機制不熟悉的朋友能夠看我上篇文章3分鐘帶你看懂android的Binder機制java

2.Activity大體啓動流程

啓動一個Activity大體會經歷一下幾個方法:android

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

具體方法本文就不詳細說了,省得篇幅太長,引用一張圖來表述整個的交互過程: git

從上圖咱們能夠看出整個通訊過程是涉及到2次Binder通訊過程的,APP進程和system_server進程分別做爲了一次client和server端。APP進程也就是咱們本身的應用進程,system_server進程是系統進程,javaframework框架的核心載體,裏面運行了大量的系統服務,好比這裏提供ApplicationThreadProxy),ActivityManagerService,結合圖,啓動流程大體以下:github

  • APP進程入口是ActivityThread,初始化時便會建立一個Binder線程(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS發送來的事件),ApplicationThread主要有兩個做用:1.做爲Binder服務端接受system_server中的client端ApplicationThreadProxy發送過來的消息,對應上面的方法是ActivityManagerService------>ApplicationThread,2.做爲消息的中轉站,將msg經過handler傳遞到ActivityThread中,也就是傳遞到上述的最後一個方法中。
  • system_server進程中的ActivityManagerService做爲Binder的服務端,接受ActivityManagerProxy做爲client端發送過來的消息,也就是Instrumentation.execStartActivity()---->ActivityManagerService.startActivity()。

這裏主要看下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中。框架

3.Hook實現

3.1 hook實現思路

咳咳!!咱們回到正題,上面只是鋪墊,咱們的主題是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給替換回去

3.2 上代碼!!

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就能夠起很大的做用了。

有興趣的朋友能夠跟着一塊兒實現一下,感謝觀看,溜了溜了~~

項目地址:github.com/weirdLin/ho…

相關文章
相關標籤/搜索