V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
KevinRed
V2EX  ›  程序员

Tinker 热修复框架集成实战

  •  
  •   KevinRed · 2020-03-25 21:47:25 +08:00 · 1491 次点击
    这是一个创建于 1482 天前的主题,其中的信息可能已经有所发展或是发生改变。

    欢迎访问原文Tinker 热修复框架集成实战

    欢迎访问原文Tinker 热修复框架集成实战

    欢迎访问原文Tinker 热修复框架集成实战

    欢迎访问原文Tinker 热修复框架集成实战

    最让移动端开发崩溃的就是,刚强推了一个版本就出了重大 bug,只能重新发版解决 但集成了热修复框架就能迎刃而解,经过对比最终选用了腾讯微信团队的 Tinker 性能还不错,支持的修复场景也多,主要是免费(ಥ_ಥ)

    下面介绍一下接入流程

    一、接入

    推荐 Android Gradle Plugin Version 使用 3.1.4

    Gradle Version 使用 4.4

    首先在 gradle.properties 配置版本号,方便后期维护

    TINKER_VERSION=1.9.13.2
    

    在项目的 build.gradle 配置依赖

    classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
    

    在 app 的 build.gradle 配置依赖

    compileOnly "com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}"
    
    annotationProcessor "com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}"
    
    implementation "com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"
    

    二、多渠道配置

    //证书配置
    
    signingConfigs {
      debug {
        storeFile file('../debug.jks')
        storePassword ''
        keyAlias ''
        keyPassword ''
      }
      release {
        storeFile file('../release.jks')
        storePassword ''
        keyAlias ''
        keyPassword ''
      }
    
    }
    
    
    //build 配置
    
    buildTypes {
      debug {
        minifyEnabled false
        useProguard false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        signingConfig signingConfigs.debug
        buildConfigField "boolean", "ISDEBUG", "true"
      }
    
      release {
        minifyEnabled false
        useProguard false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        signingConfig signingConfigs.release
        buildConfigField "boolean", "ISDEBUG", "false"
      }
    }
    
    //多渠道配置
    
    productFlavors {
      product {
        //生产
        applicationId "com.kevin.blog"
        versionCode 1
        versionName '1.0.0'
        manifestPlaceholders = [app_name : "@string/app_name"]
      }
    
      develop {
        //测试
        applicationId "com.kevin.develop.blog"
        versionCode 1
        versionName '1.0.0'
        manifestPlaceholders = [app_name : "@string/app_name_test"]
      }
    
    }
    

    三、Tinker 配置

    配置项含义可到官网查询

    apply plugin: 'com.tencent.tinker.patch'
    tinkerPatch {
    
      oldApk = ''
      ignoreWarning = false
      useSign = true
      tinkerEnable = true
      buildConfig {
        applyResourceMapping = ''
        tinkerId = ''
        keepDexApply = false
        isProtectedApp = false
        supportHotplugComponent = true
      }
    
      dex {
        dexMode = "jar"
        pattern = ["classes*.dex",
                   "assets/secondary-dex-?.jar"]
        loader = ["tinker.sample.android.app.BaseBuildInfo"]
      }
    
      lib {
        pattern = ["lib/*/*.so"]
      }
    
      res {
        pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = ["assets/sample_meta.txt"]
        largeModSize = 100
      }
      sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
      }
    }
    

    四、Gradle 脚本实现自动打包

    原创超级方便的脚本,可以把基础包和补丁复制到 tinker 目录下

    
    //productVersion
    def productVersionCode = 1
    def productVersionName = '1.0.0'
    
    
    //develop
    def developVersionCode = 1
    def developVersionName = '1.0.0'
    
    //10 release  20 debug
    def buildType = 10
    
    // 1 product   2 develop
    def productFlavor = 2
    
    def patchPath = ''
    
    def getPatchPath(){
      return patchPath
    }
    
    def bakPath = file("../tinker/")
    ext {
      tinkerEnabled = true
    
      tinkerOldApkPath = ""
      tinkerApplyResourcePath = ""
      tinkerId = ""
    
      switch (buildType + productFlavor) {
        case 11:
          tinkerOldApkPath =
              "${bakPath}/product_release/TinkerBase${productVersionName}_product_release/TinkerBase${productVersionName}_product_release.apk"
          tinkerApplyResourcePath =
              "${bakPath}/product_release/TinkerBase${productVersionName}_product_release/TinkerBase${productVersionName}_product_release-R.txt"
          tinkerId = "${productVersionName}"
          patchPath = "/outputs/apk/product/tinkerPatch/product/release/patch_signed_7zip.apk"
          break
        case 12:
          tinkerOldApkPath =
              "${bakPath}/develop_release/TinkerBase${developVersionName}_develop_release/TinkerBase${developVersionName}_develop_release.apk"
          tinkerApplyResourcePath =
              "${bakPath}/develop_release/TinkerBase${developVersionName}_develop_release/TinkerBase${developVersionName}_develop_release-R.txt"
          tinkerId = "${developVersionName}"
          patchPath = "/outputs/apk/develop/tinkerPatch/develop/release/patch_signed_7zip.apk"
          break
        case 21:
          tinkerOldApkPath =
              "${bakPath}/product_debug/TinkerBase${productVersionName}_product_debug/TinkerBase${productVersionName}_product_debug.apk"
          tinkerApplyResourcePath =
              "${bakPath}/product_debug/TinkerBase${productVersionName}_product_debug/TinkerBase${productVersionName}_product_debug-R.txt"
          tinkerId = "${productVersionName}"
          patchPath = "/outputs/apk/product/tinkerPatch/product/debug/patch_signed_7zip.apk"
          break
        case 22:
          tinkerOldApkPath =
              "${bakPath}/develop_debug/TinkerBase${developVersionName}_develop_debug/TinkerBase${developVersionName}_develop_debug.apk"
          tinkerApplyResourcePath =
              "${bakPath}/develop_debug/TinkerBase${developVersionName}_develop_debug/TinkerBase${developVersionName}_develop_debug-R.txt"
          tinkerId = "${developVersionName}"
          patchPath = "/outputs/apk/product/tinkerPatch/product/debug/patch_signed_7zip.apk"
          break
      }
    }
    
    
    //文件复制
      def date = new Date().format("YYYY-MM-dd_HH-mm-ss")
    
      android.applicationVariants.all { variant ->
        def taskName = variant.name
    
        tasks.all {
          if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
    
            it.doLast {
              def baseName = variant.baseName.replaceAll(/-/, "_")
              def fileNamePrefix = "${project.name}-${variant.baseName}"
              def newFileNamePrefix = "TinkerBase${variant.productFlavors[0].versionName}_${baseName}"
              def destPath = file("${bakPath}/${baseName}/TinkerBase${variant.productFlavors[0].versionName}_${baseName}")
              if (destPath != null && !destPath.exists()) {
                copy {
                  from variant.outputs.first().outputFile
                  into destPath
                  rename { String fileName ->
                    fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                  }
    
                  from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                  into destPath
                  rename { String fileName -> fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                  }
                }
              }
            }
          }
    
          if ("tinkerPatch${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
            it.doLast {
              def baseName = variant.baseName.replaceAll(/-/, "_")
              def fileNamePrefix = "patch_signed_7zip"
              def newFileNamePrefix = "TinkerPatch(Base_${baseName}_${variant.productFlavors[0].versionName})_${date}"
              def destPath = file("${bakPath}/${baseName}/TinkerBase${variant.productFlavors[0].versionName}_${baseName}/patch")
    
    
              copy {
                from "${buildDir}/${patchPath}"
                into destPath
                rename { String fileName ->
                  fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                }
    
              }
    
            }
          }
        }
      }
    }
    

    五、Application 改造

    这是比较麻烦的一步,如果你的 application 不是继承 Application 那就麻烦了,需要改造为直接继承 Application

    改完之后可以愉快的开始了

    首先建立 TinkerApplication 继承 DefaultApplicationLike,代码如下

    
    @SuppressWarnings("unused")
    @DefaultLifeCycle(
        application = ".MyApplication",     //application 类名
        loaderClass = "com.tencent.tinker.loader.TinkerLoader",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
    public class TinkerApplication extends DefaultApplicationLike {
    
      private static ApplicationComponent mApplicationComponent;
      public static ApplicationComponent getApplicationComponent(){
        return mApplicationComponent;
      }
     
    
      
    
      public TinkerApplication(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);
    
        //把你原来 application 内容移到这里
    
        
        MultiDex.install(base);
        TinkerInstaller.install(this);
      }
    
      
    
    
      @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
      public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
      }
    }
    

    注意原 application 中引用 application 的地方改成 getApplication()

    然后把 AndroidManifest.xml 里 application 路径改为 @DefaultLifeCycle 注解中声明的路径即可

    六、使用

    安装

    TinkerInstaller.onReceiveUpgradePatch(context, 路径);
    

    卸载

    Tinker.with(getApplicationContext()).cleanPatch();
    
    Tinker.with(getApplicationContext()).cleanPatchByVersion(版本号)
    

    杀死应用

    ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
    

    Hack 方式修复 so

    TinkerLoadLibrary.installNavitveLibraryABI(this, abi);
    

    非 Hack 方式修复 so

    TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), "lib/" + abi, 模块名);
    
    TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), 模块名);
    
    TinkerLoadLibrary.loadArmV7Library(getApplicationContext(), 模块名)
    
    1 条回复    2020-03-26 13:29:31 +08:00
    KevinRed
        1
    KevinRed  
    OP
       2020-03-26 13:29:31 +08:00 via Android
    (゚Д゚≡゚д゚)!?没人看嘛
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5470 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 06:57 · PVG 14:57 · LAX 23:57 · JFK 02:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.