Tinker热修复集成

Tencent Tinker:移动应用热修复的未来之路 Tencent Tinker是腾讯公司开发的一款移动应用热修复框架,旨在帮助开发者快速修复线上应用的bug、漏洞和功能问题。它提供了一套强大的工具和机制,使得开发者能够在不影响用户体验的情况下进行实时修复和功能更新。Tencent Tinker的出现解决了传统应用修复需要重新发布整个应用的痛点,有效提升了开发效率和用户体验。 阅读详情

官方链接

GitHub · Tencent/tinker
文档 - Shiply 腾讯平台解决方案

开发集成环境

  1. Android Studio Ladybug Feature Drop | 2024.2.2 Patch 1
  2. Gradle Version:7.4
  3. AGP Version 7.2.2
  4. Tinker:1.9.15.1
  5. compileSdk:33,targetSdk:31,minSdk:23

截至v1.9.15.1版本适配情况:

gradle 7.4 及以下支持360加固和非加固apk。

gradle 7.4 以上只支持非加固apk

依赖引入

  1. 工程build.gradle
dependencies {
        classpath 'com.android.tools.build:gradle:7.2.2'
        classpath 'com.tencent.tinker:tinker-patch-gradle-plugin:1.9.15.1'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

1.插件版本为1.9.15.1,最新版本可以查看链接:插件链接

2.代码块添加在“plugins {}”代码块之前,不然会提示错误信息“all buildscript {} blocks must appear before any plugins {} blocks in the script”

  1. APP build.gradle.kts
    implementation("com.android.tools.smali:smali-dexlib2:3.0.9")
    //可选,用于生成application类
    annotationProcessor("com.tencent.tinker:tinker-android-anno:1.9.15.1") { isChanging = true}
    compileOnly("com.tencent.tinker:tinker-android-anno:1.9.15.1") { isChanging = true}
    //tinker的核心库
    implementation("com.tencent.tinker:tinker-android-lib:1.9.15.1") { isChanging = true}
    // Maven local cannot handle transist dependencies.
    implementation("com.tencent.tinker:tinker-android-loader:1.9.15.1") { isChanging = true}
  1. 引入插件
plugins {
    id 'com.tencent.tinker.patch'
}

  1. 配置版本
项目的gradle.properties文件添加如下属性:

TINKER_ID = 1.0 

tinkerId一般为每次发版的版本号,补丁包是根据tinkerId匹配基准包的。之后会在app的build.gradle.kts当中引用该属性。

  1. 混淆文件配置

将官方demo中的文件【tinker_multidexkeep.pro】拷贝到自己项目的app目录下。

PS:该文件内有一处需要修改成自己内容的地方,需要将该文件内替换为自己的application。如下:

  1. 在app下的build.gradle添加如下内容
defaultConfig {
        multiDexEnabled = true
        multiDexKeepProguard = file("tinker_multidexkeep.pro")
        
        buildConfigField("String", "MESSAGE", "\"I am the base apk\"")
        buildConfigField( "String", "TINKER_ID", "\"1.0.0\"")
        buildConfigField( "String", "PLATFORM", "\"all\"")
    }
    
// tinker支持大工程文件配置
dexOptions {
    jumboMode = true
}

打包配置

在官方demo中,将打包的配置都写在了app下的build.gradle当中,这里可以单独创建tinker.gradle文件。并在app下的build.gradle中引入:

// 引入自动备份打包资源脚本
apply("tinker-auto-backup.gradle")
// 引入tinker配置
apply("tinker.gradle")

tinker.gradle:

官方demo当中的gradle语法属性有些已经废弃了,具体参考如下

def bakPath = file("${projectDir.absolutePath}/tinker/")

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //for normal build
    //打补丁包时候这里配置基准包的全称
    tinkerOldApkPath = "${bakPath}/old.apk"
    tinkerNewApkPath = "${bakPath}/new.apk"
    //打Release补丁包时候这里配置基准包时候生成的mapping文件proguard mapping file to build //patch apk
    tinkerApplyMappingPath = "${bakPath}/mapping.txt"
    //打补丁包时候这里配置基准包时候生成的文件全称resource R.txt to build patch apk, must input //if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/R.txt"
}

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 getOldApkPath() {
    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}

def getNewApkPath() {
    return hasProperty("NEW_APK") ? NEW_APK : ext.tinkerNewApkPath
}

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") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}

if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'

    tinkerPatch {
        oldApk = getOldApkPath()
        newApk = getNewApkPath()

        outputFolder = "${bakPath}"

        ignoreWarning = false
        allowLoaderInAnyDex = true
        removeLoaderForAllDex = true
        useSign = true
        tinkerEnable = buildWithTinker()

        buildConfig {
            applyMapping = getApplyMappingPath()
            applyResourceMapping = getApplyResourceMappingPath()
            tinkerId = getTinkerIdValue()
            keepDexApply = false

            // 应用是否开启了加固
            isProtectedApp = false
            supportHotplugComponent = false
        }

        dex {
            /**
             * optional,default 'jar'
             * only can be 'raw' or 'jar'. for raw, we would keep its original format
             * for jar, we would repack dexes with zip format.
             * if you want to support below 14, you must use jar
             * or you want to save rom or check quicker, you can use raw mode also
             */
            dexMode = "jar"

            /**
             * necessary,default '[]'
             * what dexes in apk are expected to deal with tinkerPatch
             * it support * or ? pattern.
             */
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            /**
             * necessary,default '[]'
             * Warning, it is very very important, loader classes can't change with patch.
             * thus, they will be removed from patch dexes.
             * you must put the following class into main dex.
             * Simply, you should add your own application {@code tinker.sample.android.SampleApplication}
             * own tinkerLoader, and the classes you use in them
             *
             */
            loader = [
                    //use sample, let BaseBuildInfo unchangeable with tinker
                    "tinker.sample.android.app.BaseBuildInfo"
            ]
        }

        lib {
            /**
             * optional,default '[]'
             * what library in apk are expected to deal with tinkerPatch
             * it support * or ? pattern.
             * for library in assets, we would just recover them in the patch directory
             * you can get them in TinkerLoadResult with Tinker
             */
            pattern = ["lib/*/*.so"]
        }

        res {
            /**
             * optional,default '[]'
             * what resource in apk are expected to deal with tinkerPatch
             * it support * or ? pattern.
             * you must include all your resources in apk here,
             * otherwise, they won't repack in the new apk resources.
             */
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]

            /**
             * optional,default '[]'
             * the resource file exclude patterns, ignore add, delete or modify resource change
             * it support * or ? pattern.
             * Warning, we can only use for files no relative with resources.arsc
             */
            ignoreChange = ["assets/sample_meta.txt"]

            /**
             * default 100kb
             * for modify resource, if it is larger than 'largeModSize'
             * we would like to use bsdiff algorithm to reduce patch file size
             */
            largeModSize = 100
        }

        packageConfig {
            /**
             * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE'
             * package meta file gen. path is assets/package_meta.txt in patch file
             * you can use securityCheck.getPackageProperties() in your ownPackageCheck method
             * or TinkerLoadResult.getPackageConfigByName
             * we will get the TINKER_ID from the old apk manifest for you automatic,
             * other config files (such as patchMessage below)is not necessary
             */
            configField("patchMessage", "tinker is sample to use")
            /**
             * just a sample case, you can use such as sdkVersion, brand, channel...
             * you can parse it in the SamplePatchListener.
             * Then you can use patch conditional!
             */
            configField("platform", "all")
            /**
             * patch version via packageConfig
             */
            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")

        /**
         * 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"
            /**
             * 设置path之后会覆盖zipArtifact的配置,使用本地
             * optional,default '7za'
             * you can specify the 7za path yourself, it will overwrite the zipArtifact value
             */
//            path = "/Users/pary/.gradle/caches/modules-2/files-2.1/com.tencent.mm/SevenZip/1.2.17"
        }
    }

    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")

    /**
     * bak apk and mapping
     */
    android.applicationVariants.all { variant ->
        /**
         * task type, you want to bak
         */
        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

                        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().variantOutput.outputFileName.get())
                                } catch (Exception e) {
                                    from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().variantOutput.outputFileName.get())
                                }
                            } else {
                                from variant.outputs.first().mainOutputFile.outputFile
                            }
                        } else {
                            from variant.outputs.first().outputFile
                        }

                        into destPath
                        rename { String fileName ->
                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                        }

                        def dirName = variant.dirName
                        if (hasFlavors) {
                            dirName = taskName
                        }
                        from "${buildDir}/outputs/mapping/${dirName}/mapping.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                        }

                        from "${buildDir}/intermediates/symbols/${dirName}/R.txt"
                        from "${buildDir}/intermediates/symbol_list/${dirName}/R.txt"
                        from "${buildDir}/intermediates/runtime_symbol_list/${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"
                    }

                }
            }
        }
    }
}

