Bugly提供基于Tinker的热更新服务,开发者只需提供基于线上连接的“基线包”打包的补丁包即可实现热更新和更新情况追踪。
首先在project.gradle里配置打包插件
buildscript {
repositories {
jcenter()
}
dependencies {
// tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4
classpath "com.tencent.bugly:tinker-support:lastest.release"
}
}
TinkerSupport包自1.0.3版本后已经自动加入Tinker依赖
然后,开始配置app.gradle。同样的需要配置NDK过滤器
android {
defaultConfig {
ndk {
//设置支持的SO库架构
abiFilters 'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
}
dependencies {
implementation "com.android.support:multidex:1.0.1" // 多dex配置
//注释掉原有bugly的仓库
//implementation 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.3.4
implementation 'com.tencent.bugly:crashreport_upgrade:1.3.5'
// 指定tinker依赖版本(注:应用升级1.3.5版本起,不再内置tinker)
implementation 'com.tencent.tinker:tinker-android-lib:1.9.6'
implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0
}
在app.gradle中引入自定义gradle
apply from: 'tinker-support.gradle'
接下来配置这个自定义gradle
在app.gradle平级目录下创建 tinker-support.gradle文件,内容示例如下
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-0208-15-10-00"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
// 是否启用覆盖tinkerPatch配置功能,默认值false
// 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 编译补丁包时,必需指定基线版本的apk,默认值为空
// 如果为空,则表示不是进行补丁包的编译
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// 对应tinker插件applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// 对应tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
tinkerId = "base-1.0.1"
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
// isProtectedApp = true
// 是否开启反射Application模式
enableProxyApplication = false
// 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件)
supportHotplugComponent = true
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
//oldApk ="${bakPath}/${appName}/app-release.apk"
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []
}
lib {
pattern = ["lib/*/*.so"]
}
res {
pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize = 100
}
packageConfig {
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
//tinkerId = "1.0.1-base"
//applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
}
}
参数具体内容见官方文档
enableProxyApplication 用来标记是否利用反射来重写manifest内的application信息。这里的初始化分两种情况,enableProxyApplication 为真或假时
首先创建一个继承自TinkerApplication的自定义Application,示例代码如下
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.SampleApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
其中父类的构造方法参数说明依次为
- tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
- Application的代理类,所有Application的内容都放在这个代理类内。这个SampleApplication类只需要这个构造方法
- 非必要,loaderClassName Tinker的加载器,使用默认即可
- 非必要,tinkerLoadVerifyFlag 加载dex或者lib是否验证md5,默认为false
然后将manifest中的application指向这个SampleApplication。
自定义的Application代理类需要继承自DefaultApplicationLike,示例代码如下
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) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onCreate() {
super.onCreate();
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true
Bugly.init(getApplication(), "900029763", false);
}
@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);
// 安装tinker
// TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
tinker需要开启MultiDex,建议将tinker的初始化,和Multidex的初始化放入onBaseContextAttached中
该方法无需修改Application,为了降低接入成本,插件会动态替换AndroidMinifest文件中的Application为我们定义好用于反射真实Application的类(需要您接入SDK 1.2.2版本 和 插件版本 1.0.3以上)。
为了稳定性,建议使用前者方法进行配置
该依赖需要的权限如下
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
同升级包,该依赖也需要注册activity和FileProvider
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
provider>
同样的,需要在res包下创建xml资源文件夹,并在其中创建provider_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="beta_external_path" path="Download/"/>
<external-path name="beta_external_files_path" path="Android/data/"/>
paths>
到此,初始化就算完成了
首先,需要打包拥有唯一tinkerId的基准包并安装和上报联网(或上传到bugly全量更新里)
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
。该变量默认为null,当其为null时表示本次打包非补丁打包[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSJUGy4Y-1592182261827)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/Sdfores8.eNxG4GjkF8etlVSKiZVhtpR0KNcnNC*Hyc!/b/dFYBAAAAAAAA&bo=TAGPAQAAAAADB.E!&rf=viewer_4)]
成功后打包如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49N64Dp1-1592182261829)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/pys3TXgNLs3nflLuxHgax7kem2s5Kxh6JK45LSxyWFA!/b/dFUAAAAAAAAA&bo=SwFrAQAAAAADFxI!&rf=viewer_4)]
补丁包是根据指定的基准包来进行编译的,具体步骤如下
"${bakPath}/${baseApkDir}/app-release.apk"
"app-0709-15-23-58"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl714lG8-1592182261832)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/i*ojP2Bi4TedyUKWGzQmpW3kRrW47BVipijGGWG6.kg!/b/dC0BAAAAAAAA&bo=PwGrAAAAAAADB7c!&rf=viewer_4)]
5. 将补丁包上传至bugly后台,后台会根据已连线的版本和全量版本立即进行补丁下发。一个版本同时只能进行一个补丁的下发
至此,等待约几分钟即可观察到热更新下发
bugly官方一次只支持5个渠道版本的下发任务,因此该文章不会去介绍如何使用官方方法进行多渠道打包。这里我们使用walle进行多渠道补丁的打包(必须确保每个渠道版本代码一致)
Walle不会改变dex的结构,只是修改APK Signature Block来添加自定义的渠道信息来生成渠道包。
首先配置app.gradle。添加依赖并导入自定义gradle
apply from: 'multiple-channel.gradle'
dependencies {
implementation 'com.meituan.android.walle:library:1.1.3'
}
自定义gradle同tinker-support.gradle,创建在app目录下。内容如下
apply plugin: 'walle'
walle {
// 指定渠道包的输出路径
apkOutputFolder = new File("${project.buildDir}/outputs/channels");
// 定制渠道包的APK的文件名称
apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
// 渠道配置文件
channelFile = new File("${project.getProjectDir()}/channel")
}
然后在同级目录创建channel文件(txt格式),如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hOAHwk1m-1592182261836)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/8wrEYPerF6evBmre2zxLtc.NLzqnVeCmxztuCerjxP4!/b/dDMBAAAAAAAA&bo=IwJrAgAAAAADB2o!&rf=viewer_4)]
之后用打包指令进行基准包的打包即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dJCLPSw-1592182261839)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/Id3loytIlFlvK4leyQVqndD4zaovoBZ7LuDPwmzAezE!/b/dC8BAAAAAAAA&bo=CAFuAAAAAAADB0U!&rf=viewer_4)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhzLBbFZ-1592182261840)(http://m.qpic.cn/psb?/V10DRhHC2m1DNs/2glYivXQ2fDLD936NreJY71ix3GuH21139MyQCPWeU4!/b/dDEBAAAAAAAA&bo=vwJoAQAAAAADB*Y!&rf=viewer_4)]
之后的补丁操作,就和正常的一样。
这一点非常重要,如果集成bugly但没有将其压入bugly,
bugly将无法识别补丁信息!!!
不过虽然看起来很重要,其实写起来也很简单。只要在Application(或者ApplicationLike类)的onCreate中进行初始化即可
String channel = WalleChannelReader.getChannel(getApplication());
Bugly.setAppChannel(getApplication(), channel);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
Bugly.init(getApplication(), "YOUR_APP_ID", true);
代码内可以通过WalleChannelReader.getChannel(getApplication());
来获取渠道信息
基线包打包:
补丁包打包:
"${bakPath}/${baseApkDir}"
***一定要记住在Application初始化时将渠道信息压入Bugly!!!!***
***一定要记住在Application初始化时将渠道信息压入Bugly!!!!***
***一定要记住在Application初始化时将渠道信息压入Bugly!!!!***