Android 热修复,微信开源框架tinker的接入,就是这么简单

最近在弄热更新,看了几个开源框架,感觉微信的tinker不错,学习一下,但是发现官方文档给的实在蛋疼,全是坑,做个笔记记录一下,以后用的时候避过这些坑。

微信demo

下载下来以后有很多,直接把tinker-sample-android拿出来就好了,看这个就行了。

这个是官网给的接入指南,看下里边gradle的介绍

1.导入demo的问题,导入后你有可能遇见这个

tinkerId is not set!!!

找到你的moudle的build.gradle,在那里边有这个` tinkerId = “1.0”的配置我这直接给写死了

注意:这个你自己导入的时候最好是写你的版本号,因为这个是来判断到底你的修复的差别文件在那个上边使用,如果你像我一样写死你会发现你在更新包的时候,你的apk更新不了

2.运行demo在这块会生成你的apk包
apk
3.然后你随意修改下代码
在你的gradle文件中修改tinkerOldApkPath
这里写图片描述

这个要和你上边生成的包名字保持一致

3.点击生成差别文件,debug和release看你是正式的还是测试的
这里写图片描述
4.等待运行完毕就会生成这个差别文件,没有生成差别文件的话你点击生成的log看看有什么错误,我运行的就有一个这个错误,

Warning:ignoreWarning is false, but resources.arsc is changed

这个的话你修改下build里边的这个就好了,改成true

   ignoreWarning = false

具体错误自己看下,很好理解。
这里写图片描述
5.生成差别包以后,demo里边会有一个loadpatch的按钮,把你的差别包拷贝进这个路径下,这是德莫的路径自己你可以自己定

Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk"

装你原来的应用然后点击加载按钮,然后点击kill按钮杀死应用重新进入就是你改过的了。

接入常见问题

tinkerAPI

******************************以上是导入demo********************************
集成进入项目
1.新建一个项目,找到你的project的gradle,引入这个,后边那个tinker_version是我在gradle.properties这个里边配置的版本,你也可以直接写,demo也有配置我就不在贴图了。
这里写图片描述
2.
(1.)找到model的gradle,进行配置,只贴一些必要的不必要的就不再贴了,添加依赖
这里写图片描述
(2.)apk路径你可以自己定,

//在build中创建一个bakApk文件夹
def bakPath = file("${buildDir}/bakApk/")

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true

    //旧版本apk路径配置
    tinkerOldApkPath = "${bakPath}/app-debug-0118-16-57-13.apk"
    //用于混淆,没有混淆可以不管
    tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
    //旧版本apk R文件
    tinkerApplyResourcePath = "${bakPath}/app-debug-0118-16-43-45-R.txt"

    //only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
//全局信息相关的配置项
tinkerPatch {
//    基准apk包的路径,必须输入,否则会报错。
    oldApk = tinkerOldApkPath
//    在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
    useSign = true
    ignoreWarning = true
//    编译相关的配置项
    buildConfig {
//        在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId.
// 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等
        tinkerId = "1.0"
    }
    packageConfig {
        configField("patchMessage", "tinker is sample to use")
        configField("platform", "all")
        configField("patchVersion", "1.0")
    }
//    dex相关的配置项
    dex {
/*只能是'raw'或者'jar'。
对于'raw'模式,我们将会保持输入dex的格式。
对于'jar'模式,我们将会把输入dex重新压缩封装到jar。
如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,
但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。*/
        dexMode = "jar"
//        需要处理dex路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/..
        pattern = ["classes*.dex",
                   "assets/secondary-dex-?.jar"]
/*这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。
这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
这里需要定义的类有:
1. 你自己定义的Application类;
2. Tinker库中用于加载补丁包的部分类,即com.tencent.tinker.loader.*;
3. 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
4. 其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。
这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify。
5. 使用1.7.6版本之后版本,参数12会自动填写。*/
        loader = [
                "tinker.sample.android.app.BaseBuildInfo"
        ]
    }
//    lib相关的配置项
    lib {
        pattern = ["lib/armeabi/*.so", "lib/arm64-v8a/*.so", "lib/armeabi-v7a/*.so", "lib/mips/*.so", "lib/mips64/*.so", "lib/x86/*.so", "lib/x86_64/*.so"]
    }
//    res相关的配置项
    res {
        pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        largeModSize = 100
        ignoreChange = ["assets/sample_meta.txt"]
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    }
}

List<String> flavors = new ArrayList<>();
project.android.productFlavors.each {flavor ->
    flavors.add(flavor.name)
}
boolean hasFlavors = flavors.size() > 0

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
                    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 = tinkerBuildFlavorDirectory
            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 = tinkerBuildFlavorDirectory
            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"
                }

            }
        }
    }
}

3.在代码中的使用,tinker定了一个自己的DefaultApplicationLike
我们的application得继承这个

@DefaultLifeCycle(
        application = "com.tinker.tinker.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL
)
public class ApplicationLike extends DefaultApplicationLike {
    public ApplicationLike(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);
        TinkerInstaller.install(this);
    }
}

在然后调用下这个代码

 TinkerInstaller.onReceiveUpgradePatch(this.getApplication(),"你的差别文件的路径");

其他的生成差别文件这些和demo一样
最后在附上一个我写的一个下载差别文件的测试代码

url = "你的下载路径";

        String s = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AAAAAA" + File.separator;
        File file = new File(s);
        if (!file.exists()) {
            file.mkdirs();
        }
        patch = new File(s + "patch.apk");
        if (!patch.exists()) {
            try {
                patch.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } 
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    URL url1 = new URL(url);
                    HttpURLConnection connection = (HttpURLConnection) url1.openConnection();
                    if (connection.getResponseCode() == 200) {
                        InputStream inputStream = connection.getInputStream();
                        byte[] bytes = new byte[1024];
                        int len;
                        connection.connect();
                        FileOutputStream outputStream = new FileOutputStream(patch);
                        while ((len = inputStream.read(bytes)) != -1) {
                            outputStream.write(bytes, 0, len);
                        }
                        inputStream.close();
                        outputStream.close();
                        Log.e("下载", "下载---》patch成功"); 
                    } else {
                        Log.e("下载", "下载---》patch失败");
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

最后附上一个通过修改class方式的 RocooFix热修复

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值