腾讯Bugly集成

首先在Module:app/build.gredle下添加依赖集成SDK

如果只想集成单独的异常上报可以将依赖改成这样compile 'com.tencent.bugly:crashreport:latest.release'

添加依赖.png

然后在Module:app/build.gredle下设置支持的SO库架构集成NDK,同时配置打开多dex

设置SO库.png

然后仍然在在Module:app/build.gredle下设置配置签名(当然测试时可以不配置),同时也在打开了混淆配置以及优化(当然也可以不需要配置混淆,此处配置混淆后面会提到上传mapping快速混淆配置请参考我以前的文章

签名并且混淆.png

然后仍然在在Module:app/build.gredle下设置配置lint检测编译器和运行时错误(当然此处也可以不配置),在这里我配置了多渠道打包,不配置也没关系,建议配置不配置都试一下,看看两个打补丁时会有什么不同

linty以及多渠道.png

好了,下来这一点需要注意,刚开始没添加这个东西,打好补丁后一直报错误,提示补丁版本不对如图:

tinker.png

后面添加了这句打补丁后就可以了

gradle.project.png

好了,到这里啦异常统计肯定没问题了,看看初始化

/**        * 单个异常上传统计时的初始化方法        *@appContext上下文        *@crashReportAppId注册时申请的APPID        *@isDebug自定义日志将会在Logcat中输出。        */CrashReport.initCrashReport(getApplicationContext(), Commont.APP_ID,false);/**        * 统一初始化Bugly产品,包含Beta        *@context上下文        *@appId注册时申请的APPID        *@isDebug自定义日志将会在Logcat中输出。        *        * 提示:已经接入Bugly用户改用上面的初始化方法,不影响原有的crash上报功能; init方法会自动检测更新,不需要再手动调用Beta.checkUpgrade(), 如需增加自动检查时机可以使用Beta.checkUpgrade(false,false);        * 参数1:isManual 用户手动点击检查,非用户点击操作请传false        * 参数2:isSilence 是否显示弹窗等交互,[true:没有弹窗和toast] [false:有弹窗或toast]        */Bugly.init(this, Commont.APP_ID,true);/**        * 统一初始化Bugly产品,包含Beta        *@context上下文        *@appId注册时申请的APPID        *@isDebug自定义日志将会在Logcat中输出。        *@strategyBugly高级设置        */Bugly.init(this, Commont.APP_ID,true, strategy);

上面有三种初始化的方法:

① 第一种为当你只需要只是仅仅要使用该产品的异常上传是依赖添加为上面说过的compile 'com.tencent.bugly:crashreport:latest.release',初始化时就使用第一种方法

②第二种为设置已经用到了统一的初始化方法,版本也会自动检查更新,异常也会上报

③拥有了第二种方法的能力并且还有其他能力,如果现在想添加一个App的打包渠道怎么搞,就用到了最后一个参数strategy作为Bugly高级设置。。。

到这里了,可以说应用升级也基本已经完成了,我们在到Manifest里面为升级补全配置

<!-- bugly配置权限start --><!-- bugly配置权限end --><!-- Bugly升级SDK配置开始 --><!-- 想兼容Android N或者以上的设备 --><!--友盟 Appkey 自己应用注册申请来的--><!--渠道号,多渠道这里使用了占位符$-->

在res文件下创建新文件xml并创建文件provider_paths.xml

<?xml version="1.0"encoding="utf-8"?><!-- 这里配置的两个外部存储路径是升级SDK下载的文件可能存在的路径 --><!-- /storage/emulated/0/Download/com.bugly.upgrade.demo/.beta/apk--><!--/storage/emulated/0/Android/data/com.bugly.upgrade.demo/files/apk/-->

ok,升级也完成了, 看看最后打补丁-----热修复

我这里用到了第三方的一个插件,方便简单一点,我们首先去下载一个插件Bugly

AbdroidStudio插件下载.png

完了之后就再根目录下创建一个tinker-support.gradle,然后再配置里面的东西

tinker-support.gradle.png

详细配置:

apply plugin:'com.tencent.bugly.tinker-support'def bakPath = file("${buildDir}/bakApk/")/** * 此处填写每次构建生成的基准包目录 */def baseApkDir ="app-0119-10-02-31"/** * 对于插件各参数的详细解析请参考 */tinkerSupport {    // 开启tinker-support插件,默认值trueenable=true// tinkerEnable功能开关    tinkerEnable =true// 是否编译完成后,归档apk到指定目录,默认值false// 指定归档目录,默认值当前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 ="1.0.0.4-base"//生成对于的基包版本  只有生成基准包的时候使用    tinkerId ="1.0.0.4-patch"//对于生成的补丁包版本  只有生成补丁包的时候使用    // 构建多渠道补丁时使用    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 {    tinkerEnable =true//oldApk ="${bakPath}/${appName}/app-release.apk"ignoreWarning =false//对应tinker插件userSign, 在运行过程中,    // 我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。    useSign =truedex {        dexMode ="jar"pattern = ["classes*.dex"]        loader = []    }    lib {        pattern = ["lib/*/*.so"]    }    res {        pattern = ["res/*","r/*","assets/*","resources.arsc","AndroidManifest.xml"]        ignoreChange = []        largeModSize = 100    }    // 用于生成补丁包中的'package_meta.txt'文件    packageConfig {//        configField("patchMessage","tinker is sample to use")//        configField("platform","all")//        configField("patchVersion","1.0.5")    }    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的分配    }}

在上述代码中tinkerSupport{}里面有这样一句,很清楚,如果你的项目没有用到多渠道打包,那么你可以将这一句注释掉,不用管它

构建渠道补丁.png

还有这样一句代码:

构建渠道补丁.png

这种情况下分为两种,enableProxyApplication默认为false

当enableProxyApplication为false时

① 新建SampleApplicationLike继承于DefaultApplicationLike,实现构造方法,在将Application中所有要执行的操作复制到这个类中

② 新建SampleApplication集成于TinkerApplication,构造这样写

publicSampleApplication(){//记住此处com.suchengkeji.android.bugly.applications.SampleApplicationLike改为你自己新建的路径包名super(ShareConstants.TINKER_ENABLE_ALL,"com.suchengkeji.android.bugly.applications.SampleApplicationLike","com.tencent.tinker.loader.TinkerLoader",false);    }@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@OverridepublicvoidonBaseContextAttached(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)publicvoidregisterActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks){        getApplication().registerActivityLifecycleCallbacks(callbacks);    }

③ 将启动Application改为SampleApplication

manifest.png

当enableProxyApplication为true时

① 自定义自己的Application为MyApplication,将所有要初始化的操作都写在MyApplication中,当然为了以防万一将初始化操作做到极致,将补丁安装和多dex在attachBaseContext方法中就开始初始化

@OverrideprotectedvoidattachBaseContext(Context base){super.attachBaseContext(base);// you must install multiDex whatever tinker is installed!MultiDex.install(base);// 安装tinker// TinkerManager.installTinker(this); 替换成下面Bugly提供的方法Beta.installTinker(this);    }

② 在onCreate()方法中可初始化设置Bugly一些鬼

这里额外说下Application启动时流程: attachBaseContext(Context) -----> onCreate()

③ 'Applaction'启动项改回来,改为我们自定义的

manifest2.png

到这里也就接近完了,现在看看怎么创建基准包和补丁包

如果我们现在要上线一个App为了以防万一的突发bug要创建一个基准包上线,以防有bug即使热修复

① 首先在Module:app/build.gradle中引入我们为热修复配置好的脚本插件

依赖插件脚本.png

② 现在就可以按照下图生成基准包了,图中1生成的为没有渠道的,2很明显是一个渠道,为腾讯应用宝的渠道,根据自己情况生成基准包

基准包生成.png

看看生成后的目录,其中下面框中的三个中的两个在上传基准包和生成补丁包时要用到,上传在后面,先把打补丁说了

基准包.png

如果我们的App发现了bug我们现在需要打补丁

补丁包1.png

补丁包2.png

看看打补丁后生成的目录,最后生成的7zip.apk就是要上传的补丁包

补丁包.png

基准包、mapping以及补丁包的上传

进入官网登陆,没有项目的可以新建

新建.png

复制App ID在我们的项目中配置

APPID.png

后面的直接上图了:

我们生成的基准包

启动

补丁的发布

image.png

如果你的应用再次全量更新,可以撤回和停止补丁的下发

image.png

mapping的上传

为什么要上传mapping?

mapping是你在项目中配置了混淆时才会有的东西,主要是为了对比编译前后方法名等一些量的混淆,能更方便的调试代码,定位错误位置

image.png

最后贴出以本Demo为例的源码

MyApplication

publicclassMyApplicationextendsApplication{@OverrideprotectedvoidattachBaseContext(Context base){        super.attachBaseContext(base);// you must install multiDex whatever tinker is installed!MultiDex.install(base);// 安装tinker// TinkerManager.installTinker(this); 替换成下面Bugly提供的方法Beta.installTinker(this);    }    @OverridepublicvoidonCreate(){        super.onCreate();        setBugly();    }privatevoidsetBugly(){/***** Beta高级设置 *****//**

        * true表示app启动自动初始化升级模块; false不会自动初始化;

        * 开发者如果担心sdk初始化影响app启动速度,可以设置为false,

        * 在后面某个时刻手动调用Beta.init(getApplicationContext(),false);

        */Beta.autoInit =true;/**

        * true表示初始化时自动检查升级; false表示不会自动检查升级,需要手动调用Beta.checkUpgrade()方法;

        */Beta.autoCheckUpgrade =true;/**

        * 设置升级检查周期为60s(默认检查周期为0s),60s内SDK不重复向后台请求策略);

        */Beta.upgradeCheckPeriod =60*1000;/**

        * 设置启动延时为1s(默认延时3s),APP启动1s后初始化SDK,避免影响APP启动速度;

        */Beta.initDelay =1*1000;/**

        * 设置通知栏大图标,largeIconId为项目中的图片资源;

        */Beta.largeIconId = R.mipmap.ic_launcher;/**

        * 设置状态栏小图标,smallIconId为项目中的图片资源Id;

        */Beta.smallIconId = R.mipmap.ic_launcher;/**

        * 设置更新弹窗默认展示的banner,defaultBannerId为项目中的图片资源Id;

        * 当后台配置的banner拉取失败时显示此banner,默认不设置则展示“loading“;

        */Beta.defaultBannerId = R.mipmap.ic_launcher;/**

        * 设置sd卡的Download为更新资源保存目录;

        * 后续更新资源会保存在此目录,需要在manifest中添加WRITE_EXTERNAL_STORAGE权限;

        */Beta.storageDir = Environment                .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);/**

        * 已经确认过的弹窗在APP下次启动自动检查更新时会再次显示;

        */Beta.showInterruptedStrategy =true;/**

        * 只允许在MainActivity上显示更新弹窗,其他activity上不显示弹窗; 不设置会默认所有activity都可以显示弹窗;

        */Beta.canShowUpgradeActs.add(MainActivity.class);/***** Bugly高级设置 *****/BuglyStrategy strategy =newBuglyStrategy();/**

        * 设置app渠道号

        */strategy.setAppChannel(ChannelUtils.getAppMetaData(this,"UMENG_CHANNEL"));//        /**//        * 单个异常上传统计时的初始化方法//        * @appContext 上下文//        * @crashReportAppId 注册时申请的APPID//        * @isDebug 自定义日志将会在Logcat中输出。//        *///        CrashReport.initCrashReport(getApplicationContext(), Commont.APP_ID, false);////        /**//        * 统一初始化Bugly产品,包含Beta//        * @context 上下文//        * @appId 注册时申请的APPID//        * @isDebug 自定义日志将会在Logcat中输出。//        *//        * 提示:已经接入Bugly用户改用上面的初始化方法,不影响原有的crash上报功能; init方法会自动检测更新,不需要再手动调用Beta.checkUpgrade(), 如需增加自动检查时机可以使用Beta.checkUpgrade(false,false);//        * 参数1:isManual 用户手动点击检查,非用户点击操作请传false//        * 参数2:isSilence 是否显示弹窗等交互,[true:没有弹窗和toast] [false:有弹窗或toast]//        *///        Bugly.init(this, Commont.APP_ID, true);/**

        * 统一初始化Bugly产品,包含Beta

        * @context 上下文

        * @appId 注册时申请的APPID

        * @isDebug 自定义日志将会在Logcat中输出。

        * @strategy Bugly高级设置

        */Bugly.init(this, Commont.APP_ID,true, strategy);    }}

SampleApplication

/**

* 注意:这个类集成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

*/publicclassSampleApplicationextendsTinkerApplication{publicSampleApplication(){//将‘com.suchengkeji.android.bugly.applications.SampleApplicationLike’替换为自己的包名及路径super(ShareConstants.TINKER_ENABLE_ALL,"com.suchengkeji.android.bugly.applications.SampleApplicationLike","com.tencent.tinker.loader.TinkerLoader",false);    }}

SampleApplicationLike

publicclassSampleApplicationLikeextendsDefaultApplicationLike{publicSampleApplicationLike(Application application,inttinkerFlags,                                boolean tinkerLoadVerifyFlag,longapplicationStartElapsedTime,longapplicationStartMillisTime, Intent tinkerResultIntent){        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);    }    @OverridepublicvoidonCreate(){        super.onCreate();// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId// 调试时,将第三个参数改为true//        Bugly.init(getApplication(), APP_ID, false);setBUG();    }privatevoidsetBUG(){/***** Beta高级设置 *****//**

        * true表示app启动自动初始化升级模块; false不会自动初始化;

        * 开发者如果担心sdk初始化影响app启动速度,可以设置为false,

        * 在后面某个时刻手动调用Beta.init(getApplicationContext(),false);

        */Beta.autoInit =true;/**

        * true表示初始化时自动检查升级; false表示不会自动检查升级,需要手动调用Beta.checkUpgrade()方法;

        */Beta.autoCheckUpgrade =true;/**

        * 设置升级检查周期为60s(默认检查周期为0s),60s内SDK不重复向后台请求策略);

        */Beta.upgradeCheckPeriod =60*1000;/**

        * 设置启动延时为1s(默认延时3s),APP启动1s后初始化SDK,避免影响APP启动速度;

        */Beta.initDelay =1*1000;/**

        * 设置通知栏大图标,largeIconId为项目中的图片资源;

        */Beta.largeIconId = R.mipmap.ic_launcher;/**

        * 设置状态栏小图标,smallIconId为项目中的图片资源Id;

        */Beta.smallIconId = R.mipmap.ic_launcher;/**

        * 设置更新弹窗默认展示的banner,defaultBannerId为项目中的图片资源Id;

        * 当后台配置的banner拉取失败时显示此banner,默认不设置则展示“loading“;

        */Beta.defaultBannerId = R.mipmap.ic_launcher;/**

        * 设置sd卡的Download为更新资源保存目录;

        * 后续更新资源会保存在此目录,需要在manifest中添加WRITE_EXTERNAL_STORAGE权限;

        */Beta.storageDir = Environment                .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);/**

        * 已经确认过的弹窗在APP下次启动自动检查更新时会再次显示;

        */Beta.showInterruptedStrategy =true;/**

        * 只允许在MainActivity上显示更新弹窗,其他activity上不显示弹窗; 不设置会默认所有activity都可以显示弹窗;

        */Beta.canShowUpgradeActs.add(MainActivity.class);/***** Bugly高级设置 *****/BuglyStrategy strategy =newBuglyStrategy();/**

        * 设置app渠道号

        * ChannelUtils.getAppMetaData(this, "UMENG_CHANNEL")获取渠道号

        */strategy.setAppChannel(ChannelUtils.getAppMetaData(getApplication(),"UMENG_CHANNEL"));/***** 统一初始化Bugly产品,包含Beta *****/Bugly.init(getApplication(), Commont.APP_ID,true, strategy);    }    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)    @OverridepublicvoidonBaseContextAttached(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)publicvoidregisterActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks){        getApplication().registerActivityLifecycleCallbacks(callbacks);    }}

MainActivity

publicclassMainActivityextendsAppCompatActivityimplementsAdapterView.OnItemClickListener{@BindView(R.id.list_view)    ListView listView;    String[] strings = {"异常上报","应用更新","热修复测试"};@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        setList();    }privatevoidsetList(){        listView.setAdapter(newBaseAdapter() {@OverridepublicintgetCount(){returnstrings.length;            }@OverridepublicObjectgetItem(intposition){returnstrings[position];            }@OverridepubliclonggetItemId(intposition){returnposition;            }@OverridepublicViewgetView(intposition, View convertView, ViewGroup parent){@SuppressLint("ViewHolder") View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.list_item, parent,false);                TextView textView = (TextView) view.findViewById(R.id.list_item_text);                textView.setText(strings[position]);returnview;            }        });        listView.setOnItemClickListener(this);    }@OverridepublicvoidonItemClick(AdapterView parent, View view,intposition,longid){        Log.d(LogUtils.TAG(), position +"");switch(position) {case0:/***** 异常崩溃上传 *****/CrashReport.testJavaCrash();break;case1:/***** 检查更新 *****/loadUpgradeInfo();                loadAppInfo();                Beta.checkUpgrade();break;case2:/***** 崩溃上传(此处要打修复补丁) *****/FixBugClass.getTimer(MainActivity.this);//                BugClass.getTimer(MainActivity.this);break;        }    }@OverrideprotectedvoidonStart(){super.onStart();//添加权限PermissionGen.with(this)                .addRequestCode(Commont.PHOTO_PERMISS)                .permissions(                        Manifest.permission.INTERNET,                        Manifest.permission.READ_PHONE_STATE,                        Manifest.permission.ACCESS_NETWORK_STATE,                        Manifest.permission.ACCESS_WIFI_STATE,                        Manifest.permission.READ_LOGS,                        Manifest.permission.WRITE_EXTERNAL_STORAGE)                .request();    }@PermissionSuccess(requestCode = Commont.PHOTO_PERMISS)publicvoidrequestPhotoSuccess(){//成功之后的处理//.......Log.d(LogUtils.TAG(),"动态权限加载成功");    }@PermissionFail(requestCode = Commont.PHOTO_PERMISS)publicvoidrequestPhotoFail(){//失败之后的处理,我一般是跳到设置界面Log.d(LogUtils.TAG(),"动态权限加载失败");    }/**

    * 获取升级信息

    */privatevoidloadUpgradeInfo(){/***** 获取升级信息 *****/UpgradeInfo upgradeInfo = Beta.getUpgradeInfo();if(upgradeInfo ==null) {            Log.d(LogUtils.TAG(),"无升级信息");return;        }        StringBuilder info =newStringBuilder();        info.append("id: ").append(upgradeInfo.id).append("\n");        info.append("标题: ").append(upgradeInfo.title).append("\n");        info.append("升级说明: ").append(upgradeInfo.newFeature).append("\n");        info.append("versionCode: ").append(upgradeInfo.versionCode).append("\n");        info.append("versionName: ").append(upgradeInfo.versionName).append("\n");        info.append("发布时间: ").append(upgradeInfo.publishTime).append("\n");        info.append("安装包Md5: ").append(upgradeInfo.apkMd5).append("\n");        info.append("安装包下载地址: ").append(upgradeInfo.apkUrl).append("\n");        info.append("安装包大小: ").append(upgradeInfo.fileSize).append("\n");        info.append("弹窗间隔(ms): ").append(upgradeInfo.popInterval).append("\n");        info.append("弹窗次数: ").append(upgradeInfo.popTimes).append("\n");        info.append("发布类型(0:测试 1:正式): ").append(upgradeInfo.publishType).append("\n");        info.append("弹窗类型(1:建议 2:强制 3:手工): ").append(upgradeInfo.upgradeType);        Log.d(LogUtils.TAG(), info +"");    }privatevoidloadAppInfo(){try{            StringBuilder info =newStringBuilder();            PackageInfo packageInfo =this.getPackageManager().getPackageInfo(this.getPackageName(),                            PackageManager.GET_CONFIGURATIONS);intversionCode = packageInfo.versionCode;            String versionName = packageInfo.versionName;            info.append("appid: ").append(Commont.APP_ID).append(" ")                    .append("channel: ")                    .append(ChannelUtils.getAppMetaData(MainActivity.this,"UMENG_CHANNEL"))                    .append(" ")                    .append("version: ").append(versionName).append(".").append(versionCode);            Log.d(LogUtils.TAG(), info +"");        }catch(Exception e) {            e.printStackTrace();        }    }}

Commont

publicclassCommont{/***  腾讯Bugly产品ID  ***/publicstaticfinalString APP_ID ="5e****70c08";/***  权限处理标识  ***/publicstaticfinalint PHOTO_PERMISS =100;}

ChannelUtils

/**

    * 获取app当前的渠道号或application中指定的meta-data

    *

    * @return 如果没有获取成功(没有对应值,或者异常),则返回值为空

    */publicstaticStringgetAppMetaData(Context context,Stringkey) {if(context ==null|| TextUtils.isEmpty(key)) {returnnull;        }StringchannelNumber =null;try{            PackageManager packageManager = context.getPackageManager();if(packageManager !=null) {                ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);if(applicationInfo !=null) {if(applicationInfo.metaData !=null) {                        channelNumber = applicationInfo.metaData.getString(key);                    }                }            }        }catch(PackageManager.NameNotFoundException e) {            e.printStackTrace();        }returnchannelNumber;    }//      在需要的地方调用//    String channelNumber = getAppMetaData(getBaseContext(), "UMENG_CHANNEL");//获取app当前的渠道号}

BugClass

/**

* 有错误的bug类

*/publicclassBugClass{privatestaticString timeType ="yyyy-MM-dd HH:mm:ss";publicstaticvoidgetTimer(Context c){        String date =null;        Log.d(LogUtils.TAG(),"上线后的紧急BUG");        Log.d(LogUtils.TAG(), date.length() +"");//此处留一个空指针bug@SuppressLint("SimpleDateFormat")        SimpleDateFormat sDateFormat =newSimpleDateFormat(timeType);        date = sDateFormat.format(newjava.util.Date());        Toast.makeText(c, date, Toast.LENGTH_SHORT).show();    }}

FixBugClass

/**

* 修复过bug的补丁类

*/publicclassFixBugClass{privatestaticString timeType ="yyyy-MM-dd HH:mm:ss";publicstaticvoidgetTimer(Context c){        String date =null;        Log.d(LogUtils.TAG(),"上线后的紧急BUG修复补丁");//Log.d(LogUtils.TAG(), date.length() + "");//去掉此处的bug@SuppressLint("SimpleDateFormat")        SimpleDateFormat sDateFormat =newSimpleDateFormat(timeType);        date = sDateFormat.format(newjava.util.Date());        Toast.makeText(c, date, Toast.LENGTH_SHORT).show();    }}

注意点,打完补丁之后在运行应用是,第一次仍然会崩溃,不过在崩溃时会自己打补丁修复,第二次以后就好了

好了,到这里就完了,此文章仅供参考,具体以官方文档为准,有更好的思路或方法,请不吝告知,如有错误,请及时指出。。

作者:一s独秀

链接:https://www.jianshu.com/p/fbab774dab8e

来源:简书

简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 简单的记录下在快速集成Bugly是遇到过的坑,更多说明去官方文档,此文章,简单的集成bugly实现了,异常统计,应...
    一s独秀阅读 16,617评论 12 17
  • 前言 热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。B...
    Smile__EveryDay阅读 24,833评论 31 19
  • Bugly Android热更新使用指南前言:在项目进行的时候,因为可能上线项目存在bug,影响用户的体验。所以不...
    蒲小帅丶阅读 1,623评论 2 19
  • 前言 热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。目...
    LnJan阅读 1,803评论 2 3
  • 一、官方文档 首先,官方文档,官方文档,官方文档!Bugly Android热更新 二、个人总结 1.配置Appl...
    无_理阅读 1,398评论 0 1