北大青鸟网站开发,数据上传网站,有哪些好的网站制作公司,搭建一个影视网站背景 在Android开发行业里#xff0c;插件化已经不是一门新鲜的技术了#xff0c;在稍大的平台型App上早已是标配。进入2017年#xff0c;Atlas、Replugin、VirtualAPK相继开源#xff0c;标志着插件化技术进入了成熟阶段。但纵观各大插件框架#xff0c;都是基于自身App的… 背景 在Android开发行业里插件化已经不是一门新鲜的技术了在稍大的平台型App上早已是标配。进入2017年Atlas、Replugin、VirtualAPK相继开源标志着插件化技术进入了成熟阶段。但纵观各大插件框架都是基于自身App的业务来开发的目标或多或少都有区别所以很难有一个插件框架能一统江湖解决所有问题。最后就是绕不开的兼容性问题Android每次版本升级都会给各个插件化框架带来不少冲击都要费劲心思适配一番更别提国内各个厂商对在ROM上做的定制了正如VirtualAPK的作者任玉刚所说完成一个插件化框架的 Demo 并不是多难的事儿然而要开发一款完善的插件化框架却并非易事。 早在2014年美团移动技术团队就开始关注插件化技术了并且意识到插件化架构是美团这种平台型App最好的集成形式。但由于业务增长、迭代、演化太快受限于业务耦合和架构问题插件化一直无法落地。到了2016年底经过一系列的代码架构调整、技术调研我们终于能腾出手来让插件化技术落地了。 美团平台与点评平台一起目前承载了美团所有事业群近20条业务线的业务。其中有相对成熟的业务比如外卖、餐饮他们对插件的要求是稳定性高不能因为上了插件导致业务出问题也有迭代变化很快的业务如交通、跑腿、金融等他们要求能快速迭代上线此外由于美团App采用的二进制AAR依赖方式集成已经运转了两年各种基础设施都很成熟了我们不希望换成插件形式的接入之后还要改变开发模式。所以美团平台对插件的诉求主要集中在兼容性和不影响开发模式这两个点上。 美团插件化框架的原理和特点 插件框架的兼容性体现在多个方面由于Android机制的问题有些写法在插件化之前运行的很正常但是接入插件化之后就变得不再有效。如果不解决兼容性问题插件化的口碑和推广都会很大阻碍。兼容性不仅仅指的是对Android系统、Android碎片化的兼容还要对已有基础库和构建工具的兼容。特别是后者我们经常看到Github上开源的插件化框架里面有大量Crash的Issue就是这个方面原因导致的。每个App的基础库和既有构建工具都不太一样所以为自己的App选择合适的方案显得尤为重要。 为了保证插件的兼容性并能无缝兼容当前AAR开发模式美团的插件化框架方案主要做了以下几点 插件的Dex加载使用类似MultiDex方案保证对反射的兼容替换所有的AssetManager保证对资源访问的兼容四大组件预埋代理新增Activity让构建系统来抹平AAR开发模式和插件化开发模式的差异MultiDex和组件代理这里不细说网上有很多这方面的博客可以参考。下面重点说一下美团插件化框架对资源的处理和支持AAR、插件一键切换的构建系统。 资源处理 了解插件化的读者都知道如果希望访问插件的资源需要使用AssetManager把插件的路径加入进去。但这样做是远远不够的。这是因为如果希望这个AssetManager生效就得把它放到具体的Resources或ResourcesImpl里面大部分插件化框架的做法是封装一个包含插件路径AssetManager的Resources然后插件中只使用这一个Resources。 这样的做法大多数情况是有效的但是有至少3个问题 1. 如果在插件中使用了宿主Resources如getApplicationContext().getResources()。 这个Resources就无法访问插件的资源了 2. 插件外的Resources 并不唯一需要全局查找和替换 3. Resoureces在使用的过程中有很多中间产物例如Theme、TypedArray等等。这些都需要清理才能正常使用 要完全解决这些问题我们另辟蹊径做了一个全局的资源处理方式 * 新建或者使用已有AssetManger加载插件资源 * 查找所有的Resources/Theme替换其中的AssetManger * 清理Resources缓存重建Theme * AssetManager的重建保护防止丢失插件路径 这个方案和InstantRun有点类似但是原生InstantRun有太多的问题 * 清理顺序错误应该先清理Applicaiton后清理Activity * Resources/Theme找不全没有极端情况应对机制 * Theme光清理不重建 * 完全不适配 Support包里面自己埋的“雷” 等等 举个例子Theme找不全InstantRun会替换Theme中的AssetManager做法是从每个Activity里面获取。 for (Activity activity : activities) {... // 省略部分代码Resources.Theme theme activity.getTheme();try {try {Field ma Resources.Theme.class.getDeclaredField(mAssets);ma.setAccessible(true);ma.set(theme, newAssetManager);} catch (NoSuchFieldException ignore) {Field themeField Resources.Theme.class.getDeclaredField(mThemeImpl);themeField.setAccessible(true);Object impl themeField.get(theme);Field ma impl.getClass().getDeclaredField(mAssets);ma.setAccessible(true);ma.set(impl, newAssetManager);}...} catch (Throwable e) {Log.e(LOG_TAG, Failed to update existing theme for activity activity,e);}pruneResourceCaches(resources);
}这个思路是对的但是远不够。例如Google 自己的Support包里面的一个类 android.support.v7.view.ContextThemeWrapper会生成一个新的Theme保存 public class ContextThemeWrapper extends ContextWrapper {private int mThemeResource;private Resources.Theme mTheme;private LayoutInflater mInflater;...private void initializeTheme() {final boolean first mTheme null;if (first) {mTheme getResources().newTheme();final Resources.Theme theme getBaseContext().getTheme();if (theme ! null) {mTheme.setTo(theme);}}onApplyThemeResource(mTheme, mThemeResource, first);}...
}如果没有替换了这个ContextThemeWrapper的Theme假如配合它使用的Reources/AssetManager是新的就会导致Crash java.lang.RuntimeException: Failed to resolve attribute at index 0 这是大部分开源框架都存在的Issue。 为了解决这个问题我们不仅清理所有Activity的Theme还清理了所有View的Context。 try {ListView list getAllChildViews(activity.getWindow().getDecorView());for (View v : list) {Context context v.getContext();if (context instanceof ContextThemeWrapper context ! activity !clearContextWrapperCaches.contains(context)) {clearContextWrapperCaches.add((ContextThemeWrapper) context);pruneSupportContextThemeWrapper((ContextThemeWrapper) context, newAssetManager); // 清理Theme}}
} catch (Throwable ignore) {Log.e(LOG_TAG, ignore.getMessage());
}但是这些做法还是不能解决所有问题有时候为了实现一个产品需求Android工程师可能会采取一些非常规写法导致变成插件之后资源加载失败。比如在一个自己的类里面保存了Theme。这种问题不可能一个个改业务代码那能不能让插件兼容这种写法呢 我们对这种行为也做了兼容修改字节码。 了解虚拟机指令的同学都知道如果要保存一个类变量对应的虚拟机的指令是PUTFIELD/PUTSTATIC以此为突破口用ASM写一个MethodVisitor static class MyMethodVisitor extends MethodVisitor {int stackSize 0;MyMethodVisitor(MethodVisitor mv) {super(Opcodes.ASM5, mv);}Overridepublic void visitFieldInsn(int opcode, String owner, String name, String desc) {if (opcode Opcodes.PUTFIELD || opcode Opcodes.PUTSTATIC) {if (Landroid/content/res/Resources$Theme;.equals(desc)) {stackSize 1;visitInsn(Opcodes.DUP);super.visitMethodInsn(Opcodes.INVOKESTATIC,com/meituan/hydra/runtime/Transformer,collectTheme,(Landroid/content/res/Resources$Theme;)V,false);}}super.visitFieldInsn(opcode, owner, name, desc);}Overridepublic void visitMaxs(int maxStack, int maxLocals) {super.visitMaxs(maxStack stackSize, maxLocals);stackSize 0;}
}这样可以保证所有被类保存的Theme都会被收集起来在插件安装后统一清理、重建就行了。 插件的构建系统 为了实现在AAR集成方式和插件集成方式之间一键切换并解决插件化遇到的“API陷阱”的问题我们把大量的时间花在构建系统的建设上面我们的构建系统除了支持常规的构建插件之外还支持已有构建工具和未来可能存在的构建工具。 我们将正常构建过程分为4个阶段 1. 收集依赖 2. 处理资源 3. 处理代码 4. 打包签名 那么如何保证对已有Gradle插件的支持最好的方式是不对这个构建过程做太多干涉保证它们的正常、按顺序执行。所以我们的构建系统在不干扰这个顺序的基础上把插件的构建过程插入进去对应正常构建的4个阶段主要做了如下工作。 宿主解析依赖之后分析插件的依赖进行依赖仲裁和引用计数分析宿主处理资源之前处理插件资源规避了资源访问的陷阱生成需要Merge的资源列表给宿主开发 美团AAPT 处理插件资源宿主处理代码之中规避插件API使用的陷阱复用宿主的Proguard和Gradle插件做到对原生构建过程的最大兼容。我们也修复了Proguard Mapping的问题后续会有专门的博客介绍宿主打包签名之前构建插件APK计算升级兼容的Hash特征使用V2签名加快运行时的验证构建系统的流程如下图 API陷阱 我们做插件化构建系统还有另外一个非常重要的目的就是规避“API陷阱”。下面是接入Atlas所需要注意的部分问题我们称为“API陷阱” 1. Activity通过overridePendingTransition使用的切换动画的文件要放在主APK中 2. Bundle内如果有用到自定义style那么style的parent如果也是自定义的话parent的定义必须位于主APK中这是由于5.0以后系统内style查找的固有逻辑导致的容器内暂不能完全兼容 3. Bundle内部如果有so则安装时so由于无法解压到APK lib目录中对于直接通过native层使用dlopen来使用so的情况会存在一定限制且会影响后续so动态部署所以目前bundle内so不建议使用dlopen的方式来使用 那我们是怎么做的呢 我们用构建工具自动对插件资源进行处理。先把插件独有的依赖从宿主处理的依赖里面抽离然后为宿主单独准备一份资源目录这个目录只包括需要merge的资源。 那么怎么抽离呢我们看下处理资源的task是如何获得这些资源的。代码在com.android.build.gradle.tasks.MergeResources$ConfigAction ConventionMappingHelper.map(mergeResourcesTask, inputResourceSets,new CallableListResourceSet() {Overridepublic ListResourceSet call() throws Exception {ListFile generatedResFolders Lists.newArrayList(scope.getRenderscriptResOutputDir(),scope.getGeneratedResOutputDir());if (variantData.getExtraGeneratedResFolders() ! null) {generatedResFolders.addAll(variantData.getExtraGeneratedResFolders());}if (scope.getMicroApkTask() ! null variantData.getVariantConfiguration().getBuildType().isEmbedMicroApp()) {generatedResFolders.add(scope.getMicroApkResDirectory());}return variantData.getVariantConfiguration().getResourceSets(generatedResFolders, includeDependencies, validateEnabled);}});了解Groovy的同学都知道设置这个inputResourceSets其实就是重写了这个mergeResourcesTask的getInputResourceSets方法。那么我们也这可以这么做 ConventionMapping conventionMapping (ConventionMapping) ((GroovyObject) variantData.mergeResourcesTask).getProperty(conventionMapping);
def srcMethod conventionMapping._mappings.get(inputResourceSets);conventionMapping.map(inputResourceSets, new CallableListResourceSet() {Overridepublic ListResourceSet call() throws Exception {ListResourceSet res srcMethod.getValue(null, null)... // 处理这个resreturn res}
})对于第一个问题前面提到的插件为宿主提供的资源文件夹如果是一个空的没有任何意义。我们会分析插件的AndroidManifest.xml文件以此作为root遍历被它引用的所有的资源不管是文件还是values文件夹下面的单个value全部merge进这个文件夹。 但是只是AndroidManifest.xml文件是不够的所有传给系统的文件比如提到的“Activity通过overridePendingTransition使用的切换动画的文件”也一并放进这个文件夹。这里需要使用ASM扫描插件的所有API调用类似上面的Theme查找不细展开了。 第二个问题把插件values里面style的parent也作为检索的root遍历merge。 第三个问题API陷阱除了资源还有大量的代码级别的上面的插件so加载问题就是很典型的一个例子正常使用System.loadLibrary(path)是不行的但是可以把它转化成下面的写法我们发现如果插件dlopen来加载的so之前被加载过就不会出现这个问题。 private static Pattern compile Pattern.compile(dlopen failed: library \lib(.).so\ not found);
public static void system_loadLibrary(String libname) {LinkedListString list new LinkedList();list.add(libname);while (list.size() 0) {try {System.loadLibrary(list.peekFirst());list.pop();} catch (UnsatisfiedLinkError error) {// dlopen failed: library libglog_init.so not foundMatcher matcher compile.matcher(error.getMessage());if (matcher.matches()) {String group matcher.group(1);list.addFirst(group);} else {throw error;}}}
}当然需要替换的API很多如 getIdentifier、Notification、Glide等等不一一列举。 总结 本文主要介绍美团插件化的设计思路和一些实现。经过我们这些努力美团平台的业务集成模式可以平滑的在AAR集成模式和插件化集成模式之间无缝切换且上线几乎没出现兼容问题。目前在美团App最近的几个版本上搜索、收藏、订单等重要模块都是插件形式加载的。 作者简介 李挺美团技术专家2014年加入美团。先后负责过多个业务项目和技术项目致力于推动AOP和字节码技术在美团的应用。曾独立负责美团App预装项目并推动预装实现自动化。主导了美团插件化框架的设计和开发工作目前工作重心是美团插件化框架的布道和推广。 夏伟美团资深工程师2017年加入美团。目前从事美团插件化开发美团平台的一些底层工具优化如AAPT、ProGuard等专注于Hook技术、逆向研究习惯从源码中寻找解决方案。 美团平台客户端技术团队负责美团平台的基础业务和移动基础设施的开发工作。基于海量用户的美团平台支撑了美团多条业务线的快速发展。同时我们也在移动开发技术方面做了一些积极的探索在动态化、质量保障、开发模型等方面有一定积累。客户端技术团队积极采用开源技术的同时也把我们的一些积累回馈给开源社区希望跟业界一起推动移动开发效率、质量的提升。