天天看點

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

Tinker是什麼

Tinker是微信官方的Android熱更新檔解決方案,它支援動态下發代碼、So庫以及資源,讓應用能夠在不需要重新安裝的情況下實作更新。當然,你也可以使用Tinker來更新你的插件。

它主要包括以下幾個部分:

  1. gradle編譯插件: 

    tinker-patch-gradle-plugin

  2. 核心sdk庫: 

    tinker-android-lib

  3. 非gradle編譯使用者的指令行版本: 

    tinker-patch-cli.jar

為什麼使用Tinker

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

總的來說:

  1. AndFix作為native解決方案,首先面臨的是穩定性與相容性問題,更重要的是它無法實作類替換,它是需要大量額外的開發成本的;
  2. Robust相容性與成功率較高,但是它與AndFix一樣,無法新增變量與類隻能用做的bugFix方案;
  3. Qzone方案可以做到釋出産品功能,但是它主要問題是插樁帶來Dalvik的性能問題,以及為了解決Art下記憶體位址問題而導緻更新檔包急速增大的。

特别是在Android N之後,由于混合編譯的inline政策修改,對于市面上的各種方案都不太容易解決。而Tinker熱更新檔方案不僅支援類、So以及資源的替換,它還是2.X-7.X的全平台支援。利用Tinker我們不僅可以用做bugfix,甚至可以替代功能的釋出。Tinker已運作在微信的數億Android裝置上,那麼為什麼你不使用Tinker呢?

Tinker的已知問題

由于原理與系統限制,Tinker有以下已知問題:

  1. Tinker不支援修改AndroidManifest.xml,Tinker不支援新增四大元件;
  2. 由于Google Play的開發者條款限制,不建議在GP管道動态更新代碼;
  3. 在Android N上,更新檔對應用啟動時間有輕微的影響;
  4. 不支援部分三星android-21機型,加載更新檔時會主動抛出

    "TinkerRuntimeException:checkDexInstall failed"

  5. tinker的一般模式并不支援加強,需要使用usePreGeneratedPatchDex模式,即提前生成更新檔模式。某些加強工具可能會将非exported的四大元件類名替換,這些類将無法修改。對于Android N之後的裝置,本模式可能會因為内聯而出現問題,建議過濾N之後的裝置;
  6. 對于資源替換,不支援修改remoteView。例如transition動畫,notification icon以及桌面圖示。

如何使用Tinker 接下來帶大家 在項目中一步步使用 Tinker,我們選一個平台 我選的是bugly ,騰訊的,當然你也可以選擇其他的平台, TinkerPatch ,這個也可以 ,但以後肯定要收費的,  TinkerPatch平台文檔 。

首先第一步  添加插件依賴 工程根目錄下“build.gradle”檔案中添加:

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第二步:在app module的“build.gradle”檔案中添加(我的配置): apply  plugin: 'com.android.application'

def releaseTime() {

return new Date().format( "yyyy-MM-dd", TimeZone. getTimeZone( "UTC"))

}

dependencies {

compile fileTree( dir: 'libs', include: [ '*.jar'])

compile 'com.android.support:appcompat-v7:24.1.1'

// 多 dex 配置

compile "com.android.support:multidex:1.0.1"

// 內建 Bugly 熱更新 aar (灰階時使用方式)

// compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')

compile 'com.tencent.bugly:crashreport_upgrade:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 1.2.0

compile 'com.tencent.bugly:nativecrashreport:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 2.2.0

}

