├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── keystore │ ├── debug.keystore │ └── release.keystore ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── jokerwan │ │ │ └── wanhotfixdemo │ │ │ └── ExampleInstrumentedTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── jokerwan │ │ │ │ └── wanhotfixdemo │ │ │ │ ├── BugClass.java │ │ │ │ ├── LoadBugClass.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MyApplication.java │ │ │ │ └── MyApplicationLike.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── test │ │ └── java │ │ └── com │ │ └── jokerwan │ │ └── wanhotfixdemo │ │ └── ExampleUnitTest.java └── tinker-support.gradle ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WanBuglyHotFixDemo 2 | Bugly热更新 demo 3 | 4 | 5 | 6 | 7 | ---------- 8 | 9 | 10 | 对于广大移动开发者而言,App的版本更新升级是再寻常不过的事。但是当你发现你刚发出去的包有紧急Bug需要修复时,你就不淡定了,又要经过繁琐的传统的App版本更新流程,重新发布一个修复Bug的版本,再将Apk上传到各大应用商店,用户需要花费时间去应用商店重新下载安装。如果Bug比较严重,有些用户可能会失去耐心,直接卸载掉App,于是乎,你们的用户就这样流失了。 11 | 12 | 13 | ---------- 14 | 15 | 16 | 传统的更新流程有几个弊端,一是重新发布版本费时费力代价高,二是用户安装需要去应用商店下载成本高,三是不能及时的修复bug,用户体验差。但是H5的出现使使这种情况有了小小的转机,把需要经常变更的业务逻辑以H5的方式独立出来,再嵌入App中。为什么说是小小的转机,因为App中仍有原生的代码,你不能保证原生的代码不会出Bug。于是乎,热更新就应运而生。 17 | 18 | 19 | ---------- 20 | 21 | 22 | 23 | 目前国内的热更新技术有很多,比如阿里的AndFix、Sophix;微信的Tinker;QQ空间的超级补丁;饿了吗的Amigo;美团的Robust等,各有优缺点。实现代码修复主要有两大方案:阿里系的底层替换和腾讯系的类加载。底层替换限制比较多,但能立即加载补丁包实现热修复。类加载需要冷启动才能使热修复生效,但限制少,修复的范围广。有兴趣的同学都可以去了解下。推荐一本阿里的工程师出的热修复书籍《深入探索Android热修复技术原理》,内容详细的讲解了热修复原理。今天我主要教大家如何将腾讯的基于Tinker的热修复框架Bugly集成到项目中,接下来开始讲集成的步骤。 24 | 25 | 26 | ---------- 27 | 28 | 29 | 第一步:添加插件依赖 30 | 工程根目录下“build.gradle”文件中添加: 31 | 32 | ``` 33 | buildscript { 34 | repositories { 35 | jcenter() 36 | } 37 | dependencies { 38 | // tinkersupport插件, 其中latest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4 39 | classpath "com.tencent.bugly:tinker-support: latest.release" 40 | } 41 | } 42 | ``` 43 | 44 | 45 | ---------- 46 | 47 | 48 | 第二步:集成SDK 49 | gradle配置 50 | 在app module的“build.gradle”文件中添加(示例配置): 51 | 52 | ``` 53 | android { 54 | defaultConfig { 55 | } 56 | } 57 | dependencies { 58 | compile "com.android.support:multidex:1.0.1" // 多dex配置 59 | //注释掉原有bugly的仓库 60 | //compile 'com.tencent.bugly:crashreport:latest.release' 61 | //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2 62 | compile 'com.tencent.bugly:crashreport_upgrade:1.3.4' 63 | } 64 | ``` 65 | 66 | 67 | 在app module的“build.gradle”文件中添加: 68 | 69 | ``` 70 | // 依赖插件脚本 71 | apply from: 'tinker-support.gradle' 72 | ``` 73 | 74 | 75 | tinker-support.gradle内容如下所示(示例配置): 76 | 注:您需要在同级目录下创建tinker-support.gradle这个文件哦。 77 | 78 | ``` 79 | apply plugin: 'com.tencent.bugly.tinker-support' 80 | 81 | def bakPath = file("${buildDir}/bakApk/") 82 | 83 | /** 84 | * 此处填写每次构建生成的基准包目录 85 | */ 86 | def baseApkDir = "app-0208-15-10-00" 87 | 88 | /** 89 | * 对于插件各参数的详细解析请参考 90 | */ 91 | tinkerSupport { 92 | 93 | // 开启tinker-support插件,默认值true 94 | enable = true 95 | 96 | // 指定归档目录,默认值当前module的子目录tinker 97 | autoBackupApkDir = "${bakPath}" 98 | 99 | // 是否启用覆盖tinkerPatch配置功能,默认值false 100 | // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch 101 | overrideTinkerPatchConfiguration = true 102 | 103 | // 编译补丁包时,必需指定基线版本的apk,默认值为空 104 | // 如果为空,则表示不是进行补丁包的编译 105 | // @{link tinkerPatch.oldApk } 106 | baseApk = "${bakPath}/${baseApkDir}/app-release.apk" 107 | 108 | // 对应tinker插件applyMapping 109 | baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt" 110 | 111 | // 对应tinker插件applyResourceMapping 112 | baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt" 113 | 114 | // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性 115 | tinkerId = "1.0.0-base" 116 | 117 | // 构建多渠道补丁时使用 118 | // buildAllFlavorsDir = "${bakPath}/${baseApkDir}" 119 | // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持) 120 | // isProtectedApp = true 121 | 122 | // 是否开启反射Application模式 123 | enableProxyApplication = false 124 | supportHotpugComponent = true 125 | } 126 | 127 | /** 128 | * 一般来说,我们无需对下面的参数做任何的修改 129 | * 对于各参数的详细介绍请参考: 130 | * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 131 | */ 132 | tinkerPatch { 133 | //oldApk ="${bakPath}/${appName}/app-release.apk" 134 | ignoreWarning = false 135 | useSign = true 136 | dex { 137 | dexMode = "jar" 138 | pattern = ["classes*.dex"] 139 | loader = [] 140 | } 141 | lib { 142 | pattern = ["lib/*/*.so"] 143 | } 144 | 145 | res { 146 | pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] 147 | ignoreChange = [] 148 | largeModSize = 100 149 | } 150 | 151 | packageConfig { 152 | } 153 | sevenZip { 154 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10" 155 | // path = "/usr/local/bin/7za" 156 | } 157 | buildConfig { 158 | keepDexApply = false 159 | //tinkerId = "1.0.1-base" 160 | //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式 161 | //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配 162 | } 163 | } 164 | ``` 165 | 166 | 167 | ---------- 168 | 169 | 170 | 第三步:初始化SDK 171 | enableProxyApplication = false 的情况 172 | 这是Tinker推荐的接入方式,一定程度上会增加接入成本,但具有更好的兼容性。 173 | 集成Bugly升级SDK之后,我们需要按照以下方式自定义ApplicationLike来实现Application的代码(以下是示例): 174 | 自定义Application 175 | 176 | ``` 177 | public class MyApplication extends TinkerApplication { 178 | public MyApplication() { 179 | super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.MyApplicationLike", 180 | "com.tencent.tinker.loader.TinkerLoader", false); 181 | } 182 | } 183 | ``` 184 | 185 | 别忘了将MyApplication 加入到AndroidMenifest中的application标签下的name属性中。 186 | 187 | 自定义ApplicationLike 188 | 189 | ``` 190 | public class MyApplicationLike extends DefaultApplicationLike { 191 | public MyApplicationLike(Application application, int tinkerFlags, 192 | boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, 193 | long applicationStartMillisTime, Intent tinkerResultIntent) { 194 | super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); 195 | } 196 | 197 | 198 | @Override 199 | public void onCreate() { 200 | super.onCreate(); 201 | // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId 202 | // 调试时,将第三个参数改为true 203 | Bugly.init(getApplication(), "fb2bada5b4", true); 204 | } 205 | 206 | 207 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 208 | @Override 209 | public void onBaseContextAttached(Context base) { 210 | super.onBaseContextAttached(base); 211 | // you must install multiDex whatever tinker is installed! 212 | MultiDex.install(base); 213 | 214 | // 安装tinker 215 | // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法 216 | Beta.installTinker(this); 217 | } 218 | 219 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 220 | public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) { 221 | getApplication().registerActivityLifecycleCallbacks(callbacks); 222 | } 223 | 224 | } 225 | ``` 226 | 227 | 228 | enableProxyApplication = true 时直接在你的Application的onCreate()方法中调用 229 | Bugly.init(getApplication(), "fb2bada5b4", true); 230 | 231 | 232 | ---------- 233 | 234 | 235 | 第四步:AndroidManifest.xml配置 236 | 在AndroidMainfest.xml中进行以下配置: 237 | 权限配置 238 | 239 | ``` 240 | 241 | 242 | 243 | 244 | 245 | 246 | ``` 247 | 248 | 249 | ---------- 250 | 251 | 252 | 第五步:混淆配置 253 | 为了避免混淆SDK,在Proguard混淆文件中增加以下配置: 254 | 255 | ``` 256 | -dontwarn com.tencent.bugly.** 257 | -keep public class com.tencent.bugly.**{*;} 258 | ``` 259 | 260 | 如果你使用了support-v4包,你还需要配置以下混淆规则: 261 | 262 | ``` 263 | 
-keep class android.support.**{*;} 264 | ``` 265 | 266 | 到此为止,已经成功的集成了Bugly框架 267 | 268 | 269 | ---------- 270 | 271 | 272 | 接下来测试热修复 273 | 274 | 275 | ---------- 276 | 277 | 278 | 1、编译基准包 279 | 配置基准包的tinkerId 280 | 281 | ![这里写图片描述](http://img.blog.csdn.net/20171220102925302?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 282 | 283 | 执行assembleRelease编译生成基准包: 284 | 285 | ![这里写图片描述](http://img.blog.csdn.net/20171220103011396?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 286 | 287 | 288 | 这个会在build/bakApk路径下生成每次编译的基准包、混淆配置文件、资源Id文件,如下图所示: 289 | 290 | ![这里写图片描述](http://img.blog.csdn.net/20171220103134493?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 291 | 292 | 293 | 启动上一步生成的app-release.apk,启动后,SDK会自动上报联网数据 294 | 我们每次冷启动都会请求补丁策略,会上报当前版本号和tinkerId,这样我们后台就能将这个唯一的tinkerId对应到一个版本。 295 | 296 | 297 | ---------- 298 | 299 | 300 | 2、对基线版本的bug修复 301 | 未修复前 302 | 303 | ![这里写图片描述](http://img.blog.csdn.net/20171220103222625?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 304 | 305 | 修复后 306 | 307 | ![这里写图片描述](http://img.blog.csdn.net/20171220103311406?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 308 | 309 | ---------- 310 | 311 | 312 | 3、根据基线版本生成补丁包 313 | 修改待修复apk路径、mapping文件路径、resId文件路径和tinkerId 314 | 315 | ![这里写图片描述](http://img.blog.csdn.net/20171220103432927?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 316 | 317 | 执行构建补丁包的task 318 | 319 | ![这里写图片描述](http://img.blog.csdn.net/20171220103840758?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 320 | 321 | 生成的补丁包在build/outputs/patch目录下: 322 | 323 | ![这里写图片描述](http://img.blog.csdn.net/20171220103901422?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 324 | 325 | ---------- 326 | 327 | 328 | 4、上传补丁包到平台 329 | 上传补丁包到平台并下发编辑规则 330 | 331 | ![这里写图片描述](http://img.blog.csdn.net/20171220103919737?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 332 | 333 | ![这里写图片描述](http://img.blog.csdn.net/20171220104021882?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 334 | 335 | 选择文件后会自动识别目标版本,若出现“未匹配到可用补丁的App版本”,如果你的基线版本没有上报过联网,基于这个版本生成的补丁包就无法匹配到,启动之前生成的app-release.apk,启动后,SDK会自动上报联网数据 336 | 337 | 338 | ---------- 339 | 340 | 341 | 5、测试补丁应用效果 342 | 启动基线版本App,点击显示文本时崩溃,这是我们前面造的一个空指针异常,重新启动基线版本App,等待一两分钟后,会开始自动下载补丁包,下载成功之后会立即合成补丁,我配置了Beta.canNotifyUserRestart = true ,所以会弹出对话框,提醒用户重启更新应用。由于Tinker需要再次冷启动才能使补丁生效,点击重启应用后会退出,再重新启动apk,点击显示文本,文本内容显示为修复后的内容,之前的的空指针异常就被成功修复了。 343 | 344 | ![这里写图片描述](http://img.blog.csdn.net/20171220104040143?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 345 | 346 | ![这里写图片描述](http://img.blog.csdn.net/20171220104111947?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 347 | 348 | ![这里写图片描述](http://img.blog.csdn.net/20171220104127714?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXNKb2tlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 349 | 350 | ---------- 351 | 352 | 353 | Bugly还支持全量升级、异常上报、运行统计,详情可以看Bugly官方文档:https://bugly.qq.com/docs/ 354 | 355 | 本文demo: https://github.com/isJoker/WanBuglyHotFixDemo 356 | 357 | 358 | 359 | 360 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | // recommend 7 | dexOptions { 8 | jumboMode = true 9 | } 10 | 11 | lintOptions { 12 | checkReleaseBuilds false 13 | abortOnError false 14 | } 15 | 16 | // 签名配置 17 | signingConfigs { 18 | release { 19 | try { 20 | storeFile file("./keystore/release.keystore") 21 | storePassword "testres" 22 | keyAlias "testres" 23 | keyPassword "testres" 24 | } catch (ex) { 25 | throw new InvalidUserDataException(ex.toString()) 26 | } 27 | } 28 | 29 | debug { 30 | storeFile file("./keystore/debug.keystore") 31 | } 32 | } 33 | 34 | defaultConfig { 35 | applicationId "com.jokerwan.wanhotfixdemo" 36 | minSdkVersion 15 37 | targetSdkVersion 26 38 | versionCode 4 39 | versionName '4.0' 40 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 41 | 42 | // 开启multidex 43 | multiDexEnabled true 44 | 45 | } 46 | 47 | // 构建类型 48 | buildTypes { 49 | release { 50 | minifyEnabled true 51 | signingConfig signingConfigs.release 52 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 53 | } 54 | debug { 55 | debuggable true 56 | minifyEnabled false 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | dependencies { 63 | implementation fileTree(dir: 'libs', include: ['*.jar']) 64 | implementation 'com.android.support:appcompat-v7:26.1.0' 65 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 66 | testImplementation 'junit:junit:4.12' 67 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 68 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 69 | 70 | compile "com.android.support:multidex:1.0.1" // 多dex配置 71 | 72 | //注释掉原有bugly的仓库 73 | //compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2 74 | compile 'com.tencent.bugly:crashreport_upgrade:1.3.4'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0 75 | 76 | } 77 | 78 | 79 | apply from: 'tinker-support.gradle' 80 | -------------------------------------------------------------------------------- /app/keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isJoker/WanBuglyHotFixDemo/0d4703f6420cf7b9d9f55164c1fbe6605c421090/app/keystore/debug.keystore -------------------------------------------------------------------------------- /app/keystore/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isJoker/WanBuglyHotFixDemo/0d4703f6420cf7b9d9f55164c1fbe6605c421090/app/keystore/release.keystore -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontwarn com.tencent.bugly.** 24 | -keep public class com.tencent.bugly.**{*;} 25 | -keep class android.support.**{*;} 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jokerwan/wanhotfixdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jokerwan.wanhotfixdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/jokerwan/wanhotfixdemo/BugClass.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | /** 4 | * Created by JokerWan on 2017/12/18. 5 | * WeChat: wjc398556712 6 | * Function: 7 | */ 8 | 9 | public class BugClass { 10 | 11 | public String bug() { 12 | // 这段代码会报空指针异常 13 | // String str = null; 14 | // Log.e("BugClass", "get string length:" + str.length()); 15 | // return "This is a bug class"; 16 | 17 | return "This is a fixed bug class"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/jokerwan/wanhotfixdemo/LoadBugClass.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | /** 4 | * Created by JokerWan on 2017/12/18. 5 | * WeChat: wjc398556712 6 | * Function: 7 | */ 8 | 9 | public class LoadBugClass { 10 | /** 11 | * 获取bug字符串. 12 | * 13 | * @return 返回bug字符串 14 | */ 15 | public static String getBugString() { 16 | BugClass bugClass = new BugClass(); 17 | return bugClass.bug(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/jokerwan/wanhotfixdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.TextView; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | TextView text; 12 | Button button; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | 19 | text = findViewById(R.id.text); 20 | button = findViewById(R.id.button); 21 | button.setOnClickListener(new View.OnClickListener() { 22 | @Override 23 | public void onClick(View v) { 24 | text.setText(LoadBugClass.getBugString()); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/jokerwan/wanhotfixdemo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | import com.tencent.tinker.loader.app.TinkerApplication; 4 | import com.tencent.tinker.loader.shareutil.ShareConstants; 5 | 6 | /** 7 | * Created by JokerWan on 2017/12/18. 8 | * WeChat: wjc398556712 9 | * Function: 10 | */ 11 | 12 | public class MyApplication extends TinkerApplication { 13 | 14 | public MyApplication() { 15 | super(ShareConstants.TINKER_ENABLE_ALL, "com.jokerwan.wanhotfixdemo.MyApplicationLike", 16 | "com.tencent.tinker.loader.TinkerLoader", false); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jokerwan/wanhotfixdemo/MyApplicationLike.java: -------------------------------------------------------------------------------- 1 | package com.jokerwan.wanhotfixdemo; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.support.multidex.MultiDex; 9 | import android.widget.Toast; 10 | 11 | import com.tencent.bugly.Bugly; 12 | import com.tencent.bugly.beta.Beta; 13 | import com.tencent.bugly.beta.interfaces.BetaPatchListener; 14 | import com.tencent.tinker.loader.app.DefaultApplicationLike; 15 | 16 | import java.util.Locale; 17 | 18 | /** 19 | * Created by JokerWan on 2017/12/18. 20 | * WeChat: wjc398556712 21 | * Function: MyApplication的代理类,所有之前在MyApplication的onCreate()里初始化代码现在只要在 22 | * MyApplicationLike里面的onCreate()里面初始化,插件会动态替换AndroidMinifest文件中的Application 23 | * 为我们定义好用于反射真实Application的类 24 | */ 25 | 26 | public class MyApplicationLike extends DefaultApplicationLike { 27 | 28 | public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { 29 | super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); 30 | } 31 | 32 | @Override 33 | public void onCreate() { 34 | super.onCreate(); 35 | // 设置是否开启热更新能力,默认为true 36 | Beta.enableHotfix = true; 37 | // 设置是否自动下载补丁,默认为true 38 | Beta.canAutoDownloadPatch = true; 39 | // 设置是否自动合成补丁,默认为true 40 | Beta.canAutoPatch = true; 41 | // 设置是否提示用户重启,默认为false 42 | Beta.canNotifyUserRestart = true; 43 | // 补丁回调接口 44 | Beta.betaPatchListener = new BetaPatchListener() { 45 | @Override 46 | public void onPatchReceived(String patchFile) { 47 | Toast.makeText(getApplication(), "补丁下载地址" + patchFile, Toast.LENGTH_SHORT).show(); 48 | } 49 | 50 | @Override 51 | public void onDownloadReceived(long savedLength, long totalLength) { 52 | Toast.makeText(getApplication(), 53 | String.format(Locale.getDefault(), "%s %d%%", 54 | Beta.strNotificationDownloading, 55 | (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)), 56 | Toast.LENGTH_SHORT).show(); 57 | } 58 | 59 | @Override 60 | public void onDownloadSuccess(String msg) { 61 | Toast.makeText(getApplication(), "补丁下载成功", Toast.LENGTH_SHORT).show(); 62 | } 63 | 64 | @Override 65 | public void onDownloadFailure(String msg) { 66 | Toast.makeText(getApplication(), "补丁下载失败", Toast.LENGTH_SHORT).show(); 67 | 68 | } 69 | 70 | @Override 71 | public void onApplySuccess(String msg) { 72 | Toast.makeText(getApplication(), "补丁应用成功", Toast.LENGTH_SHORT).show(); 73 | } 74 | 75 | @Override 76 | public void onApplyFailure(String msg) { 77 | Toast.makeText(getApplication(), "补丁应用失败", Toast.LENGTH_SHORT).show(); 78 | } 79 | 80 | @Override 81 | public void onPatchRollback() { 82 | 83 | } 84 | }; 85 | 86 | // 设置开发设备,默认为false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备 87 | Bugly.setIsDevelopmentDevice(getApplication(), true); 88 | // 多渠道需求塞入 89 | // String channel = WalleChannelReader.getChannel(getApplication()); 90 | // Bugly.setAppChannel(getApplication(), channel); 91 | // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId 92 | // 调试时,将第三个参数改为true 93 | Bugly.init(getApplication(), "fb2bada5b4", true); 94 | } 95 | 96 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 97 | @Override 98 | public void onBaseContextAttached(Context base) { 99 | super.onBaseContextAttached(base); 100 | // you must install multiDex whatever tinker is installed! 101 | MultiDex.install(base); 102 | 103 | // 安装tinker 104 | // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法 105 | Beta.installTinker(this); 106 | } 107 | 108 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 109 | public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) { 110 | getApplication().registerActivityLifecycleCallbacks(callbacks); 111 | } 112 | 113 | @Override 114 | public void onTerminate() { 115 | super.onTerminate(); 116 | Beta.unInit(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 |