Tinker 热修复框架集成实战

2020-03-25 21:47:25 +08:00
 KevinRed

欢迎访问原文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(), 模块名)
1496 次点击
所在节点    程序员
1 条回复
KevinRed
2020-03-26 13:29:31 +08:00
(゚Д゚≡゚д゚)!?没人看嘛

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/656200

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX