热跟新Tinker详细集成步骤

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

1.拷贝build.gradle文件从https://github.com/Tencent/tinker/tree/master/tinker-sample-android

gradle.properties里面

TINKER_VERSION=1.9.8
GRADLE_3=true

项目的build.gradle里面代码:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26

    //recommend
    dexOptions {
        jumboMode = true
    }

    defaultConfig {
        applicationId "com.chinamall21.mobile.mytinker"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        /**
         * you can use multiDex and install it in your ApplicationLifeCycle implement
         */
        multiDexEnabled true
        /**
         * buildConfig can change during patch!
         * we can use the newly value when patch
         */
        buildConfigField "String", "MESSAGE", "\"I am the base apk\""
//        buildConfigField "String", "MESSAGE", "\"I am the patch apk\""
        /**
         * client version would update with patch
         * so we can get the newly git version easily!
         */
        buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
        buildConfigField "String", "PLATFORM", "\"all\""
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

def bakPath = file("${buildDir}/bakApk/")

/**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/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-0825-17-46-20.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-0825-17-46-20-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") ? Boolean.parseBoolean(TINKER_ENABLE) : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}

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

    tinkerPatch {
 
        oldApk = getOldApkPath()

        ignoreWarning = false
        useSign = true
        tinkerEnable = buildWithTinker()
        buildConfig {
            applyMapping = getApplyMappingPath()
    
            applyResourceMapping = getApplyResourceMappingPath()

            tinkerId = getTinkerIdValue()

            keepDexApply = false
            isProtectedApp = false
            supportHotplugComponent = 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")
        }
   
        sevenZip {
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
        }
    }

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

    /**
     * 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
                        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"
                        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"
                    }

                }
            }
        }
    }
}


dependencies {
    if (is_gradle_3()) {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        testImplementation 'junit:junit:4.12'
        implementation "com.android.support:appcompat-v7:23.1.1"
        implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
        annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
        compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
        implementation 'com.android.support.constraint:constraint-layout:1.1.2'
        implementation "com.android.support:multidex:1.0.1"

    } else {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile "com.android.support:appcompat-v7:23.1.1"
        compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
        provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
        implementation 'com.android.support.constraint:constraint-layout:1.1.2'
        compile "com.android.support:multidex:1.0.1"

    }
}
def gitSha() {
    try {
        String gitRev = 'git version 2.16.2.windows.1'.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 javaVersion = JavaVersion.VERSION_1_7

2.拷贝官方的代码

热跟新Tinker详细集成步骤_第1张图片
a.png

需要注意的是必须使用SampleApplicationLike作为自己的Application,并且在注解上面替换成自己的包名

@DefaultLifeCycle(application = "{自己的包名}.SampleApplication",
                  flags = ShareConstants.TINKER_ENABLE_ALL,
                  loadVerifyFlag = false)

3.在AndroidManifest文件中修改

 android:name=".app.SampleApplication"

添加service:


4.打开studio右侧的Gradle里,双击assemableDebug

热跟新Tinker详细集成步骤_第2张图片
a.png

这时在build/bakApk下生成了二个文件


热跟新Tinker详细集成步骤_第3张图片
b.png

用它们的文件名替换App里面gradle下的文件名

热跟新Tinker详细集成步骤_第4张图片
b.png

5.修改MainActivity代码

我的电脑adb指令不太好使不知道什么原因,用小米6usb连接不可以直接连接电脑传数据,要打开开发者选项进行设置如下:

热跟新Tinker详细集成步骤_第5张图片
Screenshot_2018-08-25-18-41-07-574_com.android.se.png

这样就能看到下面二个文件夹


热跟新Tinker详细集成步骤_第6张图片
Z@U}2J@I`C3P73S_3}}ZNJ6.png

我们把刚刚生成的apk文件拷贝到手机里的Pictures文件夹里,同时修改MainActivity关于生成路径的代码:

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

6.运行拷贝到手机的apk效果如下:

热跟新Tinker详细集成步骤_第7张图片
Screenshot_2018-08-25-18-50-35-465_com.chinamall2.png

这时我们修改代码,在布局里面加了一行修复成功,然后双击TinkerPathchDebug
热跟新Tinker详细集成步骤_第8张图片
16$HW0C21}5%_4R3FE`3FD1.png

在build/outputs/apk/tinkerPatch下面看到patch_signed_7zip.apk的文件拷贝到我们手机的内存卡里的Pictures里

热跟新Tinker详细集成步骤_第9张图片
a.png

MainActivity代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void patch(View view){
        TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/patch_signed_7zip.apk");
    }

    public void kill(View view){
        ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

这时我们点击PATCH按钮并没什么卵用,log信息如下:
DefaultLoadReporter: patch loadReporter onLoadPatchListenerReceiveFail: patch receive fail: /storage/emulated/0/Pictures/patch_signed_7zip.apk, code: -2
原因是文件读写权限没有开

    
    

如果时api22以上还要在手机的权限管理里面手动打开文件读写权限

我们再次运行:弹出patch success,please restart process代表修复成功


c.gif

你可能感兴趣的:(热跟新Tinker详细集成步骤)