以前一直很好奇,啟動一個新的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就可以起很大的作用了。
TAG:Android開發 | Activity | Hook |