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 flavors = new ArrayList<>();project.android.productFlavors.each {flavor ->    flavors.add(flavor.name)}boolean hasFlavors = flavors.size() > 0def 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;@Overrideprotected 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原理

更多相关文章

  1. [Android]代码实现ColorStateList及StateListDrawable
  2. Android 完全退出应用程序实现代码
  3. android 6.0后usb otg设备不显示在文件管理器中
  4. 将Activity打包成jar文件
  5. Eclipse中跟踪调试Android Framework源代码

随机推荐

  1. Android知识体系总结(全方面覆盖Android
  2. Android(安卓)用axis2 webService产生jav
  3. android EditText inputType详解
  4. Eclipse 默认打开Android(安卓)xml 布局
  5. The lifecycle of an android applicatio
  6. Android开机广播和关机广播
  7. 读取Activity/Processes的PID(Android)
  8. android-获取手机电话的状态
  9. Android尺寸单位
  10. Android(安卓)Studio中解决Gradle DSL me