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 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原理

你可能感兴趣的:(android,android)