相比于官方Demo,修改了三处内容,因为不修改的话,打包会报错,修改的具体内容如下:

  • 在tinkerPatch{}中增加如下两个属性配置,不添加的话会有报错信息:Warning:ignoreWarning is false,but we found loader classes are found in old secondary dex。
  1. allowLoaderInAnyDex =true
  2. removeLoaderForAllDex =true
  • it.doLast{}当中修改了官方demo中的属性:
  1. //官方demo报错的属性方法:
  2. variant.outputs.first().apkData.outputFileName
  3. //可以正常使用的方法:
  4. variant.outputs.first().variantOutput.outputFileName.get()
  • sevenZip{}中配置的在打补丁包时候使用的SevenZip配置 :
  1. //修改sevenZip版本:
  2. zipArtifact =“com.tencent.mm:SevenZip:1.2.17”
  3. 最新版本查询:https://repo.maven.apache.org/maven2/com/tencent/mm/SevenZip/

tinker-auto-backup.gradle

// 是否有Flavors
def hasFlavors = project.android.productFlavors.size() > 0

// 自动备份的路径
def autoBackPath = file("${projectDir}/autoBackup/")

// 是否使用自动备份
def autoBackEnable = true
if (!autoBackEnable) {
    return
}

/**
 * 自动备份编译生成的APK和Mapping文件
 */
android.applicationVariants.all { variant ->
    def variantName = variant.name
    def capitalizedVariantName = variantName.capitalize()

    def assembleTask = tasks.findByName("assemble${capitalizedVariantName}")
    if (assembleTask != null) {
        assembleTask.doLast {
            copy {
                def date = new Date().format("MMddHHmmss")
                def fileNamePrefix = "${project.name}-${variant.baseName}-${date}"
                def destPath = autoBackPath

                // 拷贝Apk
                variant.outputs.forEach { output ->
                    from output.outputFile
                    into destPath
                    rename { String fileName ->
                        def outputName = output.dirName
                        if (outputName.endsWith("/")) {
                            outputName = outputName.substring(0, outputName.length() - 1)
                        }

                        if (!outputName.isEmpty()) {
                            def oldFileName = "${project.name}-${outputName}-${variant.baseName}"
                            def newFileName = "${fileNamePrefix}-${outputName}"
                            fileName.replace(oldFileName, newFileName)
                        } else {
                            def oldFileName = "${project.name}-${variant.baseName}"
                            def newFileName = "${fileNamePrefix}"
                            fileName.replace(oldFileName, newFileName)
                        }
                    }
                }

                from variant.outputs.first().outputFile
                into destPath
                rename { String fileName ->
                    def oldFileName = "${project.name}-${variant.baseName}"
                    fileName.replace("${oldFileName}.apk", "${fileNamePrefix}.apk")
                }

                def dirName = variant.dirName
                if (hasFlavors) {
                    dirName = variantName
                }

                // 拷贝mapping.txt
                from "${buildDir}/outputs/mapping/${dirName}/mapping.txt"
                into destPath
                rename { String fileName ->
                    fileName.replace("mapping.txt", "${fileNamePrefix}-mapping.txt")
                }

                // 拷贝R.txt
//                from "${buildDir}/intermediates/symbols/${dirName}/R.txt"
//                from "${buildDir}/intermediates/symbol_list/${dirName}/R.txt"
                from "${buildDir}/intermediates/runtime_symbol_list/${dirName}/processReleaseResources/R.txt"
                into destPath
                rename { String fileName ->
                    fileName.replace("R.txt", "${fileNamePrefix}-R.txt")
                }

                // 拷贝methodIds.txt
                from "${buildDir}/redirect/methodIds.txt"
                into destPath
                rename { String fileName ->
                    fileName.replace("methodIds.txt", "${fileNamePrefix}-methodIds.txt")
                }
            }
        }
    }
}

