上文在手把手带你实现最简单的插件化[一]咱们实现了最简单的插件化,也介绍了插件化实现过程当中须要用到的知识点,最后咱们实现了从app中加载sd卡中的dex文件,调用dex中Test类的方法。今天咱们将实现Activity的插件化,Activity是须要在清单文件中注册的,插件中的Activity没有在宿主的清单文件中注册,那么咱们如何启动它呢?java
Activity是四大组件用的最频繁的组件,Activity的插件化也是各大插件化框架必须实现的功能。Activity插件化与Activity的启动有着密切的关系。android
Activity的启动过程须要由应用进程与AMS共同完成,当要启动一个Activity时,应用进程会向AMS发起请求,AMS收到这个包含要启动的Activity信息的请求后会进行一些列的处理以及权限校验,处理校验完成后回调到应用进程,由Activity所属的应用进程完成Activity的启动。数组
所以现有的插件化框架都会有一套越过AndroidMainfest.xml注册而启动Activity的机制,本文就带大家实现和分析这一套机制。性能优化
由于AMS会进行Activity的处理和权限校验(是否注册),处理校验完会回到应用进程,由Activity所属的应用进程完成Activity的启动。那么思路就来了,咱们能够在宿主App中建立一个ProxyActivity继承自Activity,而且在清单中注册,当启动插件中的Activity的时候,在系统检测前,找到一个Hook点,而后经过Hook将插件Activity替换成ProxyActivity,等到AMS检测完以后,再找一个Hook点将它们换回来,这样就实现了插件Activity的启动。思路虽然简单,可是须要熟悉Activity启动流程,动态代理,反射,Handler等原理,因此其实并不简单,须要很深的功力。markdown
先来看一下Activity的启动流程app
经过这张图咱们能够肯定Hook点的大体位置。框架
咱们在启动Activity通常经过Intent包装后调用startActivity来启动,咱们能够在AMS检查以前将Intent中的要启动的Activity替换为咱们本地已经注册过的ProxyActivity,同时把咱们要启动的插件Activity保存在Intent中,而后在通过AMS校验以后,再把Intent中的ProxyActivity再替换为插件中的Activity并启动,也就是说可以修改Intent的地方就能够做为Hook点。ide
这里要强调一下,查找Hook点应该尽可能找静态变量或者单例对象,尽可能Hook public的对象和方法。为何呢?由于静态变量好获取,不容易被改变,并且静态变量只要找一个,不是静态变量就可能有多个对象,须要进一步的判断;为何要找public方法呢,由于private方法可能被内部调用,影响该类的多个方法,固然这不是主要缘由(public也有可能),主要是public是提供给外部使用的,通常是不容易改变。oop
下面咱们进入源码post
//Activity.java
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
···
}
复制代码
而后咱们进入Instrumentation的execStartActivity方法中
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
···
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
//这里就是咱们的Hook点,替换传入startActivity方法中的Intent参数
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;
}
复制代码
为何就能直接看出这是个Hook点呢,由于ActivityManager.getService().startActivity
这个调用中含有参数Intent,同时getService()
方法是一个静态public方法,方便hook
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
复制代码
找到该Hook点,经过动态代理(IActivityManager是个接口),咱们要生成一个代理对象,咱们要代理的是ActivityManager.getService()
返回的对象,同时替换它的参数Intent
//建立动态代理对象
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
// do something
// Intent的修改 -- 过滤
/** * IActivityManager类的方法 * startActivity(whoThread, who.getBasePackageName(), intent, * intent.resolveTypeIfNeeded(who.getContentResolver()), * token, target != null ? target.mEmbeddedID : null, * requestCode, 0, null, options) */
//过滤
if ("startActivity".equals(method.getName())) {
int index = -1;
//获取Intent参数在args数组中的index值
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
//获得原始的Intent对象
Intent intent = (Intent) args[index];
//生成代理proxyIntent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.jackie.plugingodemo", ProxyActivity.class.getName());
//保存原始的Intent对象
proxyIntent.putExtra(TARGET_INTENT, intent);
//使用proxyIntent替换数组中的Intent
args[index] = proxyIntent;
}
//args method须要的参数 --不改变原有的执行流程
//mInstance 系统的IActivityManager对象
return method.invoke(mInstance, args);
}
});
复制代码
接着咱们再使用反射将系统中的IActivityManager对象替换为咱们的代理对象proxyInstance,如何替换?咱们来看一下源码。
//ActivityManager.class
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的源码
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;
}
}
}
复制代码
能够看到,IActivityManagerSingleton.get()
实际上返回的就是mInstance对象,接下来咱们要替换的就是这个对象,代码以下:
//获取Singleton<T>对象
Field singletonField = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { //8.0
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
singletonField = clazz.getDeclaredField("gDefault");
} else {
Class<?> clazz = Class.forName("android.app.ActivityManager");
singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
}
singletonField.setAccessible(true);
Object singleton = singletonField.get(null); //静态的能够直接获取,传入null
//获取mInstance对象,mInstance是非静态的,mInstance对象是系统的IActivityManager对象,也就是ActivityManager.getService()
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
//建立动态代理对象
···
//替换
mInstanceField.set(singleton, proxyInstance);
复制代码
到此为止,咱们的第一次Hook就已经实现了,下面咱们来看第二次Hook点。
从前面的那张图咱们能够看到在出来的时候,会调用H(handler)
的handleMessage方法,在handleMessage方法中(注意这里是android 7.0,和8.0/9.0的源码不一样)
public void handleMessage(Message msg) {
1452 if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
1453 switch (msg.what) {
1454 case LAUNCH_ACTIVITY: {
1455 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
1456 final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
1457
1458 r.packageInfo = getPackageInfoNoCheck(
1459 r.activityInfo.applicationInfo, r.compatInfo);
1460 handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
1461 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1462 } break;
复制代码
在这里咱们并无看到咱们的Intent,继续玩下看handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
2688 // If we are getting ready to gc after going to the background, well
2689 // we are back active so skip it.
2690 unscheduleGcIdler();
2691 mSomeActivitiesChanged = true;
2692
2693 if (r.profilerInfo != null) {
2694 mProfiler.setProfiler(r.profilerInfo);
2695 mProfiler.startProfiling();
2696 }
2697
2698 // Make sure we are running with the most recent config.
2699 handleConfigurationChanged(null, null);
2700
2701 if (localLOGV) Slog.v(
2702 TAG, "Handling launch of " + r);
2703
2704 // Initialize before creating the activity
2705 WindowManagerGlobal.initialize();
2706
2707 Activity a = performLaunchActivity(r, customIntent);
···
复制代码
注意这个方法的参数customIntent并非咱们想要的Intent,由于上面该参数传的是null。继续看performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
2515 // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
2516
2517 ActivityInfo aInfo = r.activityInfo;
2518 if (r.packageInfo == null) {
2519 r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
2520 Context.CONTEXT_INCLUDE_CODE);
2521 }
2522
2523 ComponentName component = r.intent.getComponent();
2524 if (component == null) {
2525 component = r.intent.resolveActivity(
2526 mInitialApplication.getPackageManager());
2527 r.intent.setComponent(component);
2528 }
复制代码
能够看到该方法中有(ActivityClientRecord)r.intent
方法,注意,不是说有看到Intent的能够Hook,也要看这个intent所属的是什么对象,也就是说你要熟悉系统中的一些常见类,ActivityRecord和ActivityClientRecord都是保存Activity信息的对象。只不过,ActivityRecord归system_server进程使用,ActivityClientRecord归App进程使用。
因此这里能够对ActivityClientRecord的intent进行hook,ActivityClientRecord方法中的intent(非静态)
static final class ActivityClientRecord {
310 IBinder token;
311 int ident;
312 Intent intent;
复制代码
要获取非静态的intent,首先咱们要获取ActivityClientRecord对象,那么若是获取该对象呢?倒推回去,performLaunchActivity被handleLaunchActivity调用,而后handleLaunchActivity在处理LAUNCH_ACTIVITY消息时被调用
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
复制代码
能够看到,咱们的这个r(ActivityClientRecord)
其实是个msg.obj,也就是说能拿到msg(Message)就能够拿到r对象了,那怎么拿到msg呢,也就是咱们上面说的mCallback,将mCallback做为hook点,替换或建立整个mCallback,咱们就能够拿到该消息了。
下面咱们来看一下Handler的源码:
/** * 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);
}
}
复制代码
若是不了解Handler的源码,能够看看我以前写的文章Handler的初级、中级、高级问法,你都掌握了吗?,消息的发送最终会调用dispatchMessage方法,而后分发给handleMessage方法(若是该方法有被调用的话),仔细看该方法,若是Handle.mCallback
不为空的话,会首先执行mCallback.handleMessage(msg)
方法,同时只有在mCallback.handleMessage(msg)
返回为false的时候,才会继续执行下面的handleMessage方法,这个很是重要。咱们再来看系统的H(Handler)
//ActivityThread.java
final H mH = new H();
//Handler.java
113 public Handler() { //第一个参数是callback
114 this(null, false);
115 }
复制代码
也就是说系统的这个Handler在传callback参数时是空的,没有Callback,那么咱们须要本身建立一个Callback
// 建立的 callback
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 经过msg 能够拿到 Intent,能够换回执行插件的Intent
// 找到 Intent的方便替换的地方 --- 在这个类里面 ActivityClientRecord --- Intent intent 非静态
// msg.obj == ActivityClientRecord
switch (msg.what) {
case 100:
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// 启动代理Intent
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;
case 159:
try {
// 获取 mActivityCallbacks 对象
Field mActivityCallbacksField = msg.obj.getClass()
.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);
// 获取启动代理的 Intent
Field mIntentField = launchActivityItem.getClass()
.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
// 目标 intent 替换 proxyIntent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 必须 return false
return false;
}
};
复制代码
同时在该方法中从Intent中拿出插件的Activity,最终启动该Activity。而后经过反射给系统的H(Handler)
设置一个Callback
// 获取 ActivityThread 类的 Class 对象
Class<?> clazz = Class.forName("android.app.ActivityThread");
// 获取 ActivityThread 对象
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
// 获取 mH 对象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 建立的 callback
···
// 替换系统的 callBack
mCallbackField.set(mH, callback);
复制代码
到这来咱们就实现了Activity的插件化,固然Hook点不止这些,有兴趣的读者能够本身寻找,同时在不一样版本上源码的实现方式也不一样,须要进行适配。在Android10上,系统对源码作了较大的修改,有兴趣的能够本身实现一波。
最后你可能会碰到这么一个异常
2020-11-29 12:27:33.247 19124-19124/com.jackie.plugingodemo D/AppCompatDelegate: Exception while getting ActivityInfo
android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.jackie.plugingodemo/com.jackie.plugin.PluginActivity}
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
at androidx.appcompat.app.AppCompatDelegateImpl.isActivityManifestHandlingUiMode(AppCompatDelegateImpl.java:2649)
at androidx.appcompat.app.AppCompatDelegateImpl.updateForNightMode(AppCompatDelegateImpl.java:2499)
at androidx.appcompat.app.AppCompatDelegateImpl.applyDayNight(AppCompatDelegateImpl.java:2374)
at androidx.appcompat.app.AppCompatDelegateImpl.onCreate(AppCompatDelegateImpl.java:494)
at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:114)
at com.jackie.plugin.PluginActivity.onCreate(PluginActivity.java:12)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
复制代码
该异常提示咱们找不到ActivityInfo,而后咱们到抛出异常的方法看一下
private boolean isActivityManifestHandlingUiMode() {
if (!mActivityHandlesUiModeChecked && mHost instanceof Activity) {
final PackageManager pm = mContext.getPackageManager();
if (pm == null) {
// If we don't have a PackageManager, return false. Don't set
// the checked flag though so we still check again later
return false;
}
try {
int flags = 0;
// On newer versions of the OS we need to pass direct boot
// flags so that getActivityInfo doesn't crash under strict
// mode checks
if (Build.VERSION.SDK_INT >= 29) {
flags = PackageManager.MATCH_DIRECT_BOOT_AUTO
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
} else if (Build.VERSION.SDK_INT >= 24) {
flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
final ActivityInfo info = pm.getActivityInfo(
new ComponentName(mContext, mHost.getClass()), flags);
mActivityHandlesUiMode = info != null
&& (info.configChanges & ActivityInfo.CONFIG_UI_MODE) != 0;
} catch (PackageManager.NameNotFoundException e) {
// This shouldn't happen but let's not crash because of it, we'll just log and
// return false (since most apps won't be handling it)
Log.d(TAG, "Exception while getting ActivityInfo", e);
mActivityHandlesUiMode = false;
}
}
// Flip the checked flag so we don't check again
mActivityHandlesUiModeChecked = true;
return mActivityHandlesUiMode;
}
复制代码
而后进如pm.getActivityInfo
方法
/** * Retrieve all of the information we know about a particular activity * class. * * @param component The full component name (i.e. * com.google.apps.contacts/com.google.apps.contacts. * ContactsList) of an Activity class. * @param flags Additional option flags to modify the data returned. * @return An {@link ActivityInfo} containing information about the * activity. * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */
public abstract ActivityInfo getActivityInfo(ComponentName component, @ComponentInfoFlags int flags) throws NameNotFoundException;
复制代码
经过查看该方法注释,能够看到当找不到该Activity的包名,也就是在系统中找不到,就会抛出异常了,由于咱们的插件包名和宿主App的包名不一致致使的,不过系统也为咱们捕获了该异常了。
Activity插件化的实现很重要的一点是寻找Hook点,如何寻找Hook点须要咱们对Activity启动流程很是熟悉。插件化涉及到的技术其实不少,四大组件的启动流程,AMS/PKMS等系统服务启动流程,Handler,反射,动态代理等等,里面运用到不少Android自身的知识,而不只仅是Java的知识,有点像Android技术的“集大成者”。因此若是你想成为一个高级开发,就应该懂得像插件化,热修复这样的技术难点。最后,看完本文喜欢的点个赞和关注吧。
近期的一些文章:
Handler的初级、中级、高级问法,你都掌握了吗?(深度好文)
性能优化:为何要使用SparseArray和ArrayMap替代HashMap?
参考文章: