热更新Tinker研究(六):TinkerPatchPlugin
来源:互联网 发布:python 财经数据接口 编辑:程序博客网 时间:2024/05/20 15:37
热更新Tinker研究(一):运行tinker-sample-android
热更新Tinker研究(二):结合源码学习Dex格式
热更新Tinker研究(三):加载补丁
热更新Tinker研究(四):TinkerLoader
热更新Tinker研究(五):Application的隔离
热更新Tinker研究(六):TinkerPatchPlugin
热更新Tinker研究(七):Dex的patch文件生成
热更新Tinker研究(八):res和so的patch文件生成
热更新Tinker研究(九):Dex文件的patch
热更新Tinker研究(十):Res文件的patch
热更新Tinker研究(十一):so文件的patch
热更新Tinker研究(六):TinkerPatchPlugin
在我们运行tinkerPatchDebug或者tinkerPatchRelease任务的时候,会执行TinkerPatchPlugin的apply(),实际上编写一个gradle的task只需要继承Plugin即可。
整个插件做了以下几件事,
整个插件构造的task情况如下图:
部分过程已经省略,左边蓝色的部分是原来就存在的task,右边绿色的task是插件构建的task。
一、初始化
1,创建一些扩展属性
project的扩展tinkerPatch
project.extensions.create('tinkerPatch', TinkerPatchExtension)
TinkerPatchExtension的相关源码如下,它的字段就是支持我们去配置的。
public class TinkerPatchExtension { /** * Specifies the old apk path to diff with the new apk */ String oldApk /** * If there is loader class changes, * or Activity, Service, Receiver, Provider change, it will terminal * if ignoreWarning is false * default: false */ boolean ignoreWarning /** * If sign the patch file with the android signConfig * default: true */ boolean useSign /** * whether use tinker * default: true */ boolean tinkerEnable ......}
创建tinkerPatch的扩展
tinkerPatch的扩展属性设置如下,
project.tinkerPatch.extensions.create('buildConfig', TinkerBuildConfigExtension, project) project.tinkerPatch.extensions.create('dex', TinkerDexExtension, project) project.tinkerPatch.extensions.create('lib', TinkerLibExtension) project.tinkerPatch.extensions.create('res', TinkerResourceExtension) project.tinkerPatch.extensions.create('packageConfig', TinkerPackageConfigExtension, project) project.tinkerPatch.extensions.create('sevenZip', TinkerSevenZipExtension, project)
具体属性的每个设置类,这里不进行讨论。
2,检测和设置相关属性
检查是否包含com.android.application插件
//判断需要android插件 if (!project.plugins.hasPlugin('com.android.application')) { throw new GradleException('generateTinkerApk: Android Application plugin required') }
设置jumboMode和preDexLibaries属性
//open jumboMode 增加字符串和方法的容量 android.dexOptions.jumboMode = true //close preDexLibraries try { android.dexOptions.preDexLibraries = false } catch (Throwable e) { //no preDexLibraries field, just continue }
jumboMode设置为true,会允许更多的字符串个数。preDexLibraries会预编译一些libraries,为了更好的提升增量编译的速度,但是clean之后的编译就会变得比较慢。这里preDexLibraries设置为false。
检查是否开启了instantRun
由于插件不支持instantRun,所以这里会进行检测。
def instantRunTask = getInstantRunTask(project, variantName) if (instantRunTask != null) { throw new GradleException( "Tinker does not support instant run mode, please trigger build" + " by assemble${variantName} or disable instant run" + " in 'File->Settings...'." ) }
二、TinkerPatchSchemaTask
TinkerPatchSchemaTask的作用主要是生成patch,由于依赖于assemble任务,会在assemble任务之后执行。这里 InputParam.Builder 会负责接收一系列参数,包含oldApkPath,newApkPath,loaderPattern等等。
InputParam.Builder builder = new InputParam.Builder() if (configuration.useSign) { if (signConfig == null) { throw new GradleException("can't the get signConfig for this build") } builder.setSignFile(signConfig.storeFile) .setKeypass(signConfig.keyPassword) .setStorealias(signConfig.keyAlias) .setStorepass(signConfig.storePassword) } builder.setOldApk(configuration.oldApk) .setNewApk(buildApkPath) .setOutBuilder(outputFolder) .setIgnoreWarning(configuration.ignoreWarning) .setDexFilePattern(new ArrayList(configuration.dex.pattern)) .setDexLoaderPattern(new ArrayList(configuration.dex.loader)) .setDexMode(configuration.dex.dexMode) .setSoFilePattern(new ArrayList(configuration.lib.pattern)) .setResourceFilePattern(new ArrayList(configuration.res.pattern)) .setResourceIgnoreChangePattern(new ArrayList(configuration.res.ignoreChange)) .setResourceLargeModSize(configuration.res.largeModSize) .setUseApplyResource(configuration.buildConfig.usingResourceMapping) .setConfigFields(new HashMap(configuration.packageConfig.getFields())) .setSevenZipPath(configuration.sevenZip.path) .setUseSign(configuration.useSign)
然后由Runner负责具体的生成patch工作,与之相关的类有ApkDecoder、PatchInfo和PatchBuilder。
//gen patch ApkDecoder decoder = new ApkDecoder(config); decoder.onAllPatchesStart(); decoder.patch(config.mOldApkFile, config.mNewApkFile); decoder.onAllPatchesEnd(); //gen meta file and version file PatchInfo info = new PatchInfo(config); info.gen(); //build patch PatchBuilder builder = new PatchBuilder(config); builder.buildPatch();
ApkDecoder主要负责对比oldApk和newApk,并生成对应的文件,这里是进行dex,so,res对比,生成差分包的关键步骤buzhou。PatchInfo主要负责由前面得到的信息,来生成package_meta.txt。PatchBuilder负责将patch.apk从tempPath拷贝到最终的路径,并且进行签名,以及利用7-zip去压缩文件。
三、TinkerManifestTask
TinkerManifestTask主要是负责处理manifest相关的工作,被processResources所依赖,所以在执行processResources之前会执行TinkerManifestTask。下面是TinkerManifestTask的taskAction的部分源码。
@TaskAction def updateManifest() { // Parse the AndroidManifest.xml String tinkerValue = project.extensions.tinkerPatch.buildConfig.tinkerId if (tinkerValue == null || tinkerValue.isEmpty()) { throw new GradleException('tinkerId is not set!!!') } tinkerValue = TINKER_ID_PREFIX + tinkerValue project.logger.error("tinker add ${tinkerValue} to your AndroidManifest.xml ${manifestPath}") writeManifestMeta(manifestPath, TINKER_ID, tinkerValue) addApplicationToLoaderPattern() File manifestFile = new File(manifestPath) if (manifestFile.exists()) { FileOperation.copyFileUsingStream(manifestFile, project.file(MANIFEST_XML)) project.logger.error("tinker gen AndroidManifest.xml in ${MANIFEST_XML}") } }
这里主要也是做了两件事情,一个是将TINKER_ID写入到manifest,二是将当前的Application类,添加到LoaderPattern。LoaderPattern的作用主要是负责标记哪些类属于Loader主体类,不能用于热更新。
四、TinkerResourceIdTask
TinkerResourceIdTask也是在processResources之前执行,主要是负责处理资源文件的id,以保证和baseApk中资源id一致,这样的话在java层的代码dex文件中会做较小的改动。其中控制资源id的两个最重要的文件是ids.xml和public.xml。aapt会根据这两个文件去分配id。
@TaskAction def applyResourceId() { String resourceMappingFile = project.extensions.tinkerPatch.buildConfig.applyResourceMapping // Parse the public.xml and ids.xml if (!FileOperation.isLegalFile(resourceMappingFile)) { project.logger.error("apply resource mapping file ${resourceMappingFile} is illegal, just ignore") return } String idsXml = resDir + "/values/ids.xml"; String publicXml = resDir + "/values/public.xml"; FileOperation.deleteFile(idsXml); FileOperation.deleteFile(publicXml); List resourceDirectoryList = new ArrayList() resourceDirectoryList.add(resDir) project.logger.error("we build ${project.getName()} apk with apply resource mapping file ${resourceMappingFile}") project.extensions.tinkerPatch.buildConfig.usingResourceMapping = true Map> rTypeResourceMap = PatchUtil.readRTxt(resourceMappingFile) AaptResourceCollector aaptResourceCollector = AaptUtil.collectResource(resourceDirectoryList, rTypeResourceMap) PatchUtil.generatePublicResourceXml(aaptResourceCollector, idsXml, publicXml) File publicFile = new File(publicXml) if (publicFile.exists()) { FileOperation.copyFileUsingStream(publicFile, project.file(RESOURCE_PUBLIC_XML)) project.logger.error("tinker gen resource public.xml in ${RESOURCE_PUBLIC_XML}") } File idxFile = new File(idsXml) if (idxFile.exists()) { FileOperation.copyFileUsingStream(idxFile, project.file(RESOURCE_IDX_XML)) project.logger.error("tinker gen resource idx.xml in ${RESOURCE_IDX_XML}") } }
首先是根据tinkerPatch中buildConfig中指定的applyResourceMapping文件,applyResourceMapping是baseApk的R.txt的记录。去解析这个文件,然后根据这个规则生成新的ids.xml和public.xml,从而保证资源文件的id变化不大。
五、proguardConfigTask
proguardConfigTask在transformClassesAndResourcesWithProguardFor之前执行,它的作用是对于一些特殊混淆规则做处理。
对loader的关键类不进行混淆,
static final String PROGUARD_CONFIG_SETTINGS = "-keepattributes *Annotation* \n" + "-dontwarn com.tencent.tinker.anno.AnnotationProcessor \n" + "-keep @com.tencent.tinker.anno.DefaultLifeCycle public class *\n" + "-keep public class * extends android.app.Application {\n" + " *;\n" + "}\n" + "\n" + "-keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + " *;\n" + "}\n" + "-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + " *;\n" + "}\n" + "\n" + "-keep public class com.tencent.tinker.loader.TinkerLoader {\n" + " *;\n" + "}\n" + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + " *;\n" + "}\n" + "-keep public class com.tencent.tinker.loader.TinkerTestDexLoad {\n" + " *;\n" + "}\n" + "\n"
也会对tinkerPatch中dex的loader属性中设置的类进行保留,具体代码如下:
@TaskAction def updateTinkerProguardConfig() { def file = project.file(PROGUARD_CONFIG_PATH) project.logger.error("try update tinker proguard file with ${file}") // Create the directory if it doesnt exist already file.getParentFile().mkdirs() // Write our recommended proguard settings to this file FileWriter fr = new FileWriter(file.path) String applyMappingFile = project.extensions.tinkerPatch.buildConfig.applyMapping //write applymapping if (shouldApplyMapping && FileOperation.isLegalFile(applyMappingFile)) { project.logger.error("try add applymapping ${applyMappingFile} to build the package") fr.write("-applymapping " + applyMappingFile) fr.write("\n") } else { project.logger.error("applymapping file ${applyMappingFile} is illegal, just ignore") } fr.write(PROGUARD_CONFIG_SETTINGS) fr.write("#your dex.loader patterns here\n") //they will removed when apply Iterable loader = project.extensions.tinkerPatch.dex.loader for (String pattern : loader) { if (pattern.endsWith("*") && !pattern.endsWith("**")) { pattern += "*" } fr.write("-keep class " + pattern) fr.write("\n") } fr.close() // Add this proguard settings file to the list applicationVariant.getBuildType().buildType.proguardFiles(file) def files = applicationVariant.getBuildType().buildType.getProguardFiles() project.logger.error("now proguard files is ${files}") }
六、TinkerMultidexConfigTask
TinkerMultidexConfigTask在multidexTask之前执行,换句话说,multidexTask是依赖TinkerMultidexConfigTask的。它的主要职责是保证和loader相关的类必须要在mainDex文件中。其原理就是去在maindexlist中添加一些规则,和混淆相关的内容类似,这里也有一个固定的需要keep的类的设置。
static final String MULTIDEX_CONFIG_SETTINGS = "-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n" + " *;\n" + "}\n" + "\n" + "-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n" + " *;\n" + "}\n" + "\n" + "-keep public class * extends android.app.Application {\n" + " *;\n" + "}\n"
然后后面也会写入的配置的loader的相关类,把他们写入到maindexlist中去。
@TaskAction def updateTinkerProguardConfig() { File file = project.file(MULTIDEX_CONFIG_PATH) project.logger.error("try update tinker multidex keep proguard file with ${file}") // Create the directory if it doesn't exist already file.getParentFile().mkdirs() StringBuffer lines = new StringBuffer() lines.append("\n") .append("#tinker multidex keep patterns:\n") .append(MULTIDEX_CONFIG_SETTINGS) .append("\n") .append("#your dex.loader patterns here\n") Iterable loader = project.extensions.tinkerPatch.dex.loader for (String pattern : loader) { if (pattern.endsWith("*")) { if (!pattern.endsWith("**")) { pattern += "*" } } lines.append("-keep class " + pattern + " {\n" + " *;\n" + "}\n") .append("\n") } // Write our recommended proguard settings to this file FileWriter fr = new FileWriter(file.path) try { for (String line : lines) { fr.write(line) } } finally { fr.close() } File multiDexKeepProguard = null try { multiDexKeepProguard = applicationVariant.getVariantData().getScope().getManifestKeepListProguardFile() } catch (Throwable ignore) { try { multiDexKeepProguard = applicationVariant.getVariantData().getScope().getManifestKeepListFile() } catch (Throwable e) { project.logger.error("can't find getManifestKeepListFile method, exception:${e}") } } if (multiDexKeepProguard == null) { project.logger.error("auto add multidex keep pattern fail, you can only copy ${file} to your own multiDex keep proguard file yourself.") return } FileWriter manifestWriter = new FileWriter(multiDexKeepProguard, true) try { for (String line : lines) { manifestWriter.write(line) } } finally { manifestWriter.close() } }
七、给project注入一些任务
主要是给transform为DexTransform的TransformTask注入ImmutableDexTransform。ImmutableDexTransform是DexTransform的装饰类,相当于对DexTransform的功能进行补充。
DexTransform dexTransform = task.transform ImmutableDexTransform hookDexTransform = new ImmutableDexTransform(project, variant, dexTransform) project.logger.info("variant name: " + variant.name) Field field = TransformTask.class.getDeclaredField("transform") field.setAccessible(true) field.set(task, hookDexTransform)
这里面主要做的事,包含得到oldDexList,处理mainDexListFile,得到一些映射关系,生成jar,dex文件等等,这里不展开讨论。
- 热更新Tinker研究(六):TinkerPatchPlugin
- 热更新Tinker研究(四):TinkerLoader
- 热更新Tinker研究(一):运行tinker-sample-android
- 热更新Tinker研究(三):加载补丁
- 热更新Tinker研究(五):Application的隔离
- 热更新Tinker研究(九):Dex文件的patch
- 热更新Tinker研究(十):Res文件的patch
- 热更新Tinker研究(十一):so文件的patch
- 热更新Tinker 的研究与集成
- Android热更新(Tinker)
- 热更新Tinker研究(二):结合源码学习Dex格式
- 热更新Tinker研究(七):Dex的patch文件生成
- 热更新Tinker研究(八):res和so的patch文件生成
- 集成tinker热更新
- Tinker热更新
- Android热更新(3)-Bugly&Tinker 热更新实战!
- Android 热更新之tinker
- 微信tinker热更新
- 微信小程序开发:实时圆形进度条实现
- QML如何结合Qt使用GridView
- 用Monitor简单3步监控ActiveMQ
- build-in webserver in PhpStorm
- 正则表达式总结
- 热更新Tinker研究(六):TinkerPatchPlugin
- Windows bat脚本之for循环之 一
- Android Studio NDK及so文件开发
- Android AOP实现原理之字节码插桩(一)
- XTU 1220 A+B Again
- java反射
- 组件化开发中引用aar包引起的jar包冲突问题
- js基本语法
- oracle dblink