注意:这里自动备份R.txt文件不同版本会有区别:

7.4+ 版本使用:
// 拷贝R.txt
from "${buildDir}/intermediates/runtime_symbol_list/${dirName}/processReleaseResources/R.txt"

6.5- 版本使用
// 拷贝R.txt
from "${buildDir}/intermediates/symbols/${dirName}/R.txt"
from "${buildDir}/intermediates/symbol_list/${dirName}/R.txt"
from "${buildDir}/intermediates/runtime_symbol_list/${dirName}/R.txt"

代码接入

(1)从官方demo中找到如下文件拷贝到自己的项目:

(2)AndroidManifest中添加下内容

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

<service
            android:name=".SampleResultService"
            android:exported="false"
            android:permission="android.permission.BIND_JOB_SERVICE" />

(3)自定义Application代理类

  1. 使用「继承TinkerApplication + DefaultApplicationLike」。(自定义方式)
  2. 使用「DefaultLifeCycle注解 + DefaultApplicationLike」。(注解方式)
class MyApplication(
    tinkerFlags: Int = ShareConstants.TINKER_ENABLE_ALL,
    delegateClassName: String = SampleApplicationLike::class.java.name
) : TinkerApplication(
    tinkerFlags,
    delegateClassName
)

(4)修改SampleApplicationLike

ps:这里如果使用自定义方式则去掉@DefaultLifeCycle注解。使用注解则不需要定义MyApplication类,注解自动生成。

原先项目初始化中的内容需要放到SampleApplicationLike的onCreate方法中。

打包流程

打旧包 -> 打新包 -> 打修复补丁

1、配置打包信息

在app/tinker目录生成打包产物,app/autoBackUp目录生成备份产物

2、打old包(也就是发布后需要修复的正式包)

如下tinker目录会生成old包,根据配置改名old.apk,autoBackUp会备份old包和mapping文件、R.txt文件。

