Android 腾讯Bugly的应用升级&热更新

2023-11-09

经过去年的九月份至现在,发现自己很久没有写过比较好的文章了。今天就趁着通宵的劲,写一下对腾讯Bugly的应用升级&热更新的理解,希望对新手有所帮助,有兴趣的可以了解下,没兴趣的也可以看完之后吐槽我。。。

Bugly 文档中心:https://bugly.qq.com/docs/

官方文档写的还是蛮详细的,不过有些地方还是自己绕了很多弯,请教了公司大神才弄清楚的。

*本篇文章已授权 玩Android 独家发布

项目配置的话,本人选择直接配置应用升级&热更新。

一、配置gradle跟multiDexKeep

1、project的配置:build.gradle:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.3'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files

        // tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4
        classpath "com.tencent.bugly:tinker-support:1.1.1"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

2、module的配置:build.gradle

apply plugin: 'com.android.application'
// 热更新依赖插件脚本,如果不想要热更新可以注释掉
apply from: 'tinker-support.gradle'
//全局配置
apply from: "../config.gradle"

android {
    compileSdkVersion compile_sdk_version
    buildToolsVersion build_tools_version
    defaultConfig {
        applicationId "com.example.administrator.myapplication"
        minSdkVersion min_sdk_version
        targetSdkVersion target_sdk_version
        versionCode version_code
        versionName version_name
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
        multiDexKeepProguard file('multiDexKeep.pro')
        ndk {
            // 设置支持的SO库架构
            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'arm64-v8a' //, 'x86_64'
        }
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath true
            }
        }
    }

    signingConfigs {
        debug {
            keyAlias 'debug'
            keyPassword '123456'
            storeFile file('../debug.jks')
            storePassword '123456'
            v1SigningEnabled true
            v2SigningEnabled false
        }

        release {
            v1SigningEnabled true
            v2SigningEnabled false
            keyAlias 'key0'
            keyPassword '123456'
            storeFile file('../release.jks')
            storePassword '123456'
        }
    }


    buildTypes {
        release {
            debuggable log_debug
            shrinkResources false
            minifyEnabled false
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
            buildConfigField("boolean", "LOG_DEBUG", String.valueOf(log_debug))
        }
        debug {
            debuggable true
            jniDebuggable true
            signingConfig signingConfigs.debug
            minifyEnabled false
            zipAlignEnabled false
            buildConfigField("boolean", "LOG_DEBUG", String.valueOf(log_debug))
        }
    }

    dexOptions {
//        incremental true
//        maxProcessCount 2
        javaMaxHeapSize "4g"
        jumboMode = true
    }

    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    repositories {
        flatDir {
            dirs 'libs'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support.constraint:constraint-layout:1.1.2'
    testCompile 'junit:junit:4.12'
    androidTestCompile 'com.android.support.test:runner:1.0.2'
    androidTestCompile "com.android.support:support-annotations:$support_library_version"
    compile "com.android.support:appcompat-v7:$support_library_version"
    compile "com.android.support:support-v4:$support_library_version"
    compile "com.android.support:multidex:1.0.3" // 多dex配置
    //------------------Bugly---------------------------------------------------------
    //    compile 'com.tencent.bugly:crashreport_upgrade:latest.release'
    //    compile 'com.tencent.bugly:nativecrashreport:latest.release'
    compile 'com.tencent.bugly:crashreport_upgrade:1.3.4'
    //升级SDK已经集成crash上报功能
    compile 'com.tencent.bugly:nativecrashreport:3.3.1'
    //------------------Bugly---------------------------------------------------------
}

3、热更新配置:tinker-support.gradle

注:您需要在同级目录下创建tinker-support.gradle这个文件

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")

/**
 * 此处填写每次构建生成的基准包目录
 */
def baseApkDir = "app-0710-15-48-49"

/**
 * 对于插件各参数的详细解析请参考
 */
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"
//    baseApk = "${bakPath}/${baseApkDir}/app-debug.apk"

    // 对应tinker插件applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
//    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-debug-mapping.txt"

    // 对应tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
    //  baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-debug-R.txt"

    // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
    // **基准包这里最好是改成base-versionname,patch包就改成patch-versionname,每个版本的都不一样**
    tinkerId = "patch-1.0.1"

    // 构建多渠道补丁时使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
    //tinker 1.7.8版本及以上版本
//    isProtectedApp = true

    // 是否开启反射Application模式
    enableProxyApplication = false

}