android {

compileSdkVersion 23

buildToolsVersion "23.0.2"

// 編譯選項

compileOptions {

sourceCompatibility JavaVersion. VERSION_1_7

targetCompatibility JavaVersion. VERSION_1_7

}

// recommend

dexOptions {

jumboMode = true

}

// 簽名配置

signingConfigs {

debug {

storeFile file( "../KeyDianda.jks")

storePassword "111111"

keyAlias "key_dianda"

keyPassword "111111"

}

release {

storeFile file( "..

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-release-1223-13-43-58.apk"

// proguard mapping file to build patch apk

tinkerApplyMappingPath = "${bakPath} /app-release-1223-13-43-58-mapping.txt"

// resource R.txt to build patch apk, must input if there is resource changed

tinkerApplyResourcePath = "${bakPath} /app-release-1223-13-43-58-R.txt"

// Todo 需要修改

tinkerBuildFlavorDirectory = "${bakPath} /app-release-1223-13-43-58"

}

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.bugly.tinker-support'

// 依賴 tinker 插件

apply plugin: 'com.tencent.tinker.patch'

tinkerSupport {

}

// 全局資訊相關配置項

tinkerPatch {

oldApk = getOldApkPath() // 必選, 基準包路徑

ignoreWarning = false // 可選,預設 false

useSign = true // 可選,預設 true , 驗證基準 apk 和 patch 簽名是否一緻

// 編譯相關配置項

// Todo 需要修改 tinkerId 每次打更新檔時 tinkerId 跟打基準包時得 tinkerId 不能一樣

buildConfig {

applyMapping = getApplyMappingPath() // 可選,設定 mapping 檔案,建議保持舊 apk 的 proguard 混淆方式

applyResourceMapping = getApplyResourceMappingPath() // 可選,設定 R.txt 檔案,通過舊 apk 檔案保持 ResId 的配置設定

tinkerId = "bugbugbug_v2.0.0gggggg"

}

// dex 相關配置項

dex {

dexMode = "jar" // 可選,預設為 jar

usePreGeneratedPatchDex = true // 可選,預設為 false

pattern = [ "classes*.dex",

"assets/secondary-dex-?.jar"]

// Todo 需要修改 自己的包名

loader = [ "com.tencent.tinker.loader.*",

"com.tinker.ddinfo.SampleApplication",

]

}

// lib 相關的配置項

lib {

pattern = [ "lib/armeabi.so"]

}

// res 相關的配置項

res {

pattern = [ "res", "assets", "resources.arsc", "AndroidManifest.xml"]

ignoreChange = [ "assets/sample_meta.txt"]

largeModSize = 100

}

// 用于生成更新檔包中的 'package_meta.txt' 檔案

packageConfig {

configField( "patchMessage", "tinker is sample to use")

configField( "platform", "all")

configField( "patchVersion", "1.0")

}

// 7zip 路徑配置項,執行前提是 useSign 為 true

sevenZip {

zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional

// path = "/usr/local/bin/7za" // optional

}

}

List<String> flavors = new ArrayList<>();

project. android.productFlavors.each { flavor ->

flavors.add(flavor.name)

}

boolean hasFlavors = flavors.size() > 0

android. applicationVariants.all { variant ->

def taskName = variant.name

def date = new Date().format( "MMdd-HH-mm-ss")

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"

}

}

}

}

}

}

*這裡要注意 需要修改tinkerId每次打更新檔時tinkerId跟打基準包時得tinkerId不能一樣

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第三步: package com.tinker.ddinfo ;

import com.tencent.tinker.loader.app.TinkerApplication ;

import com.tencent.tinker.loader.shareutil.ShareConstants ;

public class SampleApplication  extends TinkerApplication {

    public SampleApplication() {

        super(ShareConstants. TINKER_ENABLE_ALL ,  "com.tinker.ddinfo.SampleApplicationLike" ,//修改成自己的包名

                "com.tencent.tinker.loader.TinkerLoader" , false) ;

    }

}

第四步: 自定義ApplicationLike package com.tinker.ddinfo;

import android.annotation.TargetApi;

import android.app.Application;

import android.content.Context;

import android.content.Intent;

import android.content.res.AssetManager;

import android.content.res.Resources;

import android.os.Build;

import android.support.multidex.MultiDex;

import com.tencent.bugly.Bugly;

import com.tencent.bugly.beta.Beta;

import com.tencent.tinker.loader.app.DefaultApplicationLike;

import com.tinker.ddinfo.utils.ExampleConfig;

public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    public SampleApplicationLike(Application application, int tinkerFlags,

            boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,

            long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,

            ClassLoader[] classLoader, AssetManager[] assetManager) {

        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,

                applicationStartMillisTime, tinkerResultIntent, resources, classLoader,

                assetManager);

    }

    @Override

    public void onCreate() {

        super.onCreate();

        // TODO: 這裡進行Bugly初始化

       // 設定開發裝置

        Bugly.setIsDevelopmentDevice(getApplication(), true);

        // 這裡實作SDK初始化,appId替換成你的在Bugly平台申請的appId

        Bugly.init(getApplication(), ExampleConfig.BUGLY_APP_ID, true);

    }

    @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);

        // TODO: 安裝tinker

        Beta.installTinker(this);

    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)

    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {

        getApplication().registerActivityLifecycleCallbacks(callbacks);

    }

} 第五步:Androidmanifest 檔案

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第六步:MainActivity 這個類是為了配合顯示 修改bug 

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第七步:根據指令打基準包 (就是apk包) 在bakAPK 目錄下

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第八步:這時要注意打完基準包後 ,需要修改bug 我在mainActivity 裡面修改了一行Toast代碼

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

然後修改 app 下面的build.gradle  檔案

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

記得需要修改 tinkerId 

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第九步:執行tinkerPatchRelease 指令 打更新檔包

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

第十步 上傳 到騰訊Bugly 平台。

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建
關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

需要注意的是 在手機應用上 需要 冷啟動 ,殺掉程序 重新 進入應用才會成功。 有時候需要等一兩分鐘

最後附上 demo 下載下傳連結

http://download.csdn.net/detail/weitf/9719535

關于使用騰訊 Bugly 平台 Tinker開源熱修複架構的 項目內建

繼續閱讀