美文网首页
Bugly热修复集成

Bugly热修复集成

作者: 竖起大拇指 | 来源:发表于2020-06-11 18:03 被阅读0次

    1.简述

    Bugly采用Tinker作为热修复的解决方案,Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码,So库以及资源,让应用能够不需要重新安装的情况下实现更新。Tinker不能让补丁实时生效,它必须在打上补丁后重启App。在开始集成之前,我们有必要了解清楚,Tinker有哪些不足:
    由于原理与系统限制,Tinker有以下已知问题:

    • Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);
    • 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
    • 在Android N上,补丁对应用启动时间有轻微的影响;
    • 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
    • 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

    2.添加插件依赖

    项目的build.gradle:

    buildscript {
        ext.kotlin_version = '1.3.30'
        repositories {
            google()
            jcenter()
            
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.2.1'  //3.6.3不支持
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
            // tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4
            // 注意:自tinkersupport 1.0.3版本起无需再配tinker插件的classpath。
            classpath "com.tencent.bugly:tinker-support:1.1.5"
    
            // 多渠道插件(多渠道打包推荐使用)
            classpath 'com.meituan.android.walle:plugin:1.1.6'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            google()
            jcenter()
            
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    

    3.集成SDK

    app的build.gradle:

    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.2"
    
        defaultConfig {
            applicationId "com.geespace.hotfix"
            minSdkVersion 23
            targetSdkVersion 26
            versionCode 1
            versionName "1.1"
    
            // 开启multidex
            multiDexEnabled true
    
            ndk {
                //设置支持的SO库架构
                abiFilters 'armeabi-v7a' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
            }
    
        }
    
        dexOptions {
            // 支持大工程模式
            jumboMode = true
        }
    
    
        // 签名配置
        signingConfigs {
            release {
                try {
                    storeFile file("./keystore/release.keystore")
                    storePassword "testres"
                    keyAlias "testres"
                    keyPassword "testres"
                } catch (ex) {
                    throw new InvalidUserDataException(ex.toString())
                }
            }
    
            debug {
                storeFile file("./keystore/debug.keystore")
            }
        }
    
    
        lintOptions {
            checkReleaseBuilds false
            abortOnError false
        }
    
        // 构建类型
        buildTypes {
            release {
                minifyEnabled false
                signingConfig signingConfigs.release
                multiDexKeepProguard file('multidex-config.pro')
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
    
            debug {
                debuggable true
                minifyEnabled false
                signingConfig signingConfigs.debug
            }
        }
    
        // 多渠道配置
    //    flavorDimensions "default"
    //
    //    productFlavors {
    //        xiaomi {
    //
    //        }
    //        yyb {
    //
    //        }
    //        wdj {
    //
    //        }
    //    }
    }
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.core:core-ktx:1.3.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        implementation 'androidx.multidex:multidex:2.0.1'
        //注释掉原有bugly的仓库
        //compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.3.4
        implementation 'com.tencent.bugly:crashreport_upgrade:latest.release'
        // 指定tinker依赖版本(注:应用升级1.3.5版本起,不再内置tinker)
        implementation 'com.tencent.tinker:tinker-android-lib:latest.release'
        //implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0
    
        // walle 瓦力(多渠道使用)
        implementation 'com.meituan.android.walle:library:1.1.6'
    
        //注意: 升级SDK已经集成crash上报功能,已经集成Bugly的用户需要注释掉原来Bugly的jcenter库;
        // 已经配置过符号表的Bugly用户保留原有符号表配置; Bugly SDK(2.1.5及以上版本)
        // 已经将Java Crash和Native Crash捕获功能分开,如果想使用NDK库,需要配置:
        // compile 'com.tencent.bugly:nativecrashreport:latest.release'
    }
    
    // 依赖插件脚本
    apply from: 'tinker-support.gradle'
    
    // 多渠道使用walle示例(注:多渠道使用)
    apply from: 'multiple-channel.gradle'
    
    
    

    4.配置Tinker

    在app的build.gradle文件同级目录下创建一个tinker-support.gradle文件.内容如下:

    apply plugin: 'com.tencent.bugly.tinker-support'
    
    def bakPath = file("${buildDir}/bakApk/")
    
    /**
     * 此处填写每次构建生成的基准包目录
     */
    def baseApkDir = "app-0611-15-14-38"
    //def myTinkerId="1.1-base"
    def myTinkerId="1.1-patch"
    
    /**
     * 对于插件各参数的详细解析请参考 如果没有特殊需求下面的参数都可以不用更改;
     * 如果apk需要加固等可以参考具体描述设置参数
     */
    tinkerSupport {
    
        // 开启tinker-support插件,默认值true
        enable = true
    
        // 指定归档目录,默认值当前module的子目录tinker
        autoBackupApkDir = "${bakPath}"
    
        //建议设置true,用户就不用再自己管理tinkerId的命名,插件会为每一次构建的base包自动生成唯一的tinkerId,
        // 默认命名规则是versionname.versioncode_时间戳
        //具体参考https://github.com/BuglyDevTeam/Bugly-Android-Demo/wiki/Tinker-ID%E8%AF%A5%E6%80%8E%E4%B9%88%E8%AE%BE%E7%BD%AE
       // autoGenerateTinkerId = true
    
        // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
        // 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。
        // 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等
        //tinkerId = "1.1-base"
        tinkerId = "${myTinkerId}"
    
        // 是否启用覆盖tinkerPatch配置功能,默认值false
        // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
        overrideTinkerPatchConfiguration = false
    
        // 编译补丁包时,必需指定基线版本的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"
    
    
        // 构建多渠道补丁时使用
         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}/${baseApkDir}/app-release.apk"
        ignoreWarning = false
        useSign = true
        dex {
            dexMode = "jar"
            pattern = ["classes*.dex"]
            loader = []
        }
        lib {
            pattern = ["lib/*/*.so", "src/main/jniLibs/*/*.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 = "${myTinkerId}"
            //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
            applyResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
        }
    }
    
    1.overrideTinkerPatchConfiguration

    当overrideTinkerPatchConfiguration=true时,tinkerPatch可以省略不写,Bugly会加载默认的Tinker配置,但请注意,如果你的so文件不是存放在libs目录下(与src目录同级),又或者资源文件的存放在你自定义的目录中,那么这时候你要小心了,这些文件在制作补丁时不会被检测,也就是说这些so文件和资源文件将不会被热修复,这种情况下就需要将
    overrideTinkerPatchConfiguration=false,并设置tinkerPatch的lib和res属性。

    2.baseApkDir

    baseApkDir时基准包(也称基线包)的目录,在生成补丁时需要根据基准包在bakApk下具体文件夹名字修改。


    image.png
    3.tinkerId

    tinkerId时Bugly热修复方案最重要的一个因素,一般来说我们可以使用git版本号、versionName等等,它会将补丁包与基准包产生对应关系,假设基准包的tinkerId为1.1-base,则生成的补丁包中的YAPATCH.MF文件关系如下:

    image.png
    对于同一个基准包,可以发布多个补丁包。
    Q:如果我打了多个补丁包,比如先上传了补丁A已经下发到了设备中并且修复,如果我又打了一个补丁,这种情况会怎样?
    A:如果你的基于基线版本打了多个补丁包,并且上传了多个,我们会以你最后上传的补丁为准,就是说后面的补丁会覆盖前面的补丁

    5.初始化SDK

    Bugly的初始化工作需要在Applicaiton中完成,但是对原生Tinker来说,默认的Applicaiton是无法完成热修复的,Tinker针对Applicaiton无法热修复的问题,给予了两个选择:

    • enableProxyApplication = false的情况

    这是推荐的接入方式,一定程度上会增加接入成本,但是具有更好的兼容性.

    package com.geespace.hotfix.application;
    
    import com.tencent.tinker.loader.app.TinkerApplication;
    import com.tencent.tinker.loader.shareutil.ShareConstants;
    
    /**
     * Created by maozonghong
     * on 2020/6/10
     */
    public class SampleApplication extends TinkerApplication {
        public SampleApplication() {
            super(ShareConstants.TINKER_ENABLE_ALL,
                    "com.geespace.hotfix.application.SampleApplicationLike",
                    "com.tencent.tinker.loader.TinkerLoader", false);
        }
    
    //    注意:这个类集成TinkerApplication类,这里面不做任何操作,所有Application的代码都会放到ApplicationLike继承类当中
    //            参数解析
    //    参数1:tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
    //    参数2:delegateClassName Application代理类 这里填写你自定义的ApplicationLike
    //    参数3:loaderClassName Tinker的加载器,使用默认即可
    //    参数4:tinkerLoadVerifyFlag 加载dex或者lib是否验证md5,默认为false
    }
    
    package com.geespace.hotfix.application;
    
    import android.annotation.TargetApi;
    import android.app.Application;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Build;
    import android.util.Log;
    import android.widget.Toast;
    
    import androidx.multidex.MultiDex;
    
    import com.meituan.android.walle.WalleChannelReader;
    import com.tencent.bugly.Bugly;
    import com.tencent.bugly.beta.Beta;
    import com.tencent.bugly.beta.interfaces.BetaPatchListener;
    import com.tencent.tinker.entry.DefaultApplicationLike;
    
    import java.util.Locale;
    
    /**
     * Created by maozonghong
     * on 2020/6/10
     */
    
    //注意:tinker需要你开启MultiDex,你需要在dependencies中进行配置
    // compile "com.android.support:multidex:1.0.1"才可以使用MultiDex.install方法;
    // SampleApplicationLike这个类是Application的代理类,以前所有在Application的实现必须要全部拷贝到这里,
    // 在onCreate方法调用SDK的初始化方法,在onBaseContextAttached中调用Beta.installTinker(this);
    
    public class SampleApplicationLike extends DefaultApplicationLike{
    
        public static final String TAG = "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();
    
            Beta.autoCheckUpgrade=true;
            // 设置是否开启热更新能力,默认为true
            Beta.enableHotfix = true;
            // 设置是否自动下载补丁,默认为true
            Beta.canAutoDownloadPatch = true;
            // 设置是否自动合成补丁,默认为true
            Beta.canAutoPatch = true;
            // 设置是否提示用户重启,默认为false
            Beta.canNotifyUserRestart = true;
            // 补丁回调接口
            Beta.betaPatchListener = new BetaPatchListener() {
                @Override
                public void onPatchReceived(String patchFile) {
                    Log.e(TAG,"onPatchReceived:"+patchFile);
                    Toast.makeText(getApplication(), "补丁下载地址" + patchFile, Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onDownloadReceived(long savedLength, long totalLength) {
                    Log.e(TAG,"onDownloadReceived: savedLength"+savedLength+",totalLength:"+totalLength);
                    Toast.makeText(getApplication(),
                            String.format(Locale.getDefault(), "%s %d%%",
                                    Beta.strNotificationDownloading,
                                    (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
                            Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onDownloadSuccess(String msg) {
                    Log.e(TAG,"onDownloadSuccess:"+msg);
                    Toast.makeText(getApplication(), "补丁下载成功", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onDownloadFailure(String msg) {
                    Log.e(TAG,"onDownloadFailure:"+msg);
                    Toast.makeText(getApplication(), "补丁下载失败", Toast.LENGTH_SHORT).show();
    
                }
    
                @Override
                public void onApplySuccess(String msg) {
                    Log.e(TAG,"onApplySuccess:"+msg);
                    Toast.makeText(getApplication(), "补丁应用成功", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onApplyFailure(String msg) {
                    Log.e(TAG,"onApplyFailure:"+msg);
                    Toast.makeText(getApplication(), "补丁应用失败", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onPatchRollback() {
    
                }
            };
    
            // 设置开发设备,默认为false,上传补丁如果下发范围指定为“开发设备”,需要调用此接口来标识开发设备
            Bugly.setIsDevelopmentDevice(getApplication(), true);
            // 多渠道需求塞入
             String channel = WalleChannelReader.getChannel(getApplication());
             Bugly.setAppChannel(getApplication(), channel);
    
    
            // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
            // 调试时,将第三个参数改为true
            Bugly.init(getApplication(), "23a0cc94f2", true);
        }
    
    
        @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);
        }
    
        @Override
        public void onTerminate() {
            super.onTerminate();
            Beta.unInit();
        }
    
    }
    
    • enableProxyApplication = true 的情况
    public class MyApplication extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
            // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
            // 调试时,将第三个参数改为true
            Bugly.init(this, "900029763", false);
        }
    
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            // you must install multiDex whatever tinker is installed!
            MultiDex.install(base);
    
    
            // 安装tinker
            Beta.installTinker();
        }
    
    }
    

    注:无须你改造Application,主要是为了降低接入成本,我们插件会动态替换AndroidMinifest文件中的Application为我们定义好用于反射真实Application的类(需要您接入SDK 1.2.2版本 和 插件版本 1.0.3以上)。

    6.AndroidManifest.xml配置

    1.权限配置
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <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" />
    
    2.Activity配置
     <activity
                android:name="com.tencent.bugly.beta.ui.BetaActivity"
                android:configChanges="keyboardHidden|orientation|screenSize|locale"
                android:theme="@android:style/Theme.Translucent" />
    
    3.FileProvider配置
     <provider
                android:name="androidx.core.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>
    
         <!--
         如果你使用的第三方库也配置了同样的FileProvider, 可以通过继承FileProvider类来解决合并冲突的问题,示例如下
          <provider
                android:name=".utils.BuglyFileProvider"
                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>
            -->
    

    7.制作基准包

    在app编码完成并测试通过后,就是打包上线了,上线前打的包就是基准包。

    • 打开app下的tinker.support.gradle文件
    • 配置基准包的tinkerId
    • 双击运行build下的assembleRelease


      image.png

      AS在执行assembleRelease指令时,就是在编译基准包了,当编译完成时,app的build目录下会自动生成基准包文件夹,以时间戳来命名的(也就是说,每次执行assembleRelease指令都会在build目录下创建不同的基准包文件夹)。见下图:


      image.png
      这两个文件对之后制作补丁包来说是非常重要的,你需要做的就是将这两个文件保存好,可以保存到云盘,硬盘,git服务器上,但是不能就让它这样放着,因为当你执行clean project时,app的build目录会被删除,这样基准包与R文件都会丢失。

    8.制作补丁包

    • 修改baseApkDir的值为基准包所有文件夹的名字。
    • 修改补丁包的 tinkerId
    • 打开侧边的Gradle标签,找到项目的主Module,双击tinker-support下的buildTinkerPatchRelease指令,生成补丁包。


      image.png

    当编译完成后,在app的build/outputs/patch目录下会在"patch_singed_7zip.apk"文件,它就是补丁包,双击打开它,可以看到其中有一个YAPATCH.MF,里面记录了基准包与补丁包的tinkerId,Created-Time,VersionName等.


    image.png

    制作好的补丁,可以上传到bugly后台,进行下发。


    image.png

    Q. 我该上传哪个补丁?patch目录跟tinkerPatch目录下的补丁有什么区别吗?
    A:你必须要上传build/outputs/patch目录下的补丁包,

    9.多渠道打包

    这里推荐使用walle来打多渠道包,新一代多渠道打包神器。

    配置示例:

    // 多渠道使用walle示例(注:多渠道使用)

    apply from: 'multiple-channel.gradle'

    创建multiple-channel.gradle,内容如下:
    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配置:
    image.png
    命令行打多渠道包:

    执行 assembleReleaseChannels Task

    image.png

    ok,到此已经实现快速打多渠道包了。

    如何获取渠道信息?
    dependencies {
          ........
       // walle 瓦力(多渠道使用)
        implementation 'com.meituan.android.walle:library:1.1.6'
        ...............
    }
    

    在代码中获取渠道信息:

    String channel = WalleChannelReader.getChannel(this.getApplicationContext());
    Bugly.setAppChannel(getApplication(), channel);
    

    集成当中遇到的问题:

    1.Could not find method getAaptOptions() for arguments [] on task的问题
    应该是gralde太新了,目前不支持gralde 5.0.我使用gradle版本5.6.4,修改了资源文件,进行补丁文件打包的时候 一直报这个错。

    2.java.io.FileNotFoundException: E:...\build\intermediates\tinker_intermediates\values_backup
    解决方法:
    1.基准文件备份下
    2.clean项目
    3.重新打补丁包

    3.org.gradle.internal.event.ListenerNotificationException: Failed to notify project evaluation listener.
    我一开始使用gradle插件版本3.6.3,apply from: 'tinker-support.gradle'的时候 一直报这个错,后面的得知时gradle插件版本太高了,改成3.6.3以下的插件版本就ok了。

    4.Sample Application: com.tencent.tinker.loader.a: Tinker Exception:createDelegate failed
    我使用的是enableProxyApplication = false模式,运行一直报创建代理Applicaiton失败.我是打release包,因为开启了 minifyEnabled true,
    可能是混淆了,把minfiyEnable设置为false 就好了。

    5.Caused by: com.tencent.tinker.android.dex.DexException: Unexpected magic: [100, 101, 120, 10, 48, 51, 55, 0]
    打补丁包的过程中一直出行这个错误,github上看到有人说是tinker不支持java1.8,这特么就尴尬了,我项目中用到lambda,难道只能放弃?

    后面偶然网上说可能出现的问题

    • 基线版本问题
      基线版本最好采用gradle assemble、gradle assembleDebug、gradle assembleRelease等生成
      直接点击运行图标生成的apk貌似有问题。
    • 确认Patch包与基线包之间是否更改过下列相关信息,如不一致则报错。'

    minSdkVersion 16
    targetSdkVersion 29
    versionCode 1
    versionName "1.0.0"

    • minSdkVersion >=26 会出现这个问题

    以上就是本人集成过程中碰到的一些问题,大部分都是跟gradle和插件版本有关,所以,升级到最新的gradle版本会碰到很多问题。

    最后贴下Demo链接:

    https://github.com/maozonghong/BuglyTinkerDemo

    相关文章

      网友评论

          本文标题:Bugly热修复集成

          本文链接:https://www.haomeiwen.com/subject/drgbtktx.html