幾乎所有的插件化都會要的一個需求,啟動一個未注冊的Activiy挫望,即加載插件包中的Activity驾锰,并且主應用并不知道插件應用中會有什么Activity扮碧,這是各個插件化框架主力解決的問題之一磁椒。
今天我們學習一下占坑式插件化框架的啟動Activity原理堤瘤。
關于動態(tài)代理的知識,了解過Retrofit
的源碼的或者看過Java設計模式之代理模式
的高級使用的,應該都了解了浆熔。本章不做介紹本辐,主介紹hook+反射
Hook是什么?
Hook直白點說就是攔截方法医增,自己對其參數(shù)等進行修改,或者替換返回值慎皱,達到自己不可告人的目的的一件事。
尋找Hook點
對于啟動Activity叶骨,老實說光startActivity
便有很多要說茫多,很多文章會帶著你一直追到ActivityManagerService
中的若干個方法,最后再調用本地的ActivityThread
里面的方法去啟動本進程的Activity忽刽。
所以光上面的流程我們看出天揖,我們把要啟動的Activity信息發(fā)給AMS,其做了各種檢查各種操作后真正讓Activity
啟動的還是我們的ActivityThread
startActivity流程
我們startActivity
是context的方法跪帝,去找Context
實現(xiàn)類class ContextImpl extends Context
今膊。
@Override
public void startActivities(Intent[] intents) {
warnIfCallingFromSystemProcess();
startActivities(intents, null);
}
/** @hide */
@Override
public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivities() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivitiesAsUser(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intents, options, userHandle.getIdentifier());
}
看到最后調用的是mMainThread.getInstrumentation().execStartActivitiesAsUser
方法,不用著急歉甚,直接ctrl鼠標左擊進去万细。是Instrumentation
類。
public void execStartActivitiesAsUser(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intents[0])) {
am.mHits++;
if (am.isBlocking()) {
return;
}
break;
}
}
}
}
try {
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
intents[i].migrateExtraStreamToClipData();
intents[i].prepareToLeaveProcess();
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
.startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
token, options, userId);
checkStartActivityResult(result, intents[0]);
} catch (RemoteException e) {
}
}
這邊我們看到了纸泄,是調用ActivityManagerNative
的方法啟動activity了赖钞。進去這個類我們只能看到一堆的binder通信,調用AMS的方法聘裁,不過此時我們不用關心了雪营,因為我們知道接下來是
AMS的事情。AMS是活在另一個PID的玩意兒衡便,我們只關心我們自己的pid献起,另一個進程的東西我們沒權限干壞事。
不過這邊我們需要注意镣陕,ActivityManagerNative
居然是個單類谴餐,那么我們hook它會安全很多,畢竟這個對象是單類呆抑。
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;
}
};
說是說AMS的事情不用關心岂嗓,但是我們得關心AMS什么時候回調回來,讓我們啟動Activity鹊碍。
去ActivityThread
看厌殉,一搜里面有個handleLaunchActivity
方法食绿,是在Handler里面被調用的,而且ActivityThread
也是我們喜歡的對象公罕,因為這個對象存在于整個應用生命周期中器紧。
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: { //這個值是常量100
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
看了這么多,我們可算是知道啟動Activity的入口和出口了楼眷,下面我們需要進行欺騙铲汪。
實現(xiàn)欺騙
欺騙系統(tǒng)就欺騙兩個地方,我們在AndroidManifest
里面申明一個假Activity
摩桶,然后在啟動真實Activity
的地方桥状,將Intent
里面的Activity
替換成我們已經注冊過的。再在ActivityThread
launch Activity的時候硝清,替換成我們需要啟動的便實現(xiàn)了啟動一個未注冊過的Activity的效果。
代碼實現(xiàn)
- 寫一個占坑Activity转晰,在
AndroidManifest
注冊
/**
* 占坑專用
*/
public class TmpActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tmp);
}
}
<!--占坑專用Activity-->
<activity android:name=".TmpActivity"/>
- 在
attachBaseContext
中欺騙應用
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
/**
* 欺騙ActivityManagerNative,將要啟動的Activity替換成我們的占坑Activity
*/
Class<?> activityManagerNativeClass = Class.forName("android.app" +
".ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// gDefault是一個 android.util.Singleton對象; 我們取出這個單例里面的字段
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ActivityManagerNative 的gDefault對象里面原始的 IActivityManager對象
final Object rawIActivityManager = mInstanceField.get(gDefault);
// 創(chuàng)建一個這個對象的代理對象, 然后替換這個字段, 讓我們的代理對象幫忙干活
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {iActivityManagerInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if ("startActivity".equals(method.getName())) {
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
Intent newIntent = new Intent();
// 替身Activity的包名, 也就是我們自己的包名
String stubPackage = "com.jerey.activityplugin";
// 這里我們把啟動的Activity臨時替換為 StubActivity
ComponentName componentName = new ComponentName(stubPackage,
TmpActivity.class
.getName());
newIntent.setComponent(componentName);
// 把我們原始要啟動的TargetActivity先存起來
newIntent.putExtra(EXTRA_TARGET_INTENT, raw);
// 替換掉Intent, 達到欺騙AMS的目的
args[index] = newIntent;
Log.d(TAG, "hook succes{s");
return method.invoke(rawIActivityManager, args);
}
return method.invoke(rawIActivityManager, args);
}
});
mInstanceField.set(gDefault, proxy);
/**
* 欺騙ActivityThread
*/
// 先獲取到當前的ActivityThread對象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField
("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 由于ActivityThread一個進程只有一個,我們獲取這個對象的mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(currentActivityThread);
// 設置它的回調, 根據源碼:
// 我們自己給他設置一個回調,就會替代之前的回調;
// public void dispatchMessage(Message msg) {
// if (msg.callback != null) {
// handleCallback(msg);
// } else {
// if (mCallback != null) {
// if (mCallback.handleMessage(msg)) {
// return;
// }
// }
// handleMessage(msg);
// }
// }
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 100) {
Object obj = msg.obj;
// 根據源碼:
// 這個對象是 ActivityClientRecord 類型
// 我們修改它的intent字段為我們原來保存的即可.
// 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);
try {
// 把替身恢復成真身
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent raw = (Intent) intent.get(obj);
Intent target = raw.getParcelableExtra(EXTRA_TARGET_INTENT);
raw.setComponent(target.getComponent());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mH.handleMessage(msg);
}
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
上面的代碼芦拿,我們先反射拿到ActivityManagerNative
,然后動態(tài)代理IActivityManager
,Hook其startActivity
方法查邢,在里面替換掉intent蔗崎,并將真實的Intent存放在假Intent的參數(shù)里面。
在系統(tǒng)最后調用打開假Intent的時候扰藕,我們從Intent中取出參數(shù)缓苛,并打開真正想打開的Activity。
- 打開Activity
我們和正常使用一樣邓深,startActivity就能打開我們未注冊的Activity了未桥。
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, UnregisterActivity.class));
}
});
Demo路徑:https://github.com/Jerey-Jobs/AppPluginDemos
總結
上面只是一個Demo,不能支持support包的AppCompatActivity
,真正的完整的插件化庫任務是艱巨的芥备!
還要支持其他組件冬耿,都是很麻煩的事情。
本文作者:Anderson/Jerey_Jobs
博客地址 : http://jerey.cn/
簡書地址 : Anderson大碼渣
github地址 : https://github.com/Jerey-Jobs