内容简介:(1)AssetManager资源插件化实现方式:主要通过反射创建新的AssetManager对象,通过addAssetPath加载插件资源。适用于资源独立的情况,无法调用宿主资源
- res目录下存放的资源文件。编译时会在R文件中生成资源文件的十六进制值。res目录下资源通过Context.getResource方法获取到Resource对象,然后通过getXXX获取资源。
- assets目录下存放的原始文件,编译时不会被编译。通过AssetManager的open方法获取目录下文件资源,AssetManager来源于Resources类的getAssets方法
2、Resources
(1)AssetManager
- AssetManage有一个addAssetPath方法,将apk路径传入,Resources就能访问当前apk的所有资源。可以通过反射的方式将插件apk路径传入addAssetPath方法。
- AssetManager内部有一个NDK方法,用来访问文件。apk打包时会生成一个resources.arsc文件,是一个Hash表,存放着十六进制和资源的对应关系
二、VirtualApk插件资源加载
资源插件化实现方式:
- 合并资源:将插件的资源合并到宿主的Resources中,可以访问宿主的资源。可能存在插件和宿主的资源id重复的情况。
解决方式:
(1)修改Android打包流程中使用到的aapt命令,为插件的资源id指定前缀,避免与宿主资源id冲突。
(2)在Android打包生成resources.arsc文件之后,对这个resources.arsc文件进行修改。 - 单独加载插件资源:每个插件都会构造单独的Resources去加载插件资源,不能访问宿主资源
1、Resources创建
#LoadedPlugin public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { ...... this.mResources = createResources(context, getPackageName(), apk); ...... } protected Resources createResources(Context context, String packageName, File apk) throws Exception { if (Constants.COMBINE_RESOURCES) { //插件资源合并到宿主中,插件可访问宿主资源 return ResourcesManager.createResources(context, packageName, apk); } else { //插件创建独立的Resources,不与宿主关联 Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk); return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } } 复制代码
2、插件资源独立
主要通过反射创建新的AssetManager对象,通过addAssetPath加载插件资源。适用于资源独立的情况,无法调用宿主资源
protected AssetManager createAssetManager(Context context, File apk) throws Exception { //通过反射创建新的AssetManager对象,通过addAssetPath加载插件资源 AssetManager am = AssetManager.class.newInstance(); Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath()); return am; } 复制代码
3、插件资源合并
先获取到宿主资源的AssetManager,再通过反射调用AssetManager的addAssetPath添加插件资源,返回新的Resources
#ResourcesManager public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception { //根据版本创建Resources对象 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //(1) return createResourcesForN(hostContext, packageName, apk); } //(2) Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath()); ResourcesManager.hookResources(hostContext, resources); return resources; } //创建Resource对象 private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception { //宿主Resources对象 Resources hostResources = hostContext.getResources(); Resources newResources = null; AssetManager assetManager; Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { //通过反射创建AssetManager assetManager = AssetManager.class.newInstance(); reflector.bind(assetManager); final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);; if (cookie1 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir); } } else { //获取到宿主的AssetManager assetManager = hostResources.getAssets(); reflector.bind(assetManager); } final int cookie2 = reflector.call(apk); if (cookie2 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + apk); } List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); for (LoadedPlugin plugin : pluginList) { final int cookie3 = reflector.call(plugin.getLocation()); if (cookie3 == 0) { throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation()); } } //通过不同的手机品牌创建Resources对象 if (isMiUi(hostResources)) { newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); } else if (isVivo(hostResources)) { newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); } else if (isNubia(hostResources)) { newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); } else if (isNotRawResources(hostResources)) { newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); } else { // is raw android resources newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } // lastly, sync all LoadedPlugin to newResources for (LoadedPlugin plugin : pluginList) { plugin.updateResources(newResources); } return newResources; } 复制代码
Hook住了ContextImpl的mResources和LoadedApk的mResources
public static void hookResources(Context base, Resources resources) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return; } try { Reflector reflector = Reflector.with(base); //hook mResources reflector.field("mResources").set(resources); Object loadedApk = reflector.field("mPackageInfo").get(); //hook mResources Reflector.with(loadedApk).field("mResources").set(resources); Object activityThread = ActivityThread.currentActivityThread(); Object resManager; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { resManager = android.app.ResourcesManager.getInstance(); } else { resManager = Reflector.with(activityThread).field("mResourcesManager").get(); } Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field("mActiveResources").get(); Object key = map.keySet().iterator().next(); map.put(key, new WeakReference<>(resources)); } catch (Exception e) { Log.w(TAG, e); } } 复制代码
4、Activity启动资源处理
#VAInstrumentation @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); Log.i(TAG, String.format("newActivity[%s]", className)); } catch (ClassNotFoundException e) { ...... // 通过反射将Resources赋值给Activity的mResources Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); return newActivity(activity); } return newActivity(mBase.newActivity(cl, className, intent)); } 复制代码
三、so的插件化
so的插件化,有两种方案:基于System.Load和基于System.LoadLibrary。
1、VirtualApk的实现
#LoadedPlugin protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception { File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR); String dexOutputPath = dexOutputDir.getAbsolutePath(); DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent); if (Constants.COMBINE_CLASSLOADER) { DexUtil.insertDex(loader, parent, libsDir); } return loader; } 复制代码
创建了一个DexClassLoader,解析出每个插件apk中的so文件,解压到某个位置,把这些路径用逗号连接起来成为一个字符串,放到DexClassLoader的构造函数的第3个参数中。这样插件中的so,就和宿主App中jniLib目录下的so一样,通过System.loadLibrary方法来加载。
#DexUtil public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { Object baseDexElements = getDexElements(getPathList(baseClassLoader)); Object newDexElements = getDexElements(getPathList(dexClassLoader)); //将宿主和插件的DexElements合并得到allDexElements Object allDexElements = combineArray(baseDexElements, newDexElements); Object pathList = getPathList(baseClassLoader); //通过反射将dexElements替换为allDexElements Reflector.with(pathList).field("dexElements").set(allDexElements); insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir); } 复制代码
so插件化核心代码
private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { if (sHasInsertedNativeLibrary) { return; } sHasInsertedNativeLibrary = true; Context context = ActivityThread.currentApplication(); //获取宿主的PathList Object basePathList = getPathList(baseClassLoader); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { Reflector reflector = Reflector.with(basePathList); List<File> nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get(); nativeLibraryDirectories.add(nativeLibsDir); //获取到宿主的so集合 Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get(); final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements); Object newPathList = getPathList(dexClassLoader); //获取到插件的so集合 Object newNativeLibraryPathElements = reflector.get(newPathList); Class<?> elementClass = newNativeLibraryPathElements.getClass().getComponentType(); Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1); //将原来宿主的so集合拷贝到新集合中 System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength); Field soPathField; if (Build.VERSION.SDK_INT >= 26) { soPathField = elementClass.getDeclaredField("path"); } else { soPathField = elementClass.getDeclaredField("dir"); } soPathField.setAccessible(true); //将插件的so集合拷贝到新集合中 final int newArrayLength = Array.getLength(newNativeLibraryPathElements); for (int i = 0; i < newArrayLength; i++) { Object element = Array.get(newNativeLibraryPathElements, i); String dir = ((File)soPathField.get(element)).getAbsolutePath(); if (dir.contains(Constants.NATIVE_DIR)) { Array.set(allNativeLibraryPathElements, baseArrayLength, element); break; } } //将宿主和插件so的合集替换上去 reflector.set(allNativeLibraryPathElements); } else { Reflector reflector = Reflector.with(basePathList).field("nativeLibraryDirectories"); File[] nativeLibraryDirectories = reflector.get(); final int N = nativeLibraryDirectories.length; File[] newNativeLibraryDirectories = new File[N + 1]; System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N); newNativeLibraryDirectories[N] = nativeLibsDir; reflector.set(newNativeLibraryDirectories); } } 复制代码
获取宿主so集合,获取插件so集合,二者合并后通过反射替换原so集合,插件so文件就能正常被加载了
四、VirtualApk的Service插件化
1、Service启动分析
插件化分析:
- Service启动跟Instrumentation没关系,不能通过Hook Instrumentation来处理
- 在Standard模式下多次启动占位Activity可创建多个Activity,但是多次启动占位Service并不会创建多个Service实例
- 通过代理分发实现:启动一个代理Service统一管理,拦截所有Service方法,修改为startService到代理Service,在代理Service的onStartCommond统一管理,创建/停止目标service。
2、Hook IActivityManager
VirtualApk初始化时通过ActivityManagerProxy Hook了IActivityManager。启动服务时,通过ActivityManagerProxy拦截到了startService的操作
public class ActivityManagerProxy implements InvocationHandler { protected Object startService(Object proxy, Method method, Object[] args) throws Throwable { IApplicationThread appThread = (IApplicationThread) args[0]; //跳转的intent Intent target = (Intent) args[1]; //检查Service信息 ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0); if (null == resolveInfo || null == resolveInfo.serviceInfo) { // is host service return method.invoke(this.mActivityManager, args); } return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE); } protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command); return mPluginManager.getHostContext().startService(wrapperIntent); } protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { // 将目标Service的相关信息存储起来 target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name)); String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation(); // 根据processName判断是否为远程服务 boolean local = PluginUtil.isLocalService(serviceInfo); // 判断交给LocalService还是RemoteService进行处理 Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class; // 参数传递 Intent intent = new Intent(); intent.setClass(mPluginManager.getHostContext(), delegate); intent.putExtra(RemoteService.EXTRA_TARGET, target); intent.putExtra(RemoteService.EXTRA_COMMAND, command); intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation); if (extras != null) { intent.putExtras(extras); } return intent; } } 复制代码
3、LocalService
public class LocalService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { ...... switch (command) { case EXTRA_COMMAND_START_SERVICE: { //获取ActivityThread ActivityThread mainThread = ActivityThread.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { //获取Service service = this.mPluginManager.getComponentsHandler().getService(component); } else { try { //通过DexClassLoader加载Service service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); //通过attach方法绑定Context attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); //调用Service的onCreate方法 service.onCreate(); this.mPluginManager.getComponentsHandler().rememberService(component, service); } catch (Throwable t) { return START_STICKY; } } //调用service的onStartCommand方法 service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement()); break; } ...... } } } 复制代码
4、RemoteService
public class RemoteService extends LocalService { private static final String TAG = Constants.TAG_PREFIX + "RemoteService"; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { return super.onStartCommand(intent, flags, startId); } //获取目标service的intent Intent target = intent.getParcelableExtra(EXTRA_TARGET); if (target != null) { //获取插件路径 String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION); ComponentName component = target.getComponent(); LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component); if (plugin == null && pluginLocation != null) { try { //加载apk插件文件 PluginManager.getInstance(this).loadPlugin(new File(pluginLocation)); } catch (Exception e) { Log.w(TAG, e); } } } return super.onStartCommand(intent, flags, startId); } } 复制代码
启动远程服务多了一步加载其他插件的Service的操作
5、Service插件化总结
- 初始化时通过ActivityManagerProxy Hook住了IActivityManager。
- 服务启动时通过ActivityManagerProxy拦截,判断是否为远程服务,如果为远程服务,启动RemoteService,如果为同进程服务则启动LocalService。
- 如果为LocalService,则通过DexClassLoader加载目标Service,然后反射调用attach方法绑定Context,然后执行Service的onCreate、onStartCommand方法
- 如果为RemoteService,则先加载插件的远程Service,后续跟LocalService一致。
参考资料:
- VirtualAPK 资源篇
- 《Android插件化开发指南》
- 《Android进阶解密》
以上所述就是小编给大家介绍的《Android进阶(十)资源和Service的插件化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android 插件化(二):资源与打包流程
- 使用Gradle插件生成资源ID映射文件
- Mad-Metasploit:一款多功能Metasploit自定义模块、插件&资源脚本套件
- 如何扩展AngularJS资源($资源)的构造函数?
- 聊聊Kubernetes计算资源模型(上)——资源抽象、计量与调度
- APICloud解密本地资源到逆向APP算法到通用资源解密
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。