![](https://i-blog.csdnimg.cn/img_convert/0aeea7e36fa585561c158b16b008b0b9.png

3、打new包(修复了old包问题的包
重复第2步打old包流程,生成新包,改名为new.apk
4、打补丁包

打补丁包之前,tinker目录文件红框标识

运行脚本任务:tinkerPatchRelease

最后会在tinker目录下生成/tinkerPatch/补丁包.apk。

最后将补丁包放到业务代码中编写的加载的目录下。

如果成功,应用上的tinker sdk也会toast提示重启应用进程来运行最新的应用,如下会打印成功日志:

5、加固包的补丁生成流程
  1. tinker.gradle 文件中 ****isProtectedApp 配置要设置为true
  2. 按照上面第一步生成old.apk包,后面生成补丁要用。
  3. 将old.apk进行加固,正常上架发布,用户使用的是加固后的old-jiagu.apk
  4. 这个时候用户使用的是old-jiagu.apk;如果发现了bug,需要进行修复。首先拉取相应分支编译出未加固的old.apk,然后修复相应的bug编译生成未加固的new.apk,这里注意old.apk和new.apk的版本号要一致,热修复只能同版本修复,无法跨版本。
  5. 使用未加固的old.apk和未加固的new.apk打包生成补丁文件patch.apk
  6. 测试补丁是否正常,通过测试后发布补丁文件即可。

总的来说,生成补丁都是使用的未加固的包来生成;使用补丁则是在加固后的包上使用。

补丁管理:

因为补丁包需要基于旧包来打,所以保存打旧包时的资源文件比较重要,也可以拉取相应旧包分支来重新打。涉及文件:old.apk、mapping.txt 和 R.txt 三个文件。

在一个app版本内可以包含多个补丁文件,但是新补丁必须要包含旧补丁已修复的代码;这样每次下发时直接下发最新的补丁包即可。

关于补丁包的安全校验,这里可以根据tinkerId和md5双重校验的方式来进行。

问题记录

SevenZip:生成补丁包时提示 Could not resolve all files for configuration ‘:app:sevenZipToolsLocator’
原因是在tinker.gradle配置文件中有一行**zipArtifact = "com.tencent.mm:SevenZip:1.1.10",**配置压缩依赖,有时候服务器可能无法下载。这里可以手动下载,改成本地目录。
  1. brew install p7zip 本地安装7zip

  2. 获取本地安装的7zip路径 which 7za

  3. 把zipArtifact配置注释掉,path更改为本机安装的7za路径

        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
             */
            /*这里改成本地安装的7za路径*/
            path = "/opt/homebrew/bin/7za"
        }

具体原因可以查看:7z包找不到 · Issue #1718 · Tencent/tinker

Mapping和R文件找不到问题
目前发现6.5版本和7.4版本R文件位置目录有变化,这里需要根据自身项目实际修改路径。

Mapping文件一般如下:

\app\build\outputs\mapping\release\mapping.txt

R.txt文件位置如下:

\app\build\intermediates\runtime_symbol_list\release\processReleaseResources\R.txt

Flutter项目使用Bugly+Tinker实现代码热修复 阅读详情

相关推荐

Android开发:热修复 Tinker 原理分析,Android多态实现原理

Override@Override//安装分包配置分包就创建了一个做模拟异常和修复异常的入口,和一个Calculate模拟异常,做了10/0的操作,修复后为10/1注:获取修复后的文件可以通过直接buildapk直接解压获取,或者用build-tools下的dx.bat执行命令获取简单贴下,点击fix按钮后的代码://将下载的修复包,复制到私有目录,解压从.dex文件中取到对应的.class文件//从sd卡取修复包//目标文件。

2401_84148974的博客 804

Android之Tinker集成使用

前言:当前市面上为了实现不发新版本的前提下实现修改线上严重bug的目标而诞生的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及腾讯的Tinker等等,但是其中用的比较广的还是腾讯的Tinker,所以在这里我也是选取了Tinker作为学习研究对象。 tinker之dex更新详解 tinker之资源更新详解 tinker之so更新详解 在文章开始之前先copy一个不...

蒙的博客 2184

Tinker热修复从 0 —1 详细完整的集成

** Tinker热修复从 0 —1 完整集成 ** 因为给新手看所以讲的非常细,文章比较长,希望能帮助大家。 废话不多说,咱们直接来干货。 ** 第一步: ** 在项目的Build.gradle中添加 buildscript { repositories { google() jcenter() } dependencies { classpath ‘com.android.tools.build:gradle:3.4.2’ // NOTE: Do not place your applica

qq_42446013的博客 796

热修复框架Tinker的从0到集成之路

热修复框架Tinker的从0到集成之路 转载请标明出处: http://blog.youkuaiyun.com/lisdye2/article/details/52049857 本文出自:【Alex_MaHao的博客】 项目中的源码已经共享到github,有需要者请移步【Alex_MaHao的github】 2015年10月,QQ空间提出了热修复方案,一时间热修复风靡Android界,阿里

茅屋 6552

热修复Tinker 集成bug

1.Caused by: com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:applicationLike must not be null. 2. Error:java.lang.RuntimeException: Annotation processors must be explicitly ...

and_caicai的博客 1874

Android热修复技术:Tinker实战指南

本文还有配套的精品资源,点击获取 简介:在Android开发中,热修复技术是保持应用稳定性和用户体验的关键。本文详细介绍了如何使用腾讯开源的Tinker进行应用的动态更新,包括集成Tinker、配置Tinker Gradle插件、创建和编译补丁包、服务器端管理和客户端补丁应用等关键步骤。文章还涵盖了测试、调试以及使用Tinker时需要关注的注意事项,确保开发者能够高效地修复...

weixin_29717341的博客 2714

Tinker热修复集成流程及采坑

这篇文章的集成AS版本是2.1.3,gradle版本也是2.x版本,其实2.x版本和3.x版本的gradle差别不是很大。 文章就直接写我从开始集成到结束的整个过程,在这中间遇到的问题我会用红色标注出来。 集成Tinker当然首先是看官方文档,进入GitHub的Tinker地址tinker去查看官方文档,点击右侧的接入指南,看整个接入的过程,大概浏览了一遍,感觉头大啊,太tm麻烦了,但是没办法不学...

static_zh的博客 944

微信Tinker热修复集成测试完整Demo

随着Android应用的复杂度不断提升,线上Bug的修复变得愈发重要。传统的修复方式需要用户重新下载安装新版本,这不仅影响用户体验,也增加了运维成本。为了解决这一问题,热修复技术应运而生。微信团队推出的Tinker框架,是业界较为成熟的一种全量热修复方案,它支持类、资源文件(res)、Android资源目录(assets)以及原生库(so)的动态替换,从而实现不重新发版即可修复线上问题。

weixin_28787801的博客 870

Android热修复——Tinker的集成

前言 做前端开发的都知道,当我们项目做完了以后,都会把应用上传到应用市场上供用户下载使用,比如上传到应用宝啊,应用汇啊,360啊,小米,华为,魅族啊,等等 但是,有时候我们会经常遇到一些很扯淡的事情,刚刚熬夜加班将项目发到应用市场上,第二天,又发现一个严重bug,难道是开会研究看看是否能在下一版解决?还是将bug解决了, 在给测试进行测试,然后再发版?如果从新发版,这个成本太高了,那怎么办呢,今...

qq_43093708的博客 229

我想自己组装一台服务器,微调大模型通义千问2.5 Omni 72B,但是我是个人购买,资金非常有限,最省的方案

阶段动作✅ 推理用 GGUF + llama.cpp + 4090 加载 14B INT4✅ 微调训练 Qwen2.5-7B-Chat,使用 LoRA / QLoRA❌ 放弃Qwen2.5-Omni-72B 全量微调(显存需求 >160G,不现实)

AngelCryToo的专栏 2822

二相混合式步进电机闭环矢量控制simulink仿真(含仿真文件)

在位置跟踪伺服系统中二相混合式步进电机闭环矢量控制simulink仿真

qq_39023633的博客 2万+

Android热修复——Tinker集成

前言 现在市场上的热修复工具或者框架有很多很多,除了Tinker比较出名的还有阿里的 AndFix、美团的 Robust 以及 QZone 的超级补丁方案。每个方案的优缺点如下图: 阿里AndFix接入简单,不过兼容性不太好;QZone方案对性能损耗较大并且补丁大小较大;美团的 Robust兼容性好,但是尚未开源。 所以,如果选择开源的热修复方案,Tinker是最佳的选择。下面记录一下Tinke...

Memory_of_the_wind的博客 451

Linux内存压力测试工具【memtester】详且精

对Linux下的压力测试工具memtester有一个比较人性化的概述,以及避坑心得。

weixin_44118923的博客 1万+

Android热修复框架Tinker的集成和遇到的问题

移动app开发的一个痛点就是遇到了bug只能重新打包,上传各个市场,审核,客户使用app后还会检查更新下载,这样为了修复一个bug而进行的这一套繁琐的操作,成本很高,不管对企业还是对用户都很烦恼;热修复的需求是很迫切的,近几年热修复的发展也很快,各个龙头企业都推出自己的热修复框架,之前了解过阿里的Dexposed,Andfix,QQ空间团队的Nuwa等等,都有很多不成熟的地方;什么是 TinkerT

zhaobo012387的博客 1105

手把手教你用Matlab为PMSM电流环设计2P2Z补偿器(附与PI对比)

本文详细介绍了如何使用Matlab的controlSystemDesigner工具为永磁同步电机(PMSM)电流环设计2P2Z补偿器,并与传统PI控制器进行性能对比。通过频域分析和时域仿真,展示了2P2Z补偿器在提升带宽、减少超调等方面的优势,为高性能伺服驱动和精密运动控制提供了实用解决方案。

weixin_42551227的博客 376

企业信息化系统的组成模块-核心业务系统

通过标准化的数据流形成完整的业务闭环,CRM生成的销售订单驱动ERP的生产和采购,ERP将生产计划传递给MES执行,MES报工数据返回ERP进行成本核算,ERP再将交付信息同步给CRM供客户查询。生产管理确保高效交付,缩短订单周期。某贸易公司实施ERP供应链模块后,库存周转率从每年4次提升到8次,库存资金占用从2000万降到1200万,释放800万现金流,同时缺货率从15%降到5%。│ │库存 │ │ MRP │ │ ERP │ │ 云ERP/ │ │。

weiyvyy的博客 694

Tinker实战指南:从零开始集成Android热修复

Tinker实战指南:从零开始集成Android热修复 【免费下载链接】tinker Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk....

gitblog_00613的博客 547

【深度学习新浪潮】OpenClaw跟机器人行业有哪些很好的研究结合点?

OpenClaw正通过"AI大脑+机器人肢体"的深度融合,重新定义机器人与人类的交互方式和自主操作能力。近期ROSClaw框架发布、人形机器人集成和空间计算试验等进展,标志着OpenClaw已从概念验证阶段进入实际应用落地期,为机器人行业带来了自然语言控制普及化具身智能平民化和跨平台开发标准化三大变革机遇。

专注:太空算力、AI infra、异构调度、大模型成本工程 228

热修复——Tinker的集成与使用(1)

基础包就是已经上架的apk文件(假设是1.0版本)。这其实很好理解,在新版本的App上架之前(假设是2.0版本),我们会用到Tinker来修复1.0版App中存在的bug,这时就需要用到Tinker来产生补丁包文件,而补丁包文件的本质,就是修复好Bug的App与1.0版本App之间的文件差异。

2401_84148974的博客 1268

Unreal从入门到精通之如何绘制用于VR的3DUI交互的手柄射线

之前我写过一篇文章《Unreal5从入门到精通之如何在VR中使用3DUI》,其中讲到使用WidgetComponent和3DUI进行交互。然而,这种方法存在一个问题:为了让射线可视化,我们需要勾选“调试/显示调试”,这样才能在手柄上看到代表其指向的一条红色射线。但最关键的问题是,这个显示调试信息的功能只属于Debug调试模式,也就是说,一旦将项目正式打包发布,这条射线辅助线就没有了。如下图所示:我就觉得Unreal官方真的是有点过分了,这不应该是最基本的功能么?难道我们VR要在Debug模式下发布?

TxNet 1611

Git下载与安装

一、Git下载 二、Git安装

欢迎订阅我的“JAVA全栈开发笔记(全)”专栏,价格优惠,仅需19.9元。 6万+

Android集成微信Tinker热修复及使用

Tinker集成 准备工作 1.打开Tinker官网http://www.tinkerpatch.com创建自己的Tinker账号。 2.在Tinker控制台上创建自己的项目,获取AppKey。 第一步 添加 gradle 插件依赖 打开项目的build.gradle文件 在下图位置中添加: classpath "com.tinkerpatch.sdk:tinkerpat...

a1084701302的专栏 451

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Android热修复之Tinker集成最新详解

前言 该文章属于初级集成详解,侧重Tinker的使用,如若想深入了解其原理请自行查阅相关文档Tinker相关文档 当前市面的热补丁方案有很多,其中比较出名的有阿里的 AndFix、美团的 Robust 以及 QZone 的超级补丁方案,先来做一个对比 热修复对比表.png 通过对比我们发现Tinker还是有一定的优势。 下面我们就来摆一摆Tinker的用法 1.先去Tin...

weixin_43901866的博客 271

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

上一篇: Linux-使用scp与服务器传输文件(macOS)
下一篇: Frida-搭建window环境
Mackkill
博客等级 码龄15年 65粉丝 39原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值