Android熱修復——Tinker的集成


前言

做前端開發的都知道,當我們項目做完了以后,都會把應用上傳到應用市場上供用戶下載使用,比如上傳到應用寶啊,應用匯啊,360啊,小米,華為,魅族啊,等等
但是,有時候我們會經常遇到一些很扯淡的事情,剛剛熬夜加班將項目發到應用市場上,第二天,又發現一個嚴重bug,難道是開會研究看看是否能在下一版解決?還是將bug解決了,
在給測試進行測試,然后再發版?如果從新發版,這個成本太高了,那怎么辦呢,今天我們就來談一談熱修復——Tinker!

現在市場上的熱修復工具或者框架有很多很多,比較出名的有阿里的 AndFix、美團的 Robust 以及 QZone 的超級補丁方案。這個方案各有利弊,但是今天我們不說這幾個方案,我們來聊一聊另一個補丁方案——Tinker。首先我們上一張圖:

Tinker熱補丁方案·不僅支持類、So以及資源的替換,它還是2.X-7.X的全平台支持。


在官網上,介紹這個Tinker,用了一句話:Tinker 已運行在微信的數億 Android 設備上,那么為什么你不使用 Tinker 呢? ps:別人幾億的用戶量在用Tinker,都不擔心出現什么問題,
你們幾個用戶量的應用,還整天瞎擔心。

這篇文章,主要是和大家聊一下怎么集成這個Tinker,並不會設計很多原理的東西,如果需要了解更深入的原理性的東西,可以到官網看看。點擊官網http://tinkerpatch.com/Docs/intro

 

集成SDK

第一步 添加 gradle 插件依賴

在項目的Gradle添加遠程依賴

 1 buildscript {
 2     repositories {
 3         jcenter()
 4     }
 5     dependencies {
 6         classpath 'com.android.tools.build:gradle:2.3.2'
 7         // TinkerPatch 插件
 8         classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.8"
 9         // NOTE: Do not place your application dependencies here; they belong
10         // in the individual module build.gradle files
11     }
12 }

 

第二步 集成 TinkerPatch SDK

在app中的gradle添加denpendencies 依賴,注意:這兩個gradle不是同一個gradle,上面的那個build gradle 是整個項目的,下面這個build gradle是在app里面的,注意區分

1 dependencies {
2     // 若使用annotation需要單獨引用,對於tinker的其他庫都無需再引用
3     provided("com.tinkerpatch.tinker:tinker-android-anno:1.8.0")
4     compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.8")
5 }

 

注意,若使用 annotation 自動生成 Application, 需要單獨引入 Tinker 的 tinker-android-anno 庫。除此之外,我們無需再單獨引入 tinker 的其他庫。

為了簡單方便,我們將 TinkerPatch 相關的配置都放於 tinkerpatch.gradle 中, 我們需要將其引入:

1 apply from: 'tinkerpatch.gradle'

 

第三步 配置 tinkerpatchSupport 參數

好了,接下來我們就在app目錄下創建這個tinkerpatch.gradle,如圖所示的目錄

 

打開tinkerpatch.gradle,將 TinkerPatch 相關的配置都放於tinkerpatch.gradle中。

 1 apply plugin: 'tinkerpatch-support'
 2 
 3 /**
 4  * TODO: 請按自己的需求修改為適應自己工程的參數
 5  */
 6 
 7 //基包路徑
 8 def bakPath = file("${buildDir}/bakApk/")
 9 //基包文件夾名(打補丁包的時候,需要修改)