/**
 * 一般来说,我们无需对下面的参数做任何的修改
 * 对于各参数的详细介绍请参考:
 * 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的分配
    }
}

基准包一般都是以app-release.apk正式版,如果想要用debug版的话,上面的baseApk、baseApkProguardMapping、baseApkResourceMapping切换下。

4、全部配置:config.gradle

ext.target_sdk_version = 27
ext.min_sdk_version = 19
ext.compile_sdk_version = 27
ext.build_tools_version = '27.0.3'
ext.support_library_version = '27.1.1'

//TODO 调试模式的开启关闭
ext.log_debug = false
ext.version_code = 1
ext.version_name = "1.0.1"

5、Bugly MultiDex注意事项,把Bugly的类放到主Dex,即写在multiDexKeep.pro

-keep class com.tencent.bugly.** {*;}
-keep class com.example.administrator.myapplication.AppLike{*;}

这里你的AppLike放在那里,对应修改其路径

附上项目目录结构图,在说明相对应的配置
这里写图片描述这里写图片描述

二、XML的配置:

1、权限跟activity和provider:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.administrator.myapplication">

    <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" />

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Bugly -->
        <activity
            android:name="com.tencent.bugly.beta.ui.BetaActivity"
            android:configChanges="keyboardHidden|orientation|screenSize|locale"
            android:theme="@android:style/Theme.Translucent" />
        <!-- Bugly -->

        <!-- 用于兼容7.0以上设备 -->
        <provider
            android:name=".MyFileProvider"
            android:authorities="${applicationId}.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="name,authorities,exported,grantUriPermissions">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"
                tools:replace="name,resource" />
        </provider>

    </application>

</manifest>

2、在res目录新建xml文件夹,创建provider_paths.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>

    <!--Bugly:这里配置的两个外部存储路径是升级SDK下载的文件可能存在的路径,一定要按照上面格式配置,不然可能会出现错误。-->
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path
        name="beta_external_path"
        path="Download/" />
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path
        name="beta_external_files_path"
        path="Android/data/" />

    <root-path
        name="root"
        path="root/" />
    <!--代表设备的根目录new File("/");-->
    <files-path
        name="my_image"
        path="images/" />
    <!--代表context.getFilesDir()-->
    <cache-path
        name="cache"
        path="caches/" />
    <!--代表context.getCacheDir()-->
    <external-path
        name="my_dir"
        path="dirs/" />
    <!--代表Environment.getExternalStorageDirectory()-->
    <!--<external-files-path/>-->
    <!--代表context.getExternalFilesDirs()-->
    <!--<external-cache-path/>-->
    <!--代表getExternalCacheDirs()-->
</paths>

三、代码模块

1、App.java

public class App extends TinkerApplication {
    public static App mInstance;

    public App() {
	    //同样修改成自己AppLike的路径
        super(ShareConstants.TINKER_ENABLE_ALL, "com.example.administrator.myapplication.AppLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }

    public static App getInstance() {
        return mInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }
}

2、AppLike.java

public class AppLike extends DefaultApplicationLike {

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

    public AppLike(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();
        //设置自定义升级对话框UI布局
        Beta.upgradeDialogLayoutId = R.layout.upgrade_dialog;
        //添加可显示弹窗的Activity
        Beta.canShowUpgradeActs.add(MainActivity.class);
        //TODO 初始化,AppID替换成平台上项目中产品信息的AppID
		Bugly.init(getApplication(), "AppID", BuildConfig.LOG_DEBUG);
        //  Q:你们是怎么定义开发设备的?
        //  BitmapQrCodeDecoder:我们会提供接口Bugly.setIsDevelopmentDevice(getApplicationContext(), true);,
        //  我们后台就会将你当前设备识别为开发设备,如果设置为false则非开发设备,我们会根据这个配置进行策略控制。
        Bugly.setIsDevelopmentDevice(getApplication(), BuildConfig.LOG_DEBUG);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        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);
    }

}

3、这里要注意一下,AndroidManifest.xml中的FileProvider类是在support-v4包中的,检查你的工程是否引入该类库。

项目中新建一个MyFileProvider.java

public class MyFileProvider extends FileProvider {
}

四、打包及发布的流程(重中之重)

1、如果你还不知道怎么打包的话,可以看下图。如果知道的话可以看下如何打补丁包,双击一下等待数秒就打包好了。
这里写图片描述

2、打完包的apk跟补丁包文件目录
这里写图片描述

bakApk中app-**目录下的apk为基准包(可以用于官网中全量更新:发布新版本)
patch目录下的patch_signed_7zip.apk为补丁包(可以用于官网热更新:发布新补丁)

3、打包需要修改配置如下

  • config.gradle

ext.log_debug = false
ext.version_code = 1
ext.version_name = “1.0.1”

  • tinker-support.gradle
    tinkerId可以不填,不过要配置自动生成tinkerId(autoGenerateTinkerId=true)

def baseApkDir = “app-0710-18-39-06”
tinkerId = “patch-1.0.1”

项目版本打包,首先要确定如下配置

ext.log_debug = false //调试模式的开启关闭,正式包要改成false
ext.version_code = 1
ext.version_name = “1.0.1”
tinkerId = “patch-1.0.1” // tinkerId=patch-version_name

点击上图中的assembleRelease进行打正式包,生成app-0710-18-39-06下的app-release.apk(这个就是正式包,也算是基准包),然后把apk上传到bugly,如下图
这里写图片描述

图中第三步,可以修改更新提示框,例如:AppLike.class中的Beta.upgradeDialogLayoutId = R.layout.upgrade_dialog;
只需在项目中新建一个布局,不过那些按钮等都要设置对应的tag,才会起作用,具体看Bugly Android 应用升级 SDK 高级配置

特别要注意的是:打包成功之后,最好把app-0710-18-39-06文件夹保存起来。为什么?

比如现在版本上线,突然出现一个重大bug,这时候可以选择用热更新进行修复。你也不用重新多弄个版本,用户也不用去重新下载apk。

然后项目打补丁包,需要如下配置

def baseApkDir = “app-0710-18-39-06”

这里的app-0710-18-39-06就是刚刚说要保存的基准包对应的文件名

弄完之后就上传补丁包,如下图
这里写图片描述

一般确认无误的情况,下发范围都是选择全部设备。

如果你想选择开发设备进行补丁包测试的话,那么又要修改如下配置,首先修改 ext.log_debug = true。

打包app-0710-18-39-06(包1):ext.log_debug = false(作为全部设备使用)
打包app-0710-18-40-40(包2):ext.log_debug = true(作为开发设备使用)

这两个app-××× 都是相继打包出来的,也就是上面说的保存包1的时候,顺便改为true打包出包2,两个都一起保存起来,然后才有以下补丁包的说法。

具体流程如如下,画得有点丑,凑合着看呗(宝图哦)

这里写图片描述


最后经过多轮测试,发现打补丁时,baseApkDir必须是认准基准包的。而tinkerid可以不用是patch-versionname,但必须不能与之前的tinkerid重复。


文章涉及到的Demo,已上传至github


项目遇到几个坑如下:

一、这里遇到一个坑,就是发布app之后,被“强制停止了”,然后重启提示“启动策略失败”。

原来是下发上限跟激活上限的问题,修改成100w就ok了
这里写图片描述
二、AS更新到3.4(当前用的版本)后找不到build下找不到assembleRelease,其实移动到了other->assembleRelease

三、点击更新老是提示“检查版本更新失败”

调用Beta.checkUpgrade()就有这样的提示,原来是android bugly不支持Android P,官方文档上最高是适配到8.x。

Android 9.0上会报联网失败,需要在项目中的res->xml文件中新建network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">android.bugly.qq.com</domain>
    </domain-config>
</network-security-config>

四、bugly热更新已激活,但是没起作用

目前了解好像是部分手机不支持,建议换个手机试试,实在不行可以弄个模拟器跑跑看


以上都是本人的一些见解,如有误点请帮忙指正,谢谢!!

看到这里的话,累了吧,分享个小姐姐给你认识

这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 腾讯Bugly的应用升级&热更新 的相关文章

随机推荐

  • xcode4的环境变量,Build Settings参数,workspace及联编设置

    一 xcode4中的环境变量 BUILT PRODUCTS DIR build成功后的 最终产品路径 可以在Build Settings参数的Per configuration Build Products Path项里设置 TARGET
  • 英语台词--冰与火之歌

    Your roof your rules 你的地盘 你做主
  • C++基础——常引用与类型转换详解

    通过前两篇对引用的讲解 想必大家都对引用有了较为深刻的理解 没看过的小伙伴可以去看看 C 基础 引用讲解1 C 基础 引用讲解2 目录 一 常引用 1 权限的平移 2 权限的放大 3 权限的缩小 二 引用的类型转换 一 常引用 这次我来说一
  • 史上最全的Python兼职接单挣钱教程,十分详细(附基础教程)

    学python编程能挣钱吗 怎么挣钱 答案是可以的 有两点我都赚到钱的方法 接私活和自媒体 一 赚钱第一种方式 接私活 刚学会python那会 就有认识的朋友介绍做一个网站的私活 当时接单赚了4K 仅代表本人个人收益 后又自己接过开发网站后
  • A2W和W2A 很好的多字节和宽字节字符串的转换宏

    分享一下我老师大神的人工智能教程 零基础 通俗易懂 http blog csdn net jiangjunshow 也欢迎大家转载本篇文章 分享知识 造福人民 实现我们中华民族伟大复兴 作者 朱金灿 来源 http blog csdn ne
  • github CPlusPlusThings 基础学习笔记

    来源 Light City CPlusPlusThings C 那些事 github com 目录 const static this inline sizeof 函数指针 纯虚函数和抽象类 vptr vtable virtual vola
  • Dell R410服务器查看系统raid级别

    注意 raid 如果有问题 会导致机器网络自动断开连接 连不上机器 1 查看inux 怎么查看raid做的 是几 软件raid 只能通过Linux系统本身来查看 cat proc mdstat 可以看到raid级别 状态等信息 硬件raid
  • 前端JSON数据传值到后端接收方式

    前端发送的数据 前端JS请求 1 demo为JSON格式数据 2 let para 3 dataJ JSON stringfiy demo 4 5 这一段是Vue封装的方法 本质就是一条url 6 this http post webRoo
  • js去除url中的localtion.search部分

    localtion search是url中拼接的参数部分 js去除url中的localtion search部分包括 部分 if location search var old url window location href var ne
  • Java算法结构---------线性表

    线性表相关介绍 线性表是一种最常用 最简单的线性结构 线性表的主要操作特定是 可以在任意位置上插入一个数据元素和删除一个数据元素 线性表可以用顺序存储结构和链式存储结构实现 用顺序存储结构实现的线性表称为顺序表 用链式存储结构实现的线性表称
  • node.js(第七章)登录鉴权的方式一Cookie&Session

    1 Cookie Session HTTP 无状态 我们知道 HTTP 是无状态的 也就是说 HTTP 请求方和响应方间无法维护状态 都是一次性的 它不知道前后的请求都发生了什么 但有的场景下 我们需要维护状态 最典型的 一 个用户登陆CS
  • Linux基础笔记15

    文本处理 wc 用于统计文件的字节数 单词数 行数等信息 并将统计结果标准输出到终端 w 统计单词数 c 统计字节数 l 统计行数 m 统计字符数 L 显示最长行的长度 help 显示帮助信息 version 显示版本信息 root iZr
  • PHP静态绑定知识点学习记录

    最近在学习JAVA基础中关于静态方法的知识 回想起PHP可以使用self 或者static 两种方式来进行静态方法的调用 有些忘记两者的差异 因此 做一下学习记录 后期绑定 的意思是说 static 不再被解析为定义当前方法所在的类 而是在
  • Linux 学习笔记1 安装linux详细教程

    系统 CentOS 8 1 1911 x86 64 dvd1 软件 VMware Workstation Pro 16 安装centos VM安装的步骤 1 去BIOS里修改设置开启以常交 f2 f10 2 安装虚拟机软件 vm15 5 演
  • QT(qCompress和qUncompress)与zlib(compress和uncompress)相互调用

    因为QT也是用zlib库的 所以理论上数据是可以直接互通的 但现实是残酷的 通过对qCompress和compress压缩的数据进行打印 可以知道qCompress比compress的数据长四个字节 而这四个字节的内容则未压缩前的数据长度
  • 解决图像目标检测两框重叠问题

    文章目录 1 问题现象 2 解决办法 3 Non Maximum Suppression 原理 3 1 什么是非极大值抑制 3 2 为什么要用非极大值抑制 3 3 如何使用非极大值抑制 3 4 效果 4 参考资料 1 问题现象 使用yolo
  • SpringBoot注解详解:从核心到Web,从数据到测试,一网打尽

    总结的了平时学习springboot常用的一些注解 方便以后开发时可以阅览回忆 springboot的常用注解可以分为以下几类 核心注解 这些注解是springboot的基础 用于启动 配置和管理springboot应用 Web MVC注解
  • 使用SurfaceView加载多张大分辨率图片做帧动画,解决OOM问题

    项目需求 动态背景 需求很简单 只是用帧动画做一个动态的背景而已 但若是70多张图片都是1920 1080 一张485k的话 传统意义上的帧动画就很难实现了 往往加载10张就开始OOM 一般来说 常用的实现动态背景的有效方式有三种 视频 果
  • JVM系列-第12章-垃圾回收器

    垃圾回收器 GC 分类与性能指标 垃圾回收器概述 垃圾收集器没有在规范中进行过多的规定 可以由不同的厂商 不同版本的JVM来实现 由于JDK的版本处于高速迭代过程中 因此Java发展至今已经衍生了众多的GC版本 从不同角度分析垃圾收集器 可
  • Android 腾讯Bugly的应用升级&热更新

    经过去年的九月份至现在 发现自己很久没有写过比较好的文章了 今天就趁着通宵的劲 写一下对腾讯Bugly的应用升级 热更新的理解 希望对新手有所帮助 有兴趣的可以了解下 没兴趣的也可以看完之后吐槽我 Bugly 文档中心 https bugl