https://blog.csdn.net/adminyuqiao/article/details/110690030
上一篇文章說了Android中的普通類的加載方法,
這次說一說四大組件的加載----以activity為例,因為android版本不同,這里以26為準,至于其他版本,需要適配下,流程邏輯沒有啥太大區別,
首先咱現在插件apk中添加一個MainActivity,當然先把setContentVIew注釋掉,因為用到了資源檔案,等下次再說,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
// 就簡單打個log吧
Log.e(TAG, "onCreate: 啟動插件的activity" );
}
大家都知道activity需要在manifest中進行注冊才能啟動,
<activity android:name=".MainActivity"/>
但是在宿主的app中不會有插件activity的注冊,這咋解決?
這里需要用到Hook技術,所謂hook說直白點就是反射和動態代理,知道這就行了,也許大家有更好的辦法,這只是一個思路而已,
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test","com.winter.test.MainActivity"));
startActivity(intent);
直接這么寫大家肯定知道無法啟動,因為沒有注冊,那么我們可以在我們的宿主中加入一個ProxyActivity并在宿主的manifest中注冊,在AMS去檢查的時候將我們的intent中的內容換成ProxyActivity,而后再換成插件的activity就可以了,
proxyActivity如下:
public class ProxyActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("winter", "onCreate: 我是宿主代理Activity" );
}
}
這里需要一點activity啟動流程和動態代理的基礎,如果不太了解希望先去了解下,
畫個圖,這個思路就是這樣

接下來的步驟就是找哪兒能夠讓我們做這些個步驟了,
一般找hook點的準則有2點
1.盡量找靜態變數或者單利的
2.盡量找public的
知道了目標就開干!
修改啟動的activity,其實就是修改intent,所以我們就去啟動流程中找intent就好了,
按照啟動流程,很容易找到下面代碼
Instrumentation.ActivityResult ar = mInstrumentation.
execStartActivity(this,
mMainThread.getApplicationThread(),
mToken, this,
intent, requestCode, options);
我們點進去看看,看到這里
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
這個ActivityTaskManager.getService.startActivity()就是我要找的hook點,
可以動態代理IActivityTaskManager這個物件,在其startActivity的時候,做點小動作,就是替換intent,是不是就可以實作了,關于動態代理,這就不說了,如果不是很明白,那就先去了解下,
首先獲取IActivityManger物件,根據原始碼
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
我們需要先獲取這個Singleton的物件,然后呼叫其get方法
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
還是一樣通過反射獲取
Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleTon = singletonField.get(null);
呼叫get方法
Class<?> singleTonClass = Class.forName("android.util.Singleton");
Field mInstanceFiled = singleTonClass.getDeclaredField("mInstance");
mInstanceFiled.setAccessible(true);
Method getMethod = singleTonClass.getMethod("get");
final Object mInstance = getMethod.invoke(singleTon);
這個mInstance就是我們想要的
而后就是通過動態代理,干點壞事情
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManager}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//過濾,只有當呼叫startActivity的時候才去動態代理
if ("startActivity".equals(method.getName())) {
int index = -1;
//獲取Intent 物件
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
}
}
//修改intent
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.winter.myutils",
"com.winter.myutils.ProxyActivity");
//我們將原先的intent保存起來,方便之后獲取
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});
最后別忘了最后一步
mInstanceFiled.set(singleTon, proxyInstance);
至此,我們就完成了在AMS檢查注冊之前替換intent的步驟,我們可以運行一下看看,當我們啟動插件中的MainActivity
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test",
"com.winter.test.MainActivity"));
startActivity(intent);
可以看到日志

OK 接下來下一步就是替換回來了,
經過了AMS檢查注冊,而后我們只要關注經過AMS之后干了啥就可以了,在這中間找我們需要的hook點
我們進入ActivityThread中的ApplicationThread的scheduleLaunchActivity方法
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
可以看到這里發送了一個LAUNCH_ACTIVITY的訊息,這個H就是一個Handler,接下來我們看handleMessage
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
...
}
然后就是一步步點進去看,正常的啟動流程,不詳細描述了
最后走到了這里
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ComponentName component = r.intent.getComponent();
...
}
我們找到了我們需要的intent,但是這個intent在ActivityClitentRecord中
static final class ActivityClientRecord {
...
Intent intent;
...
}
可以通過原始碼看到,intent并不是靜態的,所以我們需要ActivityClientRecord物件,
細心的同學已經知道了,在上面給出的原始碼中已經給出了這個物件,
在handleMessage中已經有ActivityClientRecord物件r
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
如果我們能拿到msg拿到,是不是就能拿到ActivityClientRecord物件了,
接下來就是分析Handler的原始碼,emmmm,,這個應該是最基本的,不了解的話我也沒辦法了,
所以我們要Hook Handler
我們直接看HandleMessage
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
看原始碼可以知道實際上是通過dispatchMessage方法呼叫了handleMessage,
分析dispatchMessage方法
可以看到,如果mCallback不等于空,是不是就能把msg回呼給我們,
再分析上面發送訊息的H
final H mH = new H();
其中H類中并沒有構造方法,所以其呼叫的是Handler中的構造方法
public Handler() {
//第一個引數就是callback
this(null, false);
}
所以可以看到在這里callback是空的,
所以我們可以寫一個介面,實作callback,替換系統的callback,從而拿到msg,而msg的obj就是ActivityClientRecord,所以就能拿到intent了
剩下的事情就是擼碼了
首先自己創建一個callback
Handler.Callback callback = msg -> {
switch (msg.what) {
// 通過msg 可以拿到intent
//100就是上面LAUNCH_ACTIVITY的值
case 100:
try {
//msg.obj 為ActivityClientRecord
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
//第一步保存的intent,替換
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
//必須return false
return false;
};
代碼沒啥好說的,一目了然,
接下來就是進行hook 替換了
要替換系統的callback,因為callback不是靜態的,所以首先拿到handler物件就是ActivityThread中的mh,mh也不是靜態的,所以要拿到ActivityThread物件,正好在ActivityThread中有這個
private static volatile ActivityThread sCurrentActivityThread;
正好是靜態的,我們就獲取這個玩意兒,就是通過反射api的呼叫,沒啥好說的了
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
最后別忘了
mCallbackField.set(mH, callback);
至此第二部分的替換也完成了,
運行一下試試,

OK 大功告成,
PS:這里只是基于api26 ,在android10上面不一樣,要通過適配,IActivityManager好像變成了IActiivityTaskManager,下面的Handler發送訊息的也不一樣,應該是159.而不是100.詳細代碼不寫了,這里只是提供一個思路給大家了解插件化這個東西,希望大家喜歡
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/233170.html
標籤:其他
上一篇:PS制作登錄界面