10 def baseInfo = "app-1.0.1-1018-17-29-31"
11 //版本名稱
12 def variantName = "debug"
13 
14 /**
15  * 對於插件各參數的詳細解析請參考
16  *
17  */
18 tinkerpatchSupport {
19     //可以在debug的時候關閉 tinkerPatch
20     tinkerEnable = true
21     //是否使用一鍵接入功能 默認為false  是否反射 Application 實現一鍵接入;
22     // 一般來說,接入 Tinker 我們需要改造我們的 Application, 若這里為 true, 即我們無需對應用做任何改造即可接入。
23     reflectApplication = true
24     //將每次編譯產生的 apk/mapping.txt/R.txt 歸檔存儲的位置
25     autoBackupApkPath = "${bakPath}"
26     appKey = "這里的appKey修改成你自己的appkey"// 注意!!!  需要修改成你的appkey
27 
28     /** 注意: 若發布新的全量包, appVersion一定要更新 **/
29     appVersion = "1.0.1"
30 
31     def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
32     def name = "${project.name}-${variantName}"
33     /**
34      * 基准包的文件路徑, 對應 tinker 插件中的 oldApk 參數;編譯補丁包時,
35      * 必需指定基准版本的 apk,默認值為空,則表示不是進行補丁包的編譯
36      */
37     baseApkFile = "${pathPrefix}/${name}.apk"
38 
39     /**
40      * 基准包的 Proguard mapping.txt 文件路徑, 對應 tinker 插件 applyMapping 參數;在編譯新的 apk 時候,
41      * 我們希望通過保持基准 apk 的 proguard 混淆方式,
42      * 從而減少補丁包的大小。這是強烈推薦的,編譯補丁包時,我們推薦輸入基准 apk 生成的 mapping.txt 文件。
43      */
44     baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
45     /**
46      * 基准包的資源 R.txt 文件路徑, 對應 tinker 插件 applyResourceMapping 參數;在編譯新的apk時候,
47      * 我們希望通基准 apk 的 R.txt 文件來保持 Resource Id 的分配,這樣不僅可以減少補丁包的大小,
48      * 同時也避免由於 Resource Id 改變導致 remote view 異常
49      */
50     baseResourceRFile = "${pathPrefix}/${name}-R.txt"
51     /**
52      *  若有編譯多flavors需求, 可以參照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
53      *  注意: 除非你不同的flavor代碼是不一樣的,不然建議采用zip comment或者文件方式生成渠道信息(相關工具:walle 或者 packer-ng)
54      **/
55 }
56 
57 /**
58  * 用於用戶在代碼中判斷tinkerPatch是否被使能
59  */
60 android {
61     defaultConfig {
62         buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
63     }
64 }
65 /**
66  * 一般來說,我們無需對下面的參數做任何的修改
67  * 對於各參數的詳細介紹請參考:
68  * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
69  */
70 tinkerPatch {
71     ignoreWarning = false
72     useSign = true  //是否需要簽名,打正式包如果這里是true,則要配置簽名,否則會編譯不過去
73     dex {
74         dexMode = "jar"
75         pattern = ["classes*.dex"]
76         loader = []
77     }
78     lib {
79         pattern = ["lib/*/*.so"]
80     }
81 
82     res {
83         pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
84         ignoreChange = []
85         largeModSize = 100
86     }
87     packageConfig {
88     }
89     sevenZip {
90         zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
91 //        path = "/usr/local/bin/7za"
92     }
93     buildConfig {
94         keepDexApply = false
95     }
96 }

簡單的說一下這里的參數配置:如圖

這里還需要注意一個地方,就是appKey,在我們登錄tinker的官網,並且添加一個app版本以后,都會生成一個appkey,我們要把自己的appkey填到上面的配置中,附上一張圖

 

第四步 初始化 TinkerPatch SDK

官方給了我們兩種方式來初始化TinkerPatch SDK,第一種是reflectApplication = true 的情況,另一種reflectApplication = false的情況,今天我們說reflectApplication = true這種情況,另一種情況,大家可以到官網中看看
簡單來說一下這兩種情況的區別啊,當reflectApplication = true這種情況是不需要更改我們項目的Application類,而reflectApplication = false的情況是需要改動Application這個類。

