精华内容
下载资源
问答
  • tinker

    2020-12-09 03:27:11
    <div><p>Have you already provides support for MD package "tinker"? If not I am interested of working on it.</p><p>该提问来源于开源项目:easybuilders/easybuild-easyconfigs</p></div>
  • Tinker

    2020-12-08 23:33:54
    <div><p>Styles for the tinker page -- focus here is UI, I'm planning on working on hooking things up tmr. <p>Includes new components: - ColorSpectrum - Tabs</p><p>该提问来源于开源项目:...
  • tinker DEMO

    2017-02-15 22:06:30
    Tinker
  • tinker工具

    2017-03-20 09:22:30
    tinker工具
  • TinkerDemo

    2017-08-04 14:52:24
    Android 热修复TinkerDemo
  • tinker例子

    2018-07-19 17:43:04
    python中tinker, matplot的使用例子 将串口读取数据使用绘图工具显示出来
  • TINKER interface

    2020-11-20 20:09:43
    <p>This discusses maintenance of the TINKER interface. To provide some background, Mark Friedrichs wrote the initial code but he's no longer working on OpenMM. Since then I've used it ...
  • tinker修复

    2017-01-04 12:33:56
    tinker用于实现Android热更新,热修复,实现不发布版本即可更新内容
  • Tinker Demo

    2017-06-19 10:12:55
    Tinker 接入的一个简单的例子 和对应的博客一起看 效果会更好哦 http://my.csdn.net/?ref=toolbar
  • ve tried to flash a tinkerboard image (2.0.7) on a tinkerboard s. The image has been read using win32diskimager from a modified version of the tinkerboard (only apps have been installed). If I flash ...
  • TinkerBoard

    2020-11-27 21:40:23
    m trying to get this working on the TinkerBoard, I have added the revision in the file but I still get the error. Any ideas?</p><p>该提问来源于开源项目:jgarff/rpi_ws281x</p></div>
  • tinker_csharp:只需运行此命令即可安装Tinker
  • Tinker初探

    2020-11-02 23:04:59
    文章目录注册 TinkerPatch 平台SDK接入添加Gradle插件依赖集成 TinkerPatch SDK配置 tinkerpatchSupport 参数初始化 TinkerPatch SDK使用步骤在tinker-patch平台发布差异包 前两天想试一下热修复的功能,对比各大...

    2018-03-10 12:35:18


    前两天想试一下热修复的功能,对比各大平台的热修复功能,看到tinker的文档介绍,最终决定先拿Tinker试一下。

    Tinker QZone AndFix Robust
    类替换 yes yes no no
    So替换 yes no no no
    资源替换 yes yes no no
    全平台支持 yes yes no yes
    即时生效 no no yes yes
    性能损耗 较小 较大 较小 较小
    补丁包大小 较小 较大 一般 一般
    开发透明 yes yes no no
    复杂度 较低 较低 复杂 复杂
    Rom体积 Dalvik较大 较小 较小 较小
    成功率 较高 较高 一般 最高

    Tinker热补丁方案不仅支持类、So 以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做 bugfix,甚至可以替代功能的发布。Tinker 已运行在微信的数亿 Android 设备上,那么为什么你不使用 Tinker 呢?

    不得不说,我真的低估了跟着腾讯文档走的难度。

    注册 TinkerPatch 平台

    因为需要下发补丁,直接使用TinkerPatch平台就好,在这里注册http://www.tinkerpatch.com/Index/reg,注册完成后创建一个应用,拿到appKey
    然后添加一个APP版本

    SDK接入

    测试成功的工程全部文件在这里https://github.com/huangyuanlove/TestTinker,包含构建成功之后的apk文件以及一些辅助文件。

    添加Gradle插件依赖

    AndroidStudio创建一个工程,定义使用的SDK版本,我是放在了gradle.properties 这个文件中,

    TINKER_VERSION=1.9.2
    TINKERPATCH_VERSION=1.2.2

    然后在工程的build.gradle文件中添加插件依赖

    classpath “com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:${TINKERPATCH_VERSION}”

    然后添加一些其他配置,整个文件内容如下

    buildscript {
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.0.1'
            classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:${TINKERPATCH_VERSION}"
        }
    }
    if (JavaVersion.current().isJava8Compatible()) {
        allprojects {
            tasks.withType(Javadoc) {
                options.addStringOption('Xdoclint:none', '-quiet')
            }
        }
    }
    subprojects {
        tasks.withType(JavaCompile) {
            sourceCompatibility = JavaVersion.VERSION_1_7
            targetCompatibility = JavaVersion.VERSION_1_7
        }
    }
    allprojects {
        repositories {
            google()
            jcenter()
        }
    }
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    
    集成 TinkerPatch SDK

    app/build.gradle里面添加依赖

    annotationProcessor("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    compileOnly("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    implementation("com.tinkerpatch.sdk:tinkerpatch-android-sdk:${TINKERPATCH_VERSION}") { changing = true }
    

    为了配置方便,我们把TinkerPatchSupport相关的配置放在一个单独的gradle文件中,在app下创建一个tinkerpatch.gradle,我们需要在app/build.grale文件中引用

    apply from: ‘tinkerpatch.gradle’

    配置 tinkerpatchSupport 参数

    编辑 app/tinkerpatch.gralde文件

    
    apply plugin: 'tinkerpatch-support'
    
    /**
     * TODO: 请按自己的需求修改为适应自己工程的参数
     */
    def bakPath = file("${buildDir}/bakApk/")
    def baseInfo = "app-1.0.0-0309-21-30-56" //构建差异文件时使用
    def variantName = "debug"
    
    /**
     * 对于插件各参数的详细解析请参考
     * http://tinkerpatch.com/Docs/SDK
     */
    tinkerpatchSupport {
        /** 可以在debug的时候关闭 tinkerPatch **/
        /** 当disable tinker的时候需要添加multiDexKeepProguard和proguardFiles,
            这些配置文件本身由tinkerPatch的插件自动添加,当你disable后需要手动添加
            你可以copy本示例中的proguardRules.pro和tinkerMultidexKeep.pro,
            需要你手动修改'tinker.sample.android.app'本示例的包名为你自己的包名, com.xxx前缀的包名不用修改
         **/
        tinkerEnable = true
        reflectApplication = false
        /**
         * 是否开启加固模式,只能在APK将要进行加固时使用,否则会patch失败。
         * 如果只在某个渠道使用了加固,可使用多flavors配置
         **/
        protectedApp = false
        /**
         * 实验功能
         * 补丁是否支持新增 Activity (新增Activity的exported属性必须为false)
         **/
        supportComponent = true
    
        autoBackupApkPath = "${bakPath}"
    
        appKey = "2b662623551153ee"
    
        /** 注意: 若发布新的全量包, appVersion一定要更新 **/
        appVersion = "1.0.0"
    
        def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
        def name = "${project.name}-${variantName}"
    
        baseApkFile = "${pathPrefix}/${name}.apk"
        baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
        baseResourceRFile = "${pathPrefix}/${name}-R.txt"
    
        /**
         *  若有编译多flavors需求, 可以参照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
         *  注意: 除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息(相关工具:walle 或者 packer-ng)
         **/
    }
    
    /**
     * 用于用户在代码中判断tinkerPatch是否被使能
     */
    android {
        defaultConfig {
            buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
        }
    }
    
    /**
     * 一般来说,我们无需对下面的参数做任何的修改
     * 对于各参数的详细介绍请参考:
     * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
     */
    tinkerPatch {
        ignoreWarning = false
        useSign = true
        dex {
            dexMode = "jar"
            pattern = ["classes*.dex"]
            loader = []
        }
        lib {
            pattern = ["lib/*/*.so"]
        }
    
        res {
            pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            ignoreChange = []
            largeModSize = 100
        }
    
        packageConfig {
        }
        sevenZip {
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
        }
        buildConfig {
            keepDexApply = false
        }
    }
    

    每个参数的含义如下

    参数 默认值 描述
    tinkerEnable true 是否开启 tinkerpatchSupport 插件功能
    appKey “” 在 TinkerPatch 平台 申请的 appkey
    appVersion “” 在 TinkerPatch 平台 输入的版本号,注意,我们使用 appVersion 作为 TinkerId, 我们需要保证每个发布出去的基础安装包的 appVersion 都不一样。
    reflectApplication false 是否反射 Application 实现一键接入;一般来说,接入 Tinker 我们需要改造我们
    autoBackupApkPath “” 将每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置
    baseApkFile “” 基准包的文件路径, 对应 tinker 插件中的 oldApk 参数;编译补丁包时,必需指定基准版本的 apk,默认值为空,则表示不是进行补丁包的编译。
    baseProguardMappingFile “” 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数;在编译新的 apk 时候,我们希望通过保持基准 apk 的 proguard 混淆方式,从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。
    baseResourceRFile “” 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数;在编译新的apk时候,我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小,同时也避免由于 Resource Id 改变导致 remote view 异常。
    protectedApp false 是否开启支持加固,注意:只有在使用加固时才能开启此开关
    supportComponent false 是否开启支持在补丁包中动态增加Activity 注意:新增Activity的Exported属性必须为false
    backupFileNameFormat ‘${appName}-${variantName}’ 格式化命名备份文件 这里请使用单引号
    初始化 TinkerPatch SDK

    这里推荐使用改造之后的ApplicationLike,对应tinkerpatch.gradle文件中的reflectApplication = false,这里给出了完整的ApplicationLike类,可以在这里查看https://github.com/huangyuanlove/TestTinker/blob/master/app/src/main/java/com/huangyuan/testtinker/SampleApplicationLike.java
    其中对于类的注解中的 application 的值,就是我们应用的Application类,需要在AndroidManifest.xml中的application标签中配置

    @DefaultLifeCycle(application = "com.huangyuanlove.testtinker.SampleApplication",
                      flags = ShareConstants.TINKER_ENABLE_ALL,
                      loadVerifyFlag = false)`
    

    注意:初始化的代码建议紧跟 super.onCreate(),并且所有进程都需要初始化,已达到所有进程都可以被 patch 的目的
    如果你确定只想在主进程中初始化 tinkerPatch,那也请至少在 :patch 进程中初始化,否则会有造成 :patch 进程crash,无法使补丁生效

    我们在实际应用过程中,可以在登陆等关键地方去调用TinkerPatch.with().fetchPatchUpdate(true)来检测有没有新的补丁包,若有,则去下载。下载完成补丁包后,sdk会自动去合成新的安装包,并且在息屏的时候自动重启主线程去加载新的文件,或者调用ShareTinkerInternals.killAllOtherProcess(getApplicationContext()); android.os.Process.killProcess(android.os.Process.myPid());来完成杀死主线程的目的。

    使用步骤

    首先构建基础包,模拟用户当前使用的版本。
    在gradle中找到下图所示的 assembleRelease或者assembleDebugtask,需要注意的是,如果构建基础包使用的是debug,那么在构建patch包的时候也要选择debug,还有就是尽量把app/tinkerpatch.gradle中定义的variantName改成一致的。
    基础包构建成功后,会在app/build/bakApk文件夹下生成对应的文件,找到和你构建时间一致的包。
    现在修改代码或者布局文件(模拟修复bug),修改清单文件AndroidManifest.xml中的versionName和versionCode。
    修改app/tinkerpatch.gradle文件,将其中定义的baseInfo修改为上面提到的路径。这时候不需要修改该文件中的appVersion
    在gradle中找到tinker任务包,找到tinkerPatchDebug或者tinkerPatchRelease,构建差异包(补丁文件)。构建成功后会在app/build/outputs/apk/tinkerPatch文件夹中
    在这里插入图片描述
    在这里插入图片描述

    现在我们已经成功构建的差异包patch-signed-7zip.apk,现在只需要将差异包上传到tinker-patch平台就可以了。

    在tinker-patch平台发布差异包

    我们登陆tinker-patch平台,找到在刚开始创建的项目,在该项目里面添加一个App版本,注意这里的App版本号要和tinkerpatch.gradle里面定义的appVersion一致,在官方文档中也提到过这一点:

    每一个 APP 版本对应一个已经发布的 base apk, 这里我们可以使用 APP 版本作为 TinkerID。我们需要保证每个发布的 APK 都采用不用的 APP 版本。

    创建好app版本之后,点击发布新补丁,选择补丁文件patch-signed-7zip.apk,填写一下备注就好了,这里有四种补丁的下发方式开发预览全量下发条件下发灰度下发、具体差异可以点击去查看。
    同时我们也可以在平台对应的软件版本中的实时监控里面看到补丁的下载以及合成应用次数。


    以上

    展开全文
  • Tinker原理

    2021-02-13 23:17:55
    Tinker采用的是下发差分包,然后在手机端合成全量的dex文件进行加载。而在build.gradle配置中的tinkerPatch dex.loader = ["com.tencent.tinker.loader.*", "tinker.sample.android.app.SampleApplication", "tinker...

    加载补丁dex

    Tinker采用的是下发差分包,然后在手机端合成全量的dex文件进行加载。而在build.gradle配置中的tinkerPatch

    dex.loader = ["com.tencent.tinker.loader.*",
    "tinker.sample.android.app.SampleApplication",
    "tinker.sample.android.app.BaseBuildInfo"
    ]
    

    这个配置中的类不会出现在任何全量补丁dex里,也就是说在合成后,这些类还在老的dex文件中,比如在补丁前dex顺序是这样的:oldDex1 -> oldDex2 -> oldDex3…,那么假如修改了dex1中的文件,那么补丁顺序是这样的newDex1 -> oldDex1 -> oldDex2…其中合成后的newDex1中的类是oldDex1中除了dex.loader中标明的类之外的所有类,dex.loader中的类依然在oldDex1中。

    由于Tinker的方案是基于Multidex实现的修改dexElements的顺序实现的,所以最终还是要修改classLoder中dexPathList中dexElements的顺序。Android中有两种ClassLoader用于加载dex文件,BootClassLoader、PathClassLoader和DexClassLoader都是继承自BaseDexClassLoader

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.originalPath = dexPath;
            this.pathList =
                new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            Class clazz = pathList.findClass(name);
            if (clazz == null) {
                throw new ClassNotFoundException(name);
            }
            return clazz;
        }
        
    //DexPathList
        public Class findClass(String name) {
            for (Element element : dexElements) {
                DexFile dex = element.dexFile;
                if (dex != null) {
                    Class clazz = dex.loadClassBinaryName(name, definingContext);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }
            return null;
        }
    

    最终在DexPathList的findClass中遍历dexElements,谁在前面用谁。dexElements是在方法makeDexElements中生成的,我们的目的就是hook这个方法把dex插入到dexElements的前面。

    Multidex.install()中是把dex插到dexElements的前面,Multidex是把其余的dex插到后面。相同的就是都是分版本加载

    Tinker是将dex前置,Multidex是将dex后置

    Android6.0以后把makeDexElements给改了,改成了makePathElements(List,File,List),如果找不到的话再找一下makeDexElements(List,File,List)。其余没啥区别。

    在Dalvik虚拟机中,总是在运行时通过JIT(Just-In—Time)把字节码文件编译成机器码文件再执行,这样跑起来程序就很慢,所在ART上,改为AOT(Ahead-Of—Time)提前编译,即在安装应用或OTA系统升级时提前把字节码编译成机器码,这样就可以直接执行了,提高了运行效率。但是AOT有个缺点就是每次执行的时间都太长了,并且占用的ROM空间又很大,所以在Android N上Google做了混合编译同时支持JIT和AOT。混合编译的作用简单来说,在应用运行时分析运行过的代码以及“热代码”,并将配置存储下来。在设备空闲与充电时,ART仅仅编译这份配置中的“热代码”。

    就是在应用安装和首次运行不做AOT编译,先让用户愉快的玩耍起来,然后把在运行中JIT解释执行的那部分代码收集起来,在手机空闲的时候通过dex2aot编译生成一份名为app image的base.art文件,然后在下次启动的时候一次性把app image加载进来到缓存,预先加载代替用时查找以提升应用的性能。

    app image中已经存在的类会被插入到ClassLoader的ClassTable,再次加载类时,直接从ClassTable中取而不会走DefineClass。假设base.art文件在补丁前已经存在,这里存在三种情况:

    1.补丁修改的类都不appimage中;这种情况是最理想的,此时补丁机制依然有效;
    2.补丁修改的类部分在appimage中;这种情况我们只能更新一部分的类,此时是最危险的。一部分类是新的,一部分类是旧的,app可能会出现地址错乱而出现crash。
    3.补丁修改的类全部在appimage中;这种情况只是造成补丁不生效,app并不会因此造成crash。

    Tinker的解决方案是,完全废弃掉PathClassloader,而采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。

    我们按步骤进行:

    1.新建一个AndroidNClassLoader 它的parent是originPathClassLoader。注意,PathClassLoader的optimizedDirectory只能是null,这个后面还有用。
    2.找到originPathClassLoader中的pathList 和 pathList中的类型为ClassLoader的definingContext。
    3.替换definingContext为AndroidNClassLoader
    4.将AndroidNClassLoader中的pathList替换为originPathClassLoader的pathList。

    Android 的ClassLoader采用双亲委托模型,只有parent找不到的情况下才会去找AndroidNClassLoader,那我新建这个AndroidNClassLoader有什么用,最终还是会去originPathClassLoader中取找。其实不是这样的,我们已经将originPathClassLoader中pathList中的definingContext(是个ClassLoader)替换为了AndroidNClassLoader了。这个definingContext会在生成DexFile的时候传递进去,而ClassLoader的findClass()方法会调用pathList的findClass方法,如下:

    //DexPathList.java
        public Class findClass(String name) {
            for (Element element : dexElements) {
                DexFile dex = element.dexFile;
                if (dex != null) {
                    Class clazz = dex.loadClassBinaryName(name, definingContext);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }
            return null;
        }
    

    最终还是调用的dexFile.loadClassBinaryName()方法,其中的第二个参数其实就已经是AndroidNClassLoader了

    加载补丁资源

    Tinker的资源更新采用的InstantRun的资源补丁方式,全量替换资源。由于App加载资源是依赖Context.getResources()方法返回的Resources对象,Resources 内部包装了 AssetManager,最终由 AssetManager 从 apk 文件中加载资源。我们要做的就是新建一个AssetManager(),hook掉其中的addAssetPath()方法,将我们的资源补丁目录传递进去,然后循环替换Resources对象中的AssetManager对象,达到资源替换的目的。

    public static void isResourceCanPatch(Context context) throws Throwable {
        // Create a new AssetManager instance and point it to the resources installed under /sdcard
        AssetManager assets = context.getAssets();
        // Baidu os
        if (assets.getClass().getName().equals("android.content.res.BaiduAssetManager")) {
            Class baiduAssetManager = Class.forName("android.content.res.BaiduAssetManager");
            newAssetManager = (AssetManager) baiduAssetManager.getConstructor().newInstance();
        } else {
            newAssetManager = AssetManager.class.getConstructor().newInstance();
        }
        addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        addAssetPathMethod.setAccessible(true);
        // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
        // in L, so we do it unconditionally.
        ensureStringBlocksMethod = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
        ensureStringBlocksMethod.setAccessible(true);
        // Iterate over all known Resources objects
        if (SDK_INT >= KITKAT) {
            //pre-N
            // Find the singleton instance of ResourcesManager
            Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
            Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance");
            mGetInstance.setAccessible(true);
            Object resourcesManager = mGetInstance.invoke(null);
            try {
                Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
                fMActiveResources.setAccessible(true);
                ArrayMap<?, WeakReference<Resources>> arrayMap =
                    (ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
                references = arrayMap.values();
            } catch (NoSuchFieldException ignore) {
                // N moved the resources to mResourceReferences
                Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
                mResourceReferences.setAccessible(true);
                //noinspection unchecked
                references = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
            }
        } else {
            Class<?> activityThread = Class.forName("android.app.ActivityThread");
            Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
            fMActiveResources.setAccessible(true);
            Object thread = getActivityThread(context, activityThread);
            @SuppressWarnings("unchecked")
            HashMap<?, WeakReference<Resources>> map =
                (HashMap<?, WeakReference<Resources>>) fMActiveResources.get(thread);
            references = map.values();
        }
        // check resource
        if (references == null || references.isEmpty()) {
            throw new IllegalStateException("resource references is null or empty");
        }
        try {
            assetsFiled = Resources.class.getDeclaredField("mAssets");
            assetsFiled.setAccessible(true);
        } catch (Throwable ignore) {
            // N moved the mAssets inside an mResourcesImpl field
            resourcesImplFiled = Resources.class.getDeclaredField("mResourcesImpl");
            resourcesImplFiled.setAccessible(true);
        }
    }
    

    按照步骤来吧,首先新建一个AssetManager对象,其中对BaiduROM做了兼容(BaiduAssetManager),拿到其中的addAssetPath方法的反射addAssetPathMethod,然后拿到ensureStringBlocks的反射,然后区分版本拿到Resources的集合。

    SDK >= 19,从ResourcesManager中拿到mActiveResources变量,是个持有Resources的ArrayMap,赋值给references,Android N中该变量叫做mResourceReferences
    SDK < 19,从ActivityThread中获取mActiveResources,是个HashMap持有Resources,赋值给references
    如果references为空,说明该系统不支持资源补丁,throw 一个IllegalStateException被上层调用catch。

    然后调用monkeyPatchExistingResources方法(这个方法的名字跟InstantRun的资源补丁方法名是一样的),将补丁资源路径(res/resources.apk)传递进去,代码就不贴了,简单描述为反射调用新建的AssetManager的addAssetPath将路径穿进去,然后主动调用ensureStringBlocks方法确保资源的字符串索引创建出来;然后循环遍历持有Resources对象的references集合,依次替换其中的AssetManager为新建的AssetManager,最后调用Resources.updateConfiguration将Resources对象的配置信息更新到最新状态,完成整个资源替换的过程。

    **目前来看InstantRun的资源更新方式最简便而且兼容性也最好,市面上大多数的热补丁框架都采用这套方案。Tinker的这套方案虽然也采用全量的替换,但是在下发patch中依然采用差量资源的方式获取差分包,**下发到手机后再合成全量的资源文件,有效的控制了补丁文件的大小。

    加载补丁so

    so的更新方式跟dex和资源都不太一样,因为系统提供给了开发者自定义so目录的选项

    public final class System {
        ...
        public static void load(String pathName) {
            Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
        }
        ...
    }
    

    Tinker加载SO补丁提供了两个入口,分别是TinkerInstaller和TinkerApplicationHelper。他们两个的区别是TinkerInstaller只有在Tinker.install过之后才能使用,否则会抛出异常。

    //TinkerInstaller
        public static boolean loadLibraryFromTinker(Context context, String relativePath, String libname) throws UnsatisfiedLinkError {
            final Tinker tinker = Tinker.with(context);
            libname = libname.startsWith("lib") ? libname : "lib" + libname;
            libname = libname.endsWith(".so") ? libname : libname + ".so";
            String relativeLibPath = relativePath + "/" + libname;
            //TODO we should add cpu abi, and the real path later
            if (tinker.isEnabledForNativeLib() && tinker.isTinkerLoaded()) {
                TinkerLoadResult loadResult = tinker.getTinkerLoadResultIfPresent();
                if (loadResult.libs != null) {
                    for (String name : loadResult.libs.keySet()) {
                        if (name.equals(relativeLibPath)) {
                            String patchLibraryPath = loadResult.libraryDirectory + "/" + name;
                            File library = new File(patchLibraryPath);
                            if (library.exists()) {
                                //whether we check md5 when load
                                boolean verifyMd5 = tinker.isTinkerLoadVerify();
                                if (verifyMd5 && !SharePatchFileUtil.verifyFileMd5(library, loadResult.libs.get(name))) {
                                    tinker.getLoadReporter().onLoadFileMd5Mismatch(library, ShareConstants.TYPE_LIBRARY);
                                } else {
                                    System.load(patchLibraryPath);
                                    TinkerLog.i(TAG, "loadLibraryFromTinker success:" + patchLibraryPath);
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }
    

    简单来说就是遍历检查的结果列表libs,找到要加载的类,调用System.load方法进行加载。

    遇到的问题

    在集成Tinker的过程中,遇到了一个问题(环境是Dalvik,ART没问题),在前面我们提到了dex.loader的配置,我把项目中用于下载补丁文件的工具类A加到了其中,然后下发补丁报错,出现Class ref in pre-verified class resolved to unexpected implementation的crash。Qzone的那套热补丁为了消除这个错误采用插庄的方式来规避,Tinker采用全量dex的方式来规避该问题,那为什么还会出现呢。

    根据log找到了报错点是在工具类A中的一个直接引用类B的方法中报错。错误原因在加载补丁dex一节其实已经提到一些,我们引用过来,这个配置(dex.loader)中的类不会出现在任何全量补丁dex里,也就是说在合成后,这些类还在老的dex文件中,比如在补丁前dex顺序是这样的:oldDex1 -> oldDex2 -> oldDex3…,那么假如修改了dex1中的文件,那么补丁顺序是这样的newDex1 -> oldDex1 -> oldDex2…其中合成后的newDex1中的类是oldDex1中除了dex.loader中标明的类之外的所有类,dex.loader中的类依然在oldDex1中。
    也就是说A类是在dex.loader配置中的,补丁后,A依然在oldDex1中,而A的直接引用类B却出现在了newDex1中,并且在之前A类已经被打上了preverify标志,所在A再去newDex1中加载B的话就会报该错误。

    那有的同学可能会问了,TinkerApplication也在oldDex1中的,而我们的ApplicationLike在补丁后也出现在了newDex1中,TinkerApplication反射调用ApplicationLike的生命周期方法为什么没有出现crash呢?还记得文章前面的有一个反射么,我们说了要注意后面会讲到,就是在这里用到的。

    校验preverify的方法,正常的类加载会走到这里。

    ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx,
        bool fromUnverifiedConstant)
    {
    ....
           if (!fromUnverifiedConstant &&
                IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED))
    ...
    }
    

    **而反射走了完全不同的路径,不会走到dvmResolveClass方法,也就不会报错了。**反射最直接的目的也是为了隔离开这两个类,也就是隔离开了Tinker组件和app。
    在这里插入图片描述

    为啥Dalvik有问题,ART没问题呢?那是因为在ART虚拟机原生支持从APK文件加载多个dex文件。在应用安装时执行dex2oat扫描 classes(…N).dex文件,并将它们编译成单个oat文件,供 Android设备执,也就不存在MultiDex的问题了。

    展开全文
  • Tinker support

    2020-12-26 01:02:29
    - added support for Tinker TXYZ and ARC files <h2>PR Checklist <ul><li>[x] Tests?</li><li>[x] Docs?</li><li>[x] CHANGELOG updated?</li><li>[ ] Issue raised/referenced?</li></ul>该提问来源于开源项目&#...
  • Tinker writer

    2020-12-26 00:40:10
    <ul><li>added Tinker trajectory writer </li></ul> <h2>PR Checklist <ul><li>[ ] Tests?</li><li>[ ] Docs?</li><li>[ ] CHANGELOG updated?</li><li>[ ] Issue raised/referenced?</li></ul>该提问来源于开源...
  • Tinker踩坑

    2019-11-26 15:41:59
    Tinker 接入指南 Tencent/tinker 集成Tinker最主要的两个部分:一个是接入文档,另一个是github上的demo,可以将tinker-sample-android单独下载下来,运行,参考里边的配置。集成中遇到了一些问题,记载一下 集成...

    Tinker 接入指南
    Tencent/tinker

    集成Tinker最主要的两个部分:一个是接入文档,另一个是github上的demo,可以将tinker-sample-android单独下载下来,运行,参考里边的配置。集成中遇到了一些问题,记载一下

    集成步骤

    1.在项目的build.gradle,配置

     dependencies {
            classpath 'com.android.tools.build:gradle:3.2.1'
            classpath ("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}")
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    

    问题1.如果出现下图的Bug
    在这里插入图片描述
    说明sdk中有方法已经过时,需要将gradle 版本调低一些
    2.在app的build.gradle,引入tinker库

    dependencies {
       .......
        //optional, help to generate the final application
        //生成application时使用
        compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {changing=true}
        //tinker's main Android lib
        implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"){changing=true}
    
        implementation "com.android.support:multidex:1.0.3"
    }
    

    在gradle.properties里定义TINKER_VERSION=1.9.1,方便管理

    3.参考demo和文档,对build.gradle进行配置

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "drag.mandala.com.tinkerdemo"
            minSdkVersion 22
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
            javaCompileOptions {
                annotationProcessorOptions {
                    includeCompileClasspath true
                }
            }
        }
    
        signingConfigs {
            release {
                try {
                    storeFile file("test.jks")
                    storePassword "aaaaaa"
                    keyAlias "test1"
                    keyPassword "aaaaaa"
                } catch (ex) {
                    throw new InvalidUserDataException(ex.toString())
                }
            }
    
        }
    
        buildTypes {
            release {
                //打开混淆,要不然不能生产mapping文件
                minifyEnabled true
                //配置signConfig,否则提示can't the get signConfig for this build
                signingConfig signingConfigs.release
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
    
    
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    
        //optional, help to generate the final application
        //生成application时使用
        compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {changing=true}
        //tinker's main Android lib
        implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"){changing=true}
    
        implementation "com.android.support:multidex:1.0.3"
    }
    
    
    def bakPath = file("${buildDir}/bakApk")
    ext{
        tinkerEnable =  true
        //tinkerOldApkPath,tinkerApplyMappingPath,tinkerApplyResourceMappingPath初始值为${bakPath},
        //打包成功以后,将build/bakApk下生成的文件名放到${bakPath}后,修改 minifyEnabled为 true,要不然不会生产mapping文件
        tinkerOldApkPath = "${bakPath}/app-release-1126-14-00-54.apk"
        tinkerId = "1.0"
        tinkerApplyMappingPath = "${bakPath}/app-release-1126-14-00-54-mapping.txt"
        tinkerApplyResourceMappingPath = "${bakPath}/app-release-1126-14-00-54-R.txt"
    }
    
    def buildWithTinker(){
    
        return ext.tinkerEnable
    }
    def getOldApkPath(){
    
        return ext.tinkerOldApkPath
    }
    
    def getApplyMappingPath(){
    
        return ext.tinkerApplyMappingPath
    }
    
    def getApplyResourceMappingPath(){
    
        return ext.tinkerApplyResourceMappingPath
    }
    
    def getTinkerIdValue(){
    
        return ext.tinkerId
    }
    
    if(buildWithTinker()){
        //启用了tinker
        apply plugin: 'com.tencent.tinker.patch'
    
        //所有tinker相关的参数配置
        tinkerPatch{
    
            oldApk = getOldApkPath()//指定old apk文件路径
    
            ignoreWarning = false //不忽略tinker的警告,有警告就停止生成patch文件
    
            useSign = true //强制patch文件使用签名
    
            tinkerEnable = buildWithTinker() //指定是否启用tinker
    
            buildConfig{
                applyMapping = getApplyMappingPath() //指定old apk打包时所使用的混淆文件
    
                applyResourceMapping = getApplyResourceMappingPath() // 指定old apk的资源文件
    
                tinkerId = getTinkerIdValue() // 指定TinkerId
    
                keepDexApply = false
    
                isProtectedApp = false
    
                supportHotplugComponent = false
            }
    
            dex{
                dexMode = "jar" //只能是'raw'或者'jar'
    
                pattern = ["classes*.dex",
                           "assets/secondary-dex-?.jar"] // 指定dex文件目录,第二个是官方例子配置
    
                loader = ["drag.mandala.com.tinkerdemo.MyTinkerApplication"] //指定加载patch文件时用到的类
    
            }
    
            lib{
    
                pattern = ["lib/*/*.so"]
            }
    
            res{
    
                pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] //指定tinker可以修改的所有资源文件路径
    
               // ignoreChange = ["assets/sample.txt"] //在编译时会忽略该文件的新增、删除与修改
                ignoreChange = ["assets/sample_meta.txt"]
                largeModSize = 100 //资源修改的默认值  如果大于largeModSize,tinker将使用bsdiff算法。
                // 这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
            }
    
            //不必须的配置
            //说明配置信息
            packageConfig{
    
                configField("patchMessage", "tinker is sample to use")
    
                configField("platform", "all")
    
                configField("patchVersion", "1.0")
            }
    
            /**
             * if you don't use zipArtifact or path, we just use 7za to try
             */
            sevenZip {
                /**
                 * optional,default '7za'
                 * the 7zip artifact path, it will use the right 7za with your platform
                 */
                zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
                /**
                 * optional,default '7za'
                 * you can specify the 7za path yourself, it will overwrite the zipArtifact value
                 */
    //        path = "/usr/local/bin/7za"
            }
        }
    
        List<String> flavors = new ArrayList<>();
        project.android.productFlavors.each { flavor ->
            flavors.add(flavor.name)
        }
        //是否配置多渠道
        boolean hasFlavors = flavors.size() > 0
    
    
        /**
         * bak apk and mapping
         */
        android.applicationVariants.all { variant ->
            /**
             * task type, you want to bak
             */
            def taskName = variant.name
            def date = new Date().format("MMdd-HH-mm-ss")
            tasks.all {
                if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
    
                    it.doLast {
                        copy {
                            def fileNamePrefix = "${project.name}-${variant.baseName}"
                            def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
    
                            def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
    
                            if (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {
                                def packageAndroidArtifact = variant.packageApplicationProvider.get()
                                if (packageAndroidArtifact != null) {
                                    try {
                                        from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)
                                    } catch (Exception e) {
                                        from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)
                                    }
                                } else {
                                    from variant.outputs.first().mainOutputFile.outputFile
                                }
                            } else {
                                from variant.outputs.first().outputFile
                            }
    
                            into destPath
                            rename { String fileName ->
                                fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                            }
    
                            from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                            into destPath
                            rename { String fileName ->
                                fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                            }
    
                            from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                            from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"
                            into destPath
                            rename { String fileName ->
                                fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                            }
                        }
                    }
                }
            }
        }
    }
    

    问题2: 如果出现下图的提示
    在这里插入图片描述
    需要配置:
    在这里插入图片描述
    **问题3:**出现下图的Bug
    在这里插入图片描述
    在gradle.properties配置android.enableAapt2=true

    问题4: 不能生成mapping文件,这个需要打开混淆

     minifyEnabled true
    

    如果提示“can’t the get signConfig for this build”,需要配置signConfig

     signingConfig signingConfigs.release
    

    完整配置

    
        buildTypes {
            release {
                //打开混淆,要不然不能生产mapping文件
                minifyEnabled true
                //配置signConfig,否则提示can't the get signConfig for this build
                signingConfig signingConfigs.release
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    

    4.写一个继承自DefaultApplicationLike的类

    @DefaultLifeCycle(application = ".MyTinkerApplication",
    flags = ShareConstants.TINKER_ENABLE_ALL,
    loadVerifyFlag = false)
    public class CustomTinkerLike extends DefaultApplicationLike
    {
    
        public CustomTinkerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent)
        {
            super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
        }
    
        @Override
        public void onBaseContextAttached(Context base)
        {
            super.onBaseContextAttached(base);
            //you must install multiDex whatever tinker is installed!
            MultiDex.install(base);
    
            TinkerManager.setTinkerApplicationLike(this);
    
            TinkerManager.initFastCrashProtect();
            //should set before tinker is installed
            TinkerManager.setUpgradeRetryEnable(true);
    
    
            //installTinker after load multiDex
            //or you can put com.tencent.tinker.** to main dex
            TinkerManager.installTinker(this);
            Tinker tinker = Tinker.with(getApplication());
        }
    
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
            getApplication().registerActivityLifecycleCallbacks(callback);
        }
    }
    
    

    build一下,AndroidManifest.xml里的<application 的name设置成注解里的application,

    <application
                android:allowBackup="true"
                android:icon="@mipmap/ic_launcher"
                android:label="@string/app_name"
                android:roundIcon="@mipmap/ic_launcher_round"
                android:supportsRtl="true"
                android:theme="@style/AppTheme"
                android:name=".MyTinkerApplication">
                ...
    

    配置权限,否则onReceiveUpgradePatch的时候会闪退

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    

    5.其他的类可以复制demo里的,别忘了在AndroidManifest.xml配置SampleResultService

    6.配置完成以后,先打包
    在这里插入图片描述

    打包成功以后,查看build文件夹,
    在这里插入图片描述
    将这三个文件名赋值到build.gradle中的对应位置
    在这里插入图片描述

    将app-release-1126-14-00-54.apk安装到手机上,作为一个有Bug的app,修改bug,生成patch文件
    在这里插入图片描述

    生成成功以后,继续看build文件夹
    在这里插入图片描述

    这个放到手机对应的目录下,目录地址是onReceiveUpgradePatch方法里的

    TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
    

    点击按钮加载patch,杀死进程,重启,就会是修改后的app

    问题5: 如果报com.tencent.tinker.build.util.TinkerPatchException: resource must contain resources.arsc pattern,检查build.gradle里的res下的pattern中的"resources.arsc"是否写错。还有就是通过AndroidStudio方式生成差异包的时候如果不小心修改了XML也会报这个错

    问题6: onLoadPatchListenerReceiveFail: patch receive fail: /storage/emulated/0/patch_signed_7zip.apk, code: -2
    检查文件路径是否和代码里的一致,查看清单文件中是否有添加SD卡访问权限,如果是Android7.0要考虑FileProvider(Android7.0不支持直接访问sd卡)

    问题7: Tinker does not support instant run mode, please trigger build by assembleDebug or disable instant run
    去掉instance run

    展开全文
  • tinker热修复

    2016-12-12 15:21:38
    tinker热修复
  • import tinker from 'tinker.macro' let isDebug = tinker `config('app.debug')` // ↓ ↓ ↓ ↓ ↓ ↓ ↓ let isDebug = true 如果您正在执行单个函数调用,则可以这样导入函数: import { config } from '...
  • Tinker使用

    2017-05-14 18:29:54
    Tinker使用

    Tinker使用

    Tinker简介

    Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。

    tinker官方介绍

    使用步骤

    1.在项目的build.gradle文件中,添加以下代码:

    dependencies {
        classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
    }
    

    在gradle.properties文件中配置TINKER_VERSION:

    TINKER_VERSION = 1.7.9
    

    2.在app的build.gradle文件中,添加以下代码:

    dependencies {
        provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}")
        compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}")
        compile('com.android.support:multidex:1.0.1')
    }
    
    def gitSha() {
        try {
            String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
            if (gitRev == null) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
        }
        return gitRev
        } catch (Exception e) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
        }
    }
    
    def bakPath = file("${buildDir}/bakApk/")
    
    ext {
        //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
        tinkerEnabled = true
    
        //for normal build
        //old apk file to build patch apk
        tinkerOldApkPath = "${bakPath}/app-debug-1018-17-32-47.apk"
        //proguard mapping file to build patch apk
        tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
        //resource R.txt to build patch apk, must input if there is resource changed
        tinkerApplyResourcePath = "${bakPath}/app-debug-1018-17-32-47-R.txt"
    
        //only use for build all flavor, if not, just ignore this field
        tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
    }
    
    
    def getOldApkPath() {
        return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
    }
    
    def getApplyMappingPath() {
        return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
    }
    
    def getApplyResourceMappingPath() {
        return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
    }
    
    def getTinkerIdValue() {
        return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
    }
    
    def buildWithTinker() {
        return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
    }
    
    def getTinkerBuildFlavorDirectory() {
        return ext.tinkerBuildFlavorDirectory
    }
    
    if (buildWithTinker()) {
        apply plugin: 'com.tencent.tinker.patch'
    
        tinkerPatch {
    
            oldApk = getOldApkPath()
    
            ignoreWarning = true
    
            useSign = true
    
            tinkerEnable = buildWithTinker()
    
            buildConfig {
    
            applyMapping = getApplyMappingPath()
    
            applyResourceMapping = getApplyResourceMappingPath()
    
            tinkerId = getTinkerIdValue()
    
            keepDexApply = false
    
            isProtectedApp = false
        }
    
        dex {
    
            dexMode = "jar"
    
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
    
            loader = [
                    //use sample, let BaseBuildInfo unchangeable with tinker
                    "tinker.sample.android.app.BaseBuildInfo"
            ]
        }
    
        lib {
    
            pattern = ["lib/*/*.so"]
        }
    
        res {
    
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
    
            ignoreChange = ["assets/sample_meta.txt"]
    
            largeModSize = 100
        }
    
        packageConfig {
    
            configField("patchMessage", "tinker is sample to use")
    
            configField("platform", "all")
    
            configField("patchVersion", "1.0")
        }
        //or you can add config filed outside, or get meta value from old apk
        //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
        //project.tinkerPatch.packageConfig.configField("test2", "sample")
    
    
        sevenZip {
    
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    
    //        path = "/usr/local/bin/7za"
        }
    }
    
    List<String> flavors = new ArrayList<>();
    project.android.productFlavors.each {flavor ->
        flavors.add(flavor.name)
    }
    boolean hasFlavors = flavors.size() > 0
    def date = new Date().format("MMdd-HH-mm-ss")
    
    
    android.applicationVariants.all { variant ->
    
        def taskName = variant.name
    
        tasks.all {
            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
    
                it.doLast {
                    copy {
                        def fileNamePrefix = "${project.name}-${variant.baseName}"
                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
    
                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
                        from variant.outputs.outputFile
                        into destPath
                        rename { String fileName ->
                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                        }
    
                        from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                        }
    
                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                        }
                    }
                }
            }
        }
    }
    project.afterEvaluate {
        //sample use for build all flavor for one time
        if (hasFlavors) {
            task(tinkerPatchAllFlavorRelease) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
    
                    }
    
                }
            }
    
            task(tinkerPatchAllFlavorDebug) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
                    }
    
                }
            }
        }
    }
    }
    

    注意:

    - defaultConfig中配置multiDexEnabled true
    - ignoreWarning = true而不是false
    - tinkerId赋一个值或者使用versionName
    

    3.创建MyApplicationLinke,代码如下:

    @SuppressWarnings("unused")
    @DefaultLifeCycle(application = "com.example.tinkerdemo.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
    public class MyApplicationLinker extends DefaultApplicationLike {
    
        public MyApplicationLinker(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                 long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
        }
    
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        @Override
        public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        //you must install multiDex whatever tinker is installed!
        MultiDex.install(base);
    
        //installTinker after load multiDex
        //or you can put com.tencent.tinker.** to main dex
        TinkerInstaller.install(this);
        //        Tinker tinker = Tinker.with(getApplication());
        }
    }
    

    在类的注解中有application = “com.example.tinkerdemo.MyApplication”,意思是帮你生成Application,注意包名。生成的MyApplication的位置如图:

    4.修复部分的代码:

    public class MainActivity extends AppCompatActivity {
    
    private TextView mTextView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text);
        mTextView.setText("这是有问题的APK");
        //点击TextView,开始修复
        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //指定补丁包位置,加载补丁包信息;test为补丁包名称,可以定制
                TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
                        Environment.getExternalStorageDirectory().getAbsolutePath() + "/test");
            }
        });
    }
    }
    

    5.运行打包后,生成如图所示的文件:

    6.修改代码,修改app中build.gradle中老版本的信息,即将上图中bakApk中的apk的名称app-debug-0510-15-43-08复制给build.gradle中的tinkerOldApkPath = “${bakPath}/app-debug-0510-15-43-08.apk”

    7.使用命令生成新的apk和差异包

    在app的目录下,运行gradle tinkerPatchdebug命令。
    在app/build/output/tinkerPath/目录下生成差异包,如图所示:

    移动端通过后台下载差异包,然后在后台服务中修复。

    tinker原理

    展开全文
  • awesome-asus-tinker-board:华硕Tinker Board资源精选列表
  • Android Tinker

    2019-08-23 10:23:52
    Tinker 优势:基于classloader的classlaoder开发了自己的classlaoder,基于原声的aapt开发了自己的aapt.基于自己的dex文件格式,研发了dexdiff算法 使用流程:集成sdk→配置gradle→初始化tinker(applicaiton)→...
  • buglydemo&tinker;

    2017-11-21 09:26:53
    buglydemo&tinker; buglydemo&tinker; buglydemo&tinker; buglydemo&tinker;

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,523
精华内容 1,809
关键字:

tinker