ClassLoader 加载 dex 文件 继承关系
BootClassLoader: 加载 Android Framework 层的 class 文件。
PathClassLoader: 加载 dex 文件,包括 jar、zip、apk 中的 dex 文件。
DexClassLoader: 加载 dex 文件,包括 jar、zip、apk 中的 dex 文件。
ClassLoader 加载 Class 的流程
双亲委托机制: 首先检测这个类是否已经被加载了,如果已经加载了,直接获取并返回。如果没有被加载,当 parent 不为 null 时,调用 parent 的 loadClass() 进行加载;当 parent 为 null 时,调用 findClass() 方法,依次递归,如果找到了或者加载了就返回,如果既没找到也加载不了,才自己去加载。
BootClassLoader#findClass() 是直接调用了 Class.classForName() 方法; DexClassLoader#findClass() 是加载传入的 APK 文件中的所有 dexFile 到 dexElements 数组,然后再调用 Element#findClass() 查找 dexFile 中是否包含指定的 class。
执行插件中的方法 原理
1、创建插件的 DexClassLoader 类加载器,然后通过反射获取插件的 dexElements 值;
1 2 DexClassLoader ----dexElements[]
2、获取宿主的 PathClassLoader 类加载器,然后通过反射获取宿主的 dexElements 值;
1 2 3 BaseDexClassLoader ----pathList --------dexElements[]
3、合并宿主的 dexElements 与插件的 dexElements,生成新的 Element[]; 4、最后通过反射将新的 Element[] 赋值给宿主的 dexElements。
核心代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 private boolean loadPluginDex (String apkPath) { try { Class baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader" ); Field pathListField = baseDexClassLoader.getDeclaredField("pathList" ); pathListField.setAccessible(true ); DexClassLoader dexClassLoader = new DexClassLoader (apkPath, getCacheDir().getAbsolutePath(), null , getClassLoader()); Object pluginPathList = pathListField.get(dexClassLoader); Class pluginPathListClass = pluginPathList.getClass(); Field pluginDexElementsField = pluginPathListClass.getDeclaredField("dexElements" ); pluginDexElementsField.setAccessible(true ); Object[] pluginDexElements = (Object[]) pluginDexElementsField.get(pluginPathList); PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader(); Object hostPathList = pathListField.get(pathClassLoader); Class hostPathListClass = hostPathList.getClass(); Field hostDexElementsField = hostPathListClass.getDeclaredField("dexElements" ); hostDexElementsField.setAccessible(true ); Object[] hostDexElements = (Object[]) hostDexElementsField.get(hostPathList); Object[] dexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), pluginDexElements.length + hostDexElements.length); System.arraycopy(pluginDexElements, 0 , dexElements, 0 , pluginDexElements.length); System.arraycopy(hostDexElements, 0 , dexElements, pluginDexElements.length, hostDexElements.length); hostDexElementsField.set(hostPathList, dexElements); return true ; } catch (Exception e) { e.printStackTrace(); } return false ; }private String loadPluginMethod () { try { Class<?> threadClazz = Class.forName("io.weichao.plugin.util.PluginUtil" ); Method method = threadClazz.getMethod("getMessage" ); return (String) method.invoke(null ); } catch (Exception e) { e.printStackTrace(); } return null ; }
启动插件中的 Activity 关键难点——无法通过 AMS 检测 插件的 Activity 没有在宿主的清单文件中注册。
启动 Activity 流程
绕过 AMS 检测——hook hook: 劫持,改变代码的正常执行流程。
1、在宿主中创建一个 ProxyActivity 继承自 Activity,并且在 manifest 中注册; 2、当启动插件的 Activity 时,在 AMS 检测前,找到一个 hook 点,然后通过 hook 将启动插件 Activity 的 Intent 替换成启动 ProxyActivity 的 Intent;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 Activity#startActivity() ----startActivityForResult() Instrumentation#execStartActivity() ----ActivityManager.getService().startActivity(..., intent, ...) 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; } };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; } } } Activity#startActivity() ----startActivityForResult() Instrumentation#execStartActivity() ----ActivityManagerNative.getDefault().startActivity(..., intent, ...) 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; } };
3、在 AMS 检测完后,找到一个 hook 点,然后通过 hook 将启动 ProxyActivity 的 Intent 替换成启动插件 Activity 的 Intent;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ActivityStackSupervisor#realStartActivityLocked() ActivityThread#scheduleLaunchActivity(intent, ...) ----sendMessage(H.LAUNCH_ACTIVITY, r) Handler#dispatchMessage(msg) ----callback(msg) Callback ----mCallback final H mH = new H ();private static volatile ActivityThread sCurrentActivityThread; ActivityStackSupervisor#realStartActivityLocked() clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent (r.intent), ...) ClientTransaction ----List<ClientTransactionItem> mService.getLifecycleManager().scheduleTransaction(clientTransaction) ActivityThread#scheduleLaunchActivity(clientTransaction) ----sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, clientTransaction) Handler#dispatchMessage(msg) ----callback(msg) Callback ----mCallback
4、启动插件的 Activity。
核心代码 将启动插件 Activity 的 Intent 替换成启动 ProxyActivity 的 Intent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 try { Object singleton = null ; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Class<?> clazz = Class.forName("android.app.ActivityManager" ); Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton" ); singletonField.setAccessible(true ); singleton = singletonField.get(null ); } else { Class<?> clazz = Class.forName("android.app.ActivityManagerNative" ); Field singletonField = clazz.getDeclaredField("gDefault" ); singletonField.setAccessible(true ); singleton = singletonField.get(null ); } Class<?> singletonClass = Class.forName("android.util.Singleton" ); Field mInstanceField = singletonClass.getDeclaredField("mInstance" ); mInstanceField.setAccessible(true ); final Object mInstance = mInstanceField.get(singleton); Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager" ); Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class []{iActivityManagerClass}, new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if ("startActivity" .equals(method.getName())) { int index = 0 ; for (int i = 0 ; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break ; } } Intent intent = (Intent) args[index]; Intent proxyIntent = new Intent (); proxyIntent.setClassName("io.weichao.plugin_demo" , ProxyActivity.class.getName()); proxyIntent.putExtra(TARGET_INTENT, intent); args[index] = proxyIntent; } return method.invoke(mInstance, args); } }); mInstanceField.set(singleton, proxyInstance); } catch (Exception e) { e.printStackTrace(); Log.e("aaaaa" , "aaaaa" + e.getMessage()); }
将启动 ProxyActivity 的 Intent 替换成启动插件 Activity 的 Intent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 try { Class<?> clazz = Class.forName("android.app.ActivityThread" ); Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread" ); sCurrentActivityThreadField.setAccessible(true ); Object activityThread = sCurrentActivityThreadField.get(null ); Field mHField = clazz.getDeclaredField("mH" ); mHField.setAccessible(true ); Object mH = mHField.get(activityThread); Class<?> handlerClass = Class.forName("android.os.Handler" ); Field mCallbackField = handlerClass.getDeclaredField("mCallback" ); mCallbackField.setAccessible(true ); mCallbackField.set(mH, new Handler .Callback() { @Override public boolean handleMessage (@NonNull Message msg) { switch (msg.what) { case 100 : try { Field intentField = msg.obj.getClass().getDeclaredField("intent" ); intentField.setAccessible(true ); Intent proxyIntent = (Intent) intentField.get(msg.obj); Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT); if (intent != null ) { intentField.set(msg.obj, intent); } } catch (Exception e) { e.printStackTrace(); Log.e("aaaaa" , "aaaaa" + e.getMessage()); } break ; case 159 : try { Class<?> clazz = Class.forName("android.app.servertransaction.ClientTransaction" ); Field mActivityCallbacksField = clazz.getDeclaredField("mActivityCallbacks" ); mActivityCallbacksField.setAccessible(true ); List activityCallbacks = (List) mActivityCallbacksField.get(msg.obj); for (int i = 0 ; i < activityCallbacks.size(); i++) { if (activityCallbacks.get(i).getClass().getName() .equals("android.app.servertransaction.LaunchActivityItem" )) { Object launchActivityItem = activityCallbacks.get(i); Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent" ); mIntentField.setAccessible(true ); Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem); Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT); if (intent != null ) { mIntentField.set(launchActivityItem, intent); } break ; } } } catch (Exception e) { e.printStackTrace(); Log.e("aaaaa" , "aaaaa" + e.getMessage()); } break ; } return false ; } }); } catch (Exception e) { e.printStackTrace(); Log.e("aaaaa" , "aaaaa" + e.getMessage()); }
启动插件的 Activity 1 2 3 Intent intent = new Intent (); intent.setComponent(new ComponentName ("io.weichao.plugin" , "io.weichao.plugin.activity.MainActivity" )); startActivity(intent);
让插件中的 Activity 加载插件中的资源 前期铺垫 1、加载 res 中的资源是通过 Resources,但是最后也是调用 AssetManager,只是会先通过 id 查找。AssetManager 可以加载未编译过的资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 getResources().getString(R.string.app_name);public String getString (@StringRes int id) throws NotFoundException { return getText(id).toString(); }@NonNull public CharSequence getText (@StringRes int id) throws NotFoundException { CharSequence res = mResourcesImpl.getAssets().getResourceText(id); if (res != null ) { return res; } throw new NotFoundException ("String resource ID #0x" + Integer.toHexString(id)); }
2、启动 Activity 时会调用 ActivityThread#performLaunchActivity(),此时建立了 Resources、AssetManager 和 Context 的绑定关系,并且指定了资源目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Activity performLaunchActivity (ActivityClientRecord r, Intent customIntent) { ... ContextImpl appContext = createBaseContextForActivity(r); ... activity.attach(appContext, ...); ... return activity; }private ContextImpl createBaseContextForActivity (ActivityClientRecord r) { ... ContextImpl appContext = ContextImpl.createActivityContext( this , r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); ... return appContext; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static ContextImpl createActivityContext (ActivityThread mainThread, LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { ... ContextImpl context = new ContextImpl (null , mainThread, packageInfo, activityInfo.splitName, activityToken, null , 0 , classLoader); ... final ResourcesManager resourcesManager = ResourcesManager.getInstance(); context.setResources(resourcesManager.createBaseActivityResources(activityToken, packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, classLoader)); ... return context; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public @Nullable Resources createBaseActivityResources (@NonNull IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader) { ... return getOrCreateResources(activityToken, key, classLoader); }private @Nullable Resources getOrCreateResources (@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { ... ResourcesImpl resourcesImpl = createResourcesImpl(key); ... }private @Nullable ResourcesImpl createResourcesImpl (@NonNull ResourcesKey key) { ... final AssetManager assets = createAssetManager(key); ... final ResourcesImpl impl = new ResourcesImpl (assets, dm, config, daj); ... return impl; }protected @Nullable AssetManager createAssetManager (@NonNull final ResourcesKey key) { AssetManager assets = new AssetManager (); if (key.mResDir != null ) { if (assets.addAssetPath(key.mResDir) == 0 ) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null ; } } ... }
原理 创建 AssetManager,反射 addAssetPath 方法设置插件的资源目录; 创建 Resources,绑定该 AssetManager; 在 Application 中重写 getResources(),使用该 Resources; 在插件的 Activity 中重写 getResources(),使用 Application 中的 Resources。 核心代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static Resources loadResource (Context context) { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath" , String.class); addAssetPathMethod.setAccessible(true ); addAssetPathMethod.invoke(assetManager, apkPath); Resources resources = context.getResources(); return new Resources (assetManager, resources.getDisplayMetrics(), resources.getConfiguration()); } catch (Exception e) { e.printStackTrace(); } return null ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyApplication extends Application { private Resources resources; @Override public void onCreate () { super .onCreate(); resources = LoadUtil.loadResource(this ); } @Override public Resources getResources () { return resources == null ? super .getResources() : resources; } }
1 2 3 4 5 6 7 8 9 10 public class MainActivity extends Activity { @Override public Resources getResources () { if (getApplication() != null && getApplication().getResources() != null ) { return getApplication().getResources(); } return super .getResources(); } }
当宿主的 Activity 和插件的 Activity 都继承于 AppCompatActivity 时会报错 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 java.lang.RuntimeException: Unable to start activity ComponentInfo{io.weichao.plugin/io.weichao.plugin.activity.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:753) at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659) at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552) at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161) at io.weichao.plugin.activity.MainActivity.onCreate(MainActivity.java:15) 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) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
报错的原因 当宿主和插件都使用 AppCompatActivity 时,由于编译时会将资源建立映射关系,在宿主和插件中分别建立了 1 次映射关系,且这 2 次映射关系基本上不会相同。在宿主中 key1 -> value1,在插件中 key2 -> value2。 但是 AppCompatDelegateImpl 类只会在宿主中被加载一次,也就是宿主和插件都使用 key1 查找资源。
解决办法 让插件使用自己的 Resource。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MainActivity extends AppCompatActivity { protected Context mContext; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Resources resource = LoadUtil.getResource(getApplication()); mContext = new ContextThemeWrapper (getBaseContext(), 0 ); Class<? extends Context > clazz = mContext.getClass(); try { Field mResourcesField = clazz.getDeclaredField("mResources" ); mResourcesField.setAccessible(true ); mResourcesField.set(mContext, resource); } catch (Exception e) { e.printStackTrace(); } View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null ); setContentView(view); } }