初始化TinkerPatch SDK

 1 package com.example.administrator.tinkerdemotest;
 2 
 3 import android.app.Application;
 4 
 5 import com.tencent.tinker.loader.app.ApplicationLike;
 6 import com.tinkerpatch.sdk.TinkerPatch;
 7 import com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike;
 8 
 9 /**
10  * Created by Administrator on 2017/10/18 0018.
11  *
12  * reflectApplication = true 時
13  */
14 
15 public class tinkerApplication extends Application {
16 
17     private ApplicationLike tinkerApplicationLike;
18     @Override
19     public void onCreate() {
20         super.onCreate();
21 
22         if (BuildConfig.TINKER_ENABLE) {
23             // 我們可以從這里獲得Tinker加載過程的信息
24             tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
25 
26             // 初始化TinkerPatch SDK, 更多配置可參照API章節中的,初始化SDK
27             TinkerPatch.init(tinkerApplicationLike)
28                     .reflectPatchLibrary()
29                     .setPatchRollbackOnScreenOff(true)
30                     .setPatchRestartOnSrceenOff(true);
31 
32             // 每隔3個小時去訪問后台時候有更新,通過handler實現輪訓的效果
33             new FetchPatchHandler().fetchPatchWithInterval(3);
34         }
35     }
36 }

最后在加上FetchPatchHandler這個類

 1 package com.example.administrator.tinkerdemotest;
 2 
 3 
 4 import android.os.Handler;
 5 import android.os.Message;
 6 
 7 import com.tinkerpatch.sdk.TinkerPatch;
 8 
 9 /**
10  * Created by Administrator on 2017/10/18 0018.
11  */
12 
13 public class FetchPatchHandler extends Handler {
14 
15     public static final long HOUR_INTERVAL = 3600 * 1000;
16     private long checkInterval;
17 
18 
19     /**
20      * 通過handler, 達到按照時間間隔輪訓的效果
21      */
22     public void fetchPatchWithInterval(int hour) {
23         //設置TinkerPatch的時間間隔
24         TinkerPatch.with().setFetchPatchIntervalByHours(hour);
25         checkInterval = hour * HOUR_INTERVAL;
26         //立刻嘗試去訪問,檢查是否有更新
27         sendEmptyMessage(0);
28     }
29 
30 
31     @Override
32     public void handleMessage(Message msg) {
33         super.handleMessage(msg);
34         //這里使用false即可
35         TinkerPatch.with().fetchPatchUpdate(false);
36         //每隔一段時間都去訪問后台, 增加10分鍾的buffer時間
37         sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000);
38     }
39 }

 

最后將AndroidManifest.xml中的添加上相應的網絡和SD的權限,還要在application中加上 android:name=".tinkerApplication",附上代碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.example.administrator.tinkerdemotest">
 4 
 5     <uses-permission android:name="android.permission.INTERNET"/>
 6     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 7     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 8 
 9 
10     <application
11         android:allowBackup="true"
12         android:icon="@mipmap/ic_launcher"
13         android:label="@string/app_name"
14         android:roundIcon="@mipmap/ic_launcher_round"
15         android:name=".tinkerApplication"
16         android:supportsRtl="true"
17         android:theme="@style/AppTheme">
18         <activity android:name=".MainActivity">
19             <intent-filter>
20                 <action android:name="android.intent.action.MAIN" />
21 
22                 <category android:name="android.intent.category.LAUNCHER" />
23             </intent-filter>
24         </activity>
25     </application>
26 
27 </manifest>

好了,到這里,集成Tinker的代碼基本上已經完成了,現在運行看看如圖

雙擊assembleDebug就可以運行項目了,結果如圖:

然后在app-->build下有個app-debug.apk的包,我們將這個包安裝到手機上,就相當於是我們本地的應用,在里面我加了一句話,如圖

 

接着我們打有補丁的包,打補丁的包之前,我們的改兩個地方,在tinkerpatch.gradle中找到如圖中的代碼,我們需要將baseInfo,和variantName改成和自己的一直如圖:

改完以后,我們就可以打補丁的包了,如圖

 

 將baseInfo的值改為 右圖中1所對應的值,variantName的值改為右圖2對應的值。改完以后,我們就可以打補丁的包了,如圖

 

 然后在build --> outputs --> tinkerPatch 中就可以找到我們的補丁的包了,如圖

 

 最后將我們的補丁包上傳到tinker官網,就可以使用了,如圖

 

最后附上一條動態的效果圖。注意:一定要把app進程殺死,才會有效果。

 

源碼鏈接:https://github.com/343661629/Tinker

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM