Android Integrated Bugly Hot Update
@ Author GQ 18 December 2017
Recently, a new project will be opened. The leader said that he would not let the old app update, tens of megabytes at a time.
So integrated heat is the trend.~
By the way, use Google Dad Kt language!
Information check
Mainstream hot frameworks include:
Ali AndFix
Tencent Bugly
-
Robust, American Corps
Tinker is the official Android hotpatch solution for Wechat (recommended)
It supports dynamic downloading of code, So libraries and resources, enabling applications to update without reinstallation. Of course, you can also use Tinker to update your plug-in.
Frame comparison
Data from Guo Shen's subscription number: https://mp.weixin.qq.com/s/lMcCSKG54xvvCo9ZGI8ZlA
Select Bulgy
Create a new project
First run normally
Integrated Bugly
Be careful not to rush to build projects. When things are not loaded properly, they will report errors. Modify the configuration files in order first.
Before build ing, remember to register for Advanced Official Website, get appId and change it to project
In the build.gradle of the project
buildscript {
ext.kotlin_version = '1.1.51'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
//Current version, you can use latest.release to introduce the latest version
classpath "com.tencent.bugly:tinker-support:1.1.1"
}
}
In build.gradle under app
Note: You have to write the packaged jks in build.gradle. Don't fool around with moths. Because of this crap, I got stuck here for an hour.
The imported packages are all imported. Remember to import one more v4 package, which will be used.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: 'tinker-support.gradle'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion "27.0.0"
defaultConfig {
applicationId rootProject.ext.android.applicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
// Open multidex
multiDexEnabled true
}
dexOptions {
// Supporting large-scale engineering model
jumboMode = true
}
signingConfigs {
release {
keyAlias 'oakey'
keyPassword '123456'
//Write on your own path
storeFile file('/Users/GUOQI/Android/workplace_git/jks/test.jks')
//Set your own password
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:25.4.0'
//V4 package
implementation 'com.android.support:support-v4:25.4.0'
implementation 'com.android.support:design:25.4.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-vector-drawable:25.4.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
// Multi dex configuration
implementation 'com.android.support:multidex:1.0.2'
// Remote Warehouse Integration (Recommendation)
implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'
}
- It uses something like rootProject.ext.android.xxx. Don't worry about it just for sharing.
Create a new config.gradle file under the project and directory
This gradle file is used to store the public items just mentioned.
ext {
android = [compileSdkVersion: 25,
applicationId : "com.example.test",//Write your own name
minSdkVersion : 19,
targetSdkVersion : 21,
versionCode : 1,
versionName : "1.0"]
}
Back in build.gradle, which was generated by default for the project
apply from: 'config.gradle'//Introduce the config.gradle file that I just wrote, and add this line.
buildscript {
...
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' } //Introduce this warehouse
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
The most important new file, tinker-support.gradle
Note that in the app directory, don't build the wrong location, resulting in the error can not be found.
Then open editing the tinker-support.gradle content
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* Fill in the base package directory generated for each build here
*
* You just need to change myTinkeId here in the future.
*/
def baseApkDir = "app-1218-12-53-16" //When packing, the following numbers are automatically generated according to the timestamp, which can be ignored for the time being.
//Def myTinkerId = base-"+ rootProject. ext. android. versionName // / Used to generate benchmark packages (without modification)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // Used to generate patch packs (patch - ${version Name}. X. x is the best one to modify every time patch packs are generated)
/**
* For detailed analysis of plug-in parameters, please refer to
*
* enable Attribute true
* enableProxyApplication Attribute false
* The above two attributes should not be mistakenly altered by other different information on the Internet
*/
tinkerSupport {
// Open the tinker-support plug-in with the default value true
enable = true
// Whether the reinforcement mode is enabled or not, default is false.(tinker-spport 1.0.7 support)
// isProtectedApp = true
// Whether to turn on Reflective Application mode
enableProxyApplication = false
// Do you support the addition of non-export Activeness (Note: To modify the Android Manifest file, set true)
supportHotplugComponent = true
// Specify the archive directory, default value of current module subdirectory tinker
autoBackupApkDir = "${bakPath}"
// Whether to enable overlay tinkerPatch configuration function, default value false
// The tinkerPatch configuration does not take effect after opening, that is, there is no need to add tinkerPatch
overrideTinkerPatchConfiguration = true
// When compiling patch packs, you must specify the baseline version of the apk, which defaults to null
// If null, it means that the patch package is not compiled
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// applyMapping corresponding to tinker plug-in
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// ApplyResource Mapping for the tinker plug-in
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// Constructing benchmark and patch packs specifies different tinkerId and must guarantee uniqueness
tinkerId = "${myTinkerId}"
// Use when building multi-channel patches
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
}
/**
* Generally speaking, we do not need to make any changes to the following parameters
* For a detailed description of each parameter, please refer to:
* 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"// Optional. Set up the mapping file. It is recommended that the proguard confusion of the old apk be maintained.
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt"// Optional, set the R.txt file, and keep ResId allocation through the old apk file
}
}
Create a new MyApplication Life to inherit Default Application Like
Initialize everything in order, direct cv
class MyApplicationLike(application: Application, tinkerFlags: Int,
tinkerLoadVerifyFlag: Boolean, applicationStartElapsedTime: Long,
applicationStartMillisTime: Long, tinkerResultIntent: Intent) : DefaultApplicationLike(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent) {
private var mContext: Application? = null
override fun onCreate() {
super.onCreate()
mContext = application
//Configure Tinker
configTinker()
//Initialize the image loading, initialize the xxx itself used to write here
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override fun onBaseContextAttached(base: Context?) {
super.onBaseContextAttached(base)
// you must install multiDex whatever tinker is installed!
MultiDex.install(base)
// Install tinker
Beta.installTinker(this)
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
fun registerActivityLifecycleCallback(callbacks: Application.ActivityLifecycleCallbacks) {
application.registerActivityLifecycleCallbacks(callbacks)
}
override fun onTerminate() {
super.onTerminate()
Beta.unInit()
}
companion object {
val TAG = "Tinker.MyApplicationLike"
}
private fun configTinker() {
// Set whether to turn on hot update capability by default to true
Beta.enableHotfix = true
// Set whether patches are automatically downloaded by default to true
Beta.canAutoDownloadPatch = true
// Sets whether patches are automatically synthesized by default to true
Beta.canAutoPatch = true
// Set whether to prompt the user to restart, default to false
Beta.canNotifyUserRestart = true
// Patch callback interface
Beta.betaPatchListener = object : BetaPatchListener {
override fun onPatchReceived(patchFile: String) {
Toast.makeText(mContext, "Patch Download Address" + patchFile, Toast.LENGTH_SHORT).show()
}
override fun onDownloadReceived(savedLength: Long, totalLength: Long) {
Toast.makeText(mContext,
String.format(Locale.getDefault(), "%s %d%%",
Beta.strNotificationDownloading,
(if (totalLength == 0L) 0 else savedLength * 100 / totalLength).toInt()),
Toast.LENGTH_SHORT).show()
}
override fun onDownloadSuccess(msg: String) {
Toast.makeText(mContext, "Patch Download Successful", Toast.LENGTH_SHORT).show()
}
override fun onDownloadFailure(msg: String) {
Toast.makeText(mContext, "Patch download failed", Toast.LENGTH_SHORT).show()
}
override fun onApplySuccess(msg: String) {
Toast.makeText(mContext, "Successful patch application", Toast.LENGTH_SHORT).show()
}
override fun onApplyFailure(msg: String) {
Toast.makeText(mContext, "Patch application failed", Toast.LENGTH_SHORT).show()
}
override fun onPatchRollback() {
}
}
// Set up the development device, default is false, upload patch if the download scope is specified as "development device", you need to call this interface to identify the development device.
Bugly.setIsDevelopmentDevice(mContext, false)
// Multi-channel demand congestion
// String channel = WalleChannelReader.getChannel(getApplication());
// Bugly.setAppChannel(getApplication(), channel);
// SDK initialization is implemented here, and appId is replaced by your appId applied for on the Bugly platform.
Bugly.init(mContext, AppId.APP_ID_BUGLY, true)
}
}
Test Application for a new project inherits from Tinker Application
Note that the second parameter fills in its corresponding MyApplication Life package name. Note when cv
class TestApplication : TinkerApplication(ShareConstants.TINKER_ENABLE_ALL, "com.xxx.MyApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false) {
//There's nothing to write inside.
}
Modify Manifest's name attribute
Write your new empty TestApplication
<application
android:name=".example.TestApplication"
...
- Then Manifest must add the following permissions and configurations
<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"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
//Note that I didn't write the application label and put it inside the application itself.
<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>
Execute build waiting for boot build
After the build is successful, click the right column gradle to execute assembleRelease
Waiting for the release package to be built successfully
You need to use the packaged jks configuration mentioned at the beginning, so you must write it in build.gradle.
After success, you can see it in the build under app
- The newly generated file of bakApk represents the benchmark apk
- app-release.apk is already an official package for signature generation and can be installed directly into mobile phones or simulators.
Making Patch Documents
Change textView on any page of app
Prove that this is a class or resource file that needs to be updated
Open the tinker-support.gradle file
Annotate the line of base, release the line of patch, generate patches many times and remember to change the number of. 0.1.
def baseApkDir = "app-1218-12-53-16"
//Def myTinkerId = base-"+ rootProject. ext. android. versionName // / Used to generate benchmark packages (without modification)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // Used to generate patch packs (patch - ${version Name}. X. x is the best one to modify every time patch packs are generated)
Then open the right sidebar build.gradle and execute build Tinker Patch Release
Waiting for the execution to complete successfully
Packed patch file in app/build/output/patch, the focus is xx7zip.apk, this is the patch file
Open Bugly's official website
Official website https://bugly.qq.com/v2/index
Registration - > Login - > Create Application - > Get AppId - > Modify to Project
Upload the above 7zip.apk to the project - > Application Upgrade - > Hot Update - > Publish New Upgrade
- Show that it's in the process of posting, and you need to wait a little longer.
- The apk installed on the phone or simulator before opening it again will prompt for hot updates and restart the application