DropBox系列-打造车载系统APM框架

news2024/9/25 23:20:42

前言:

作者本人负责公司的APM监控模块,因为工作的原因,对ANR,crash等流程研究的比较多,最近在打造APM监控平台的时候,顺带对DropBox的实现原理进行了一定的学习和研究,发现了一些妙用,因此写这篇文章来分享。

本系列分为两篇文章,

1.DropBox系列-安卓DropBox介绍:主要介绍DropBox的一些实现原理。

2.DropBox系列-利用DropBox打造车载系统APM框架:主要介绍利用DropBox打造一个面向所有APP的稳定性监控框架。

PS:本文的代码以安卓9(SDK=28)的源码来介绍,因为目前车载开发基本上都是以安卓9为基础来定制的,而且DropBox对于那些拥有系统权限的APP来讲意义更大一些。

一.车载系统APM框架介绍

1.1为什么要单独搞一套APM框架

为什么要做单独搞一套APM监控框架?直接用现成的bugly,友盟,KOOM不可以吗?为什么要重复的造轮子呢?

我的理解是这样的,如果有合适的轮子,自然是最完美的,但是目前的这些轮子,也许对我们来说并不是最适合的,原因有三,如下:

1.侵入性问题。直接使用现成的APM框架是可以的,但是并不是最好的选择。现有的互联网大厂的APM框架对于APP是有侵入性的,不但影响APP的包体积大小,而且有可能会影响APP性能的,甚至有可能接入监控框架导致出现性能问题,比如接入bugly造成IO异常。

2.完整性问题。单一的某一个框架,并不能完整的记录我们所需要的所有目标。比如bugly只能记录崩溃/ANR(6.0以上ANR功能失效)/错误数量,KOOM专注于内存状态的监控,没有一个大一统的框架可以帮我们监控到所有我们想要的数据。

3.接入成本问题。我们的车机上有一百多个应用,假设一个应用接入bugly需要半天时间(接入+测试 +验证),那么100个应用就需要50个工作日,成本可以见一般。

1.2 APM框架介绍

作为车载系统开发,我们有着定制framework这一得天独厚的优势,所以,我们可以针对freamwork层做一些不影响原有逻辑的注入,来实现对所有APP的APM监控。

车载APM就是基于原理去做的,它作为一个单独的APP存在,使用观察者模式,发现APP应用出现异常的时候,主动去补货异常日志进行上报,从而做一个观察者,对所有的APP应用进行稳定性监控。

二.安卓异常处理机制及指标监控

2.1 java崩溃流程简介

具体流程可以参考这一篇文章,本章只是简介。android源码学习-android异常处理机制_失落夏天的博客-CSDN博客_android 异常处理

安卓应用程序发生异常后,会一层一层的上报,最终上报到Thread的dispatchUncaughtException方法中:

public final void dispatchUncaughtException(Throwable e) {
        Thread.UncaughtExceptionHandler initialUeh =
                Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        getUncaughtExceptionHandler().uncaughtException(this, e);
}

initialUeh用来记录崩溃信息,其实现类在安卓中是RuntimeInit.LoggingHandler,用来记录一些崩溃的信息。

而getUncaughtExceptionHandler()返回的是RuntimeInit.KillApplicationHandler,这个就是我们APP中真正去处理崩溃的实现类,我们看一下这个实现类:

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;

        ...
        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                ensureLogging(t, e);
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            }
            ...
        }
        ...
    }

主要做了三件事:

  1. 生成错误信息;

  1. 通过binder通知到AMS的handlerApplicationCrash方法;

  1. 删掉当前进程,完成应用的彻底退出。

AMS的handlerApplicationCrash方法中收到崩溃信息后,会通过DroxBoxManagerSerivice服务完成最终崩溃信息的记录和持久化,最终存储到data/system/dropbox文件夹下,这一块我们就不详细赘述了,我们只要支持,所有APP的崩溃,都会通知到AMS即可。

所以,我们只要在AMS中加上适当的钩子,完成崩溃消息的转发,就可以记录所有APP的java层崩溃(native崩溃另讲,检测机制不一样)。

2.2 ANR监控原理

具体原理可以参考这一篇文章,本章仍是简介。ANR系列之一:ANR显示和日志生成原理讲解_失落夏天的博客-CSDN博客

导致ANR的场景有很多种,比如输入事件无响应,广播事件无响应,Service中超时等等,具体类型和超时时间如下(下图中的某些时间其实是有问题的)。

无论哪种类型的ANR,最终都会通知到AnrHelper.appNotResponding方法中,所以我们从这个流程开始往下看。

 经过种种转发,最终的执行逻辑在ProcessErrorSatetRecord方法中,主要会执行下面三大块逻辑:

1.收集ANR信息

2.保存ANR日志

3.弹出ANR显示框

2.3 APP启动速度监控原理

APP启动,主要分为四部分:

  1. Laucher或其它应用通知AMS,显式或者隐式的去启动相应的Activity。

  1. 如果Activity所对应的APP进程不存在,则会走进程创建流程,通知Zygote去fork产生对应的进程,APP进程产生后,回调通知AMS。

  1. AMS通知APP一侧去加载对应的APK文件,以及创建和初始化Application及其它必要元素。

  1. AMS通知APP一侧去创建,初始化,以及最终显示Activity。

至此,整个启动流程结束,整个流程的时间可以理解为通俗理解上的冷启动时间。

完整的APP启动流程图如下:

所以,针对开始和结束分别设置一个钩子,那么两者之间的时间差,就是我们所需要冷启动时间了。

开始的事件很容易设置,AMS的中的startActivityWithFeature方法。

结束的事件相对会麻烦一些。因为最后的绘制流程,是APP进程和surfaceFlingger进程的通信,并不经过system_server进程。所以我们这里退而求其次,把APP进程把Window注册到WindowManager的事件作为结束的点,从而求出其中的差值。

这样,和标准意义上的冷启动流程,会有一些误差。这个误差就是Activity在onResume之后,首次measure,layout和draw的时间,这个误差理论上不会很大,除非是十分复杂的多层嵌套布局,或者ViewPager的不当使用。这一块的计算,我们放到后续再进行优化。

2.4 APP绘制卡顿监控

和上面说的监控首次绘制原理一样,APP绘制的流程除了注册Window,其余的渲染,以及vsync信息,都是surfaceFlingger进程来控制的。这一块需要想办法对surfaceFlingger进行一定的注入。这一项,我们一样放到后续进行相关的探索。

2.5 APP运行状态监控

APP运行状态包含很多指标,比如内存使用率,线程数,FD数量等等,这些数量的超标都会导致最终的OOM。KOOM和kensor两个框架都是对这些指标进行监控的。一样,我们放到后续在对这些指标进行相关的探索。

三.实现方案踩坑

3.1 车载系统APM架构设计

按照由简入难的原则,第一期的目标,我们先打算只实现崩溃和ANR两种异常指标的监控。

我们基于对系统代码改动量最小的原则,采用C/S的架构来设计。

首先我们通过一个独立的APP来实现异常信息的采集,分析和上报的工作,这个APP来实现绝大多数的逻辑。而系统侧只负责通知,即发生了异常之后,通知APP即可,不做其他任何的逻辑处理。这样对系统侧的修改就会尽可能的小。整体架构图设计如下:

接下来,我们该尝试,系统侧发现了异常之后,如何通知APP了。

3.2 和系统通讯方案尝试

方案1:信号量通讯

ANR系列之一:ANR显示和日志生成原理讲解_失落夏天的博客-CSDN博客

这篇文章中,我们有介绍,发现了ANR之后,系统会去主动采集最近使用进程的java层堆栈状态,而这种采集的通讯使用的是信号量,即系统发送一个信号量,APP侧接受了之后就开始采集自身的堆栈。APP侧并不止发生ANR的那一个,最近使用的APP也都会进行采集。

所以,我们只要在保证我们的APM_APP在最近使用的APP列表中,这样发生了ANR之后,我们只要监听这个信号量就知道发生ANR了。目前友盟和腾讯其实使用的就是这种方案。

优势:系统代码改动量很小,只需要添加一行代码,把APM_APP添加到最近使用列表中。

缺陷:ANR和Crash的实现方案就不统一了,因为Crash无法使用信号量。

因为设计初衷,还是希望crash和anr走一样的机制,这样方便后续的维护,所以信号量这个方案第一个被放弃。

方案2:binder通讯

由上面的流程我们可以知道,只要发生了crash,一定会通知AMS的handlerApplicationCrash方法,而ANR一定会通知AnrHelper的appNotResponding方法。

所以,可以通过APM_APP向系统注册一个binder引用。在上述的两个方法中,系统通过binder引用把异常信息传递给APM_APP。这样,就可以完成异常的监控了。

优势:监听是实时的,而且对于APP侧来说接口是一个,方法统一了。

缺陷:对系统的侵入性较大,而且一旦APM_APP被系统杀死,是完全无感知的。

这个方案需要修改系统源码,虽然作为车载系统开发修改源码是完全可以实现的,但是不利于后续的扩展,因为不同车型的系统源码都要改一遍。而且APM_APP是一个后台服务,一旦低内存被杀死,就不能继续进行异常的监听了。

所以这个方案作为一个备选,看是否有更好的方案再说。

方案3:文件夹监听

我们通过上面的原理流程可以得到,无论ANR还是crash,其实最终都会通知到DropBox去生成对应的异常日志文件。dropbox文件夹地址:data/system/dropbox,所以我们是否可以通过监听这个文件夹的变化,从而实现对异常信息的监听呢?

这样做有几个问题,首先是SELinux权限问题,普通APP是无法完成对这个文件夹的监听的。

但是对于车载系统开发来说问题不大,SELinux权限可以配置的。主动关掉SELinux后在进行尝试,最终发现这个方案不太可行。

首先监听crash文件是没有问题的,但是监听ANR文件的时候,由于ANR的生成文件是一个压缩包,所以通过FileObserver监听的时候,收到ANR类型的压缩文件会导致监听完全失效。而且这个方案和方案2存在一样的问题,一旦APM_APP进程挂掉,是无感知的,就无法监控后面异常信息了。

方案4:动态广播

通过上一篇文章的阅读

https://blog.csdn.net/rzleilei/article/details/128328967

我们知道dropbox完成异常信息的存储之后,会发送一个DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED类型的广播,所以,我们只要注册一个广播接收器来接受这个广播,这样收到了这个广播之后,我们在去主动读取dropbox文件夹下对应的异常文件,就可以知道具体的异常信息了。

说干就干,主动尝试了一把,动态广播的方式是可以的。但是静态广播的方式,会提示:Background execution not allowed: receiving Intent错误。权限的问题,哪怕已经申请了对应的权限,还是不行。

动态广播的话,还是存在APM_APP进程被杀死的场景。但是这已经是一个很好的方向了。因为这个方案对系统侧是0侵入,所以完全不用考虑适配不同车机系统的场景。而且crash和ANR的异常类型上报统一是完全可以保证的。

方案5:静态广播

静态广播会在发送广播的时候创建对应的进程,所以是可以保活的,因为这无疑是最理想的方向。既然上面说到会提示权限的问题,那么我们就尝试去解决静态广播权限的问题。

具体的错误信息如下:

12-09 16:58:31.732  1251  1296 W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.DROPBOX_ENTRY_ADDED flg=0x10 (has extras) } to com.xxx.apm/.broadcast.DropBoxBroadcast

报错的代码如下:

if (!skip) {
            final int allowed = mService.getAppStartModeLocked(
                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                // We won't allow this receiver to be launched if the app has been
                // completely disabled from launches, or it was not explicitly sent
                // to it and the app is in a state that should not receive it
                // (depending on how getAppStartModeLocked has determined that).
                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                    Slog.w(TAG, "Background execution disabled: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                        || (r.intent.getComponent() == null
                            && r.intent.getPackage() == null
                            && ((r.intent.getFlags()
                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                            && !isSignaturePerm(r.requiredPermissions))) {
                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                            component.getPackageName());
                    Slog.w(TAG, "Background execution not allowed: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                }
            }
        }

flag=16,package=nulll,comonent=null。所以唯一的判断条件就是isSignaturePerm(r.requiredPermissions)。requiredPermissions其实应该是READ_LOGS的权限,但是APM_APP即使申请到了这个权限,仍然会提示这个错误。所以我们看一下isSignaturePerm这个方法:

final boolean isSignaturePerm(String[] perms) {
        if (perms == null) {
            return false;
        }
        IPackageManager pm = AppGlobals.getPackageManager();
        for (int i = perms.length-1; i >= 0; i--) {
            try {
                PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0);
                if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE
                        | PermissionInfo.PROTECTION_FLAG_PRIVILEGED))
                        != PermissionInfo.PROTECTION_SIGNATURE) {
                    // If this a signature permission and NOT allowed for privileged apps, it
                    // is okay...  otherwise, nope!
                    return false;
                }
            } catch (RemoteException e) {
                return false;
            }
        }
        return true;
    }

系统竟然判断的是android包名是否具有这个权限,有点扯,不理解为什么判断的是系统包名,而不是APP包名。但是没有办法我们只能另外找办法。

继续往上照,发现allowed != ActivityManager.APP_START_MODE_NORMAL这里还有一个判断。也就是说allowed如果等于ActivityManager.APP_START_MODE_NORMAL,就不用走后面的判断了,我们朝着这个方向进行尝试。

最终发现在ActivityManagerService的appRestrictedInBackgroundLocked方法中有这样一个判断:

// Apps that target O+ are always subject to background check
        if (packageTargetSdk >= Build.VERSION_CODES.O) {
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
            }
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }

targetSDK>=26,就直接返回APP_START_MODE_DELAYED_RIGID。所以,我们把APM_APP的targetSDK改为25在进行尝试,发现果然可行。

至此,和系统通讯方案的尝试终于有了结论,我们最终决定使用静态广播的方式来实现,它具有以下几大优势:
1.保活。哪怕APM_APP被杀死,下次广播来的时候系统也会帮忙重新启动对应进程。

2.对系统完全没有侵入性,0改动,移植到其它车型的系统也完全可用。

3.ANR和CRASH上报方案是一致的,不需要区别对应,方便后续维护管理。

四.车载系统APM框架设计和实现

前面探索出来了实现的原理,则最终实现起来就简单的多,

主要分为3个模块:

1.dropbox广播注册者:负责接收异常信息;

2.DB存储:负责记录已经上报过的日志信息;

3.上报模块:负责具体异常信息的上报。

4.1 异常信息接收

创建广播接收者

首先,创建一个广播接收者DropBoxBroadcast。

在onReceive方法中,会接受到系统传递过来的两个参数,分别为tag和time。

tag的形式为:data_app_crash或者data_app_anr,

time就是时间戳的形式。

两个参数一组合,就可以得到文件名,两者以tag@time的方式进行组合。

广播接收到,就意味着异常日志文件已经生成完成,我们直接去解析对应的异常文件,得到我们想要的参数进行上报即可。

需要注意的是,ANR类型的日志文件是gz格式的,需要进行解压缩后才能读取和使用。

广播注册

然后,通过静态注册的方式注册一个广播接收者,action为android.intent.action.DROPBOX_ENTRY_ADDED。

<receiver
            android:name=".broadcast.DropBoxBroadcast"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.DROPBOX_ENTRY_ADDED" />
            </intent-filter>
        </receiver>

4.2 兜底方案处理

虽然我们的广播是可以保活的,但是为了考虑种种情况,我们还是做了一个兜底方案。

即APP启动之后,通过线程延时去读区dropbox中的所有文件,和本地已经上传成功的做对比,找出来那些没有上传过的,进行上传。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/97603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【DELM回归预测】基于海鸥算法改进深度学习极限学习机SOA-DELM实现数据回归预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

基于java+springmvc+mybatis+vue+mysql的疫情时期药物管理系统

项目介绍 前端页面&#xff1a; 功能&#xff1a;首页、医生、药品信息、药品资讯、个人中心、后台管理、在线客服 管理员后台管理页面&#xff1a; 功能&#xff1a;首页、个人中心、用户管理、医生管理、药品类别管理、药品信息管理、药品购买管理、在线咨询管理、系统管理…

Python图像处理【4】图像线性变换

图像线性变换0. 前言1. 2D 线性几何变换数学原理2. 使用 scipy.ndimage 旋转图像3. 使用 Numpy 翻转图像4. 使用 scipy.ndimage 实现仿射变换4.1 仿射变换原理4.2 实现仿射变换小结系列链接0. 前言 图像线性变换是图像处理中的基本运算&#xff0c;通常用于调整图像的视觉效果…

【Spring Cloud】如何安装与配置Nacos注册中心?

本期目录1. Nacos介绍2. Nacos安装2.1 Windows安装3. 配置Nacos3.1 引入依赖3.2 修改配置文件3.3 启动并测试1. Nacos介绍 Nacos 是阿里巴巴的产品&#xff0c;使用 Java 语言开发。比 Eureka 功能更丰富&#xff0c;除了可以作注册中心&#xff0c;还可以作配置中心。 2. Nac…

【Java开发】Staffjoy 02 :系统架构设计

本文主要对 Staffjoy 项目的架构做一个介绍和衍生&#xff0c;包括数据模型、各服务接口模型及框架选择等&#xff0c;希望能让大家对于项目有个整体的把握和判断&#xff0c;本文最后也列举了 Dubbo、Spring Cloud 和 K8s 三种微服务框架的异同~ 目录 1 架构设计 1.1 总体架…

毕业设计 基于PID控制的智能平衡车 - stm32 物联网 单片机【超详细】

文章目录0 前言1 课题背景2 设计内容3 设计方案3.1 设计思路3.3 硬件设计3.4 软件设计3.4.1 关键技术 - PID控制算法3.4.2 关键技术 - 倾角估计算法3.4.3 关键技术 - 直立控制算法3.4.4 速度控制3.4.5 方向控制4 视频演示5 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业…

长短时记忆网络(LSTM)负荷预测项目(matlab)

目录 1. LSTM介绍 2. 数据集准备及预处理 3. LSTM模型搭建与训练 4. 预测模型测试 1. LSTM介绍 长短期记忆网络 LSTM&#xff08;long short-term memory&#xff09;是 RNN 的一种变体&#xff0c;其核心概念在于细胞状态以及“门”结构。细胞状态相当于信息传输的路径…

每日一道LeetCode(一):两数之和

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

刷完这1000道JAVA面试题,让你成功逆袭上岸

内容涵盖&#xff1a;Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等技术栈。 由于整个笔记比较全面&#xff0c;内容相当的多 &#xff0c;这里仅展示面经中的面试真题&#xff0…

Keras深度学习实战(41)——语音识别

Keras深度学习实战&#xff08;41&#xff09;——语音识别0.前言1. 模型与数据集分析1.1 数据集分析1.2 模型分析2. 语音识别模型2.1 数据加载与预处理2.2 模型构建与训练小结系列链接0.前言 语音识别(Automatic Speech Recognition, ASR&#xff0c;或称语音转录文本)使声音…

openssl加密base64编码

openssl OpenSSL 是一个安全套接字层密码库&#xff0c;囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议&#xff0c;并提供丰富的应用程序供测试或其它目的使用。 首先&#xff0c;要安装 openssl: centos命令: sudo yum install openssl-devel ubuntu命令&#x…

WebService基于Baidu OCR和Map API的导航服务

哈尔滨工业大学国家示范性软件学院 《面向服务的软件系统》大作业 项目题目&#xff1a; 基于OCR和地图API的路牌定位与导航服务 项目组成员&#xff1a; 姓名 学号 李启明 120L021920 完成日期&#xff1a; 2022年 12 月 15 日 1.选题 1.1 作业…

NUS CS5477 assignment1

课程链接三维视觉 作业任务任务 课程任务就一个&#xff0c;实现一个Linear Sweep Algorithm&#xff0c;这个算法是用来检测两张图片之间的对应点。 因为SIFT检测如果把检测点的数量增大&#xff0c;可能会存在一些错误错误检测点&#xff0c;所有通常把SIFT检测的点的数量…

内网穿透:在家远程ssh访问学校内部网服务器

注册一个cpolar账号 cpolar官网注册即可&#xff08;邮箱即可&#xff09; cpolar支持http/https/tcp协议&#xff0c;不限制流量&#xff08;花生壳免费只能使用1G流量&#xff09;&#xff0c;也不需要公网ip&#xff0c;只要在服务器上安装客户端即可配置&#xff0c;免费&…

攻防世界-file_include

题目 访问路径获得源码 <?php highlight_file(__FILE__);include("./check.php");if(isset($_GET[filename])){$filename $_GET[filename];include($filename);} ?> 通过阅读php代码&#xff0c;我们明显的可以发现&#xff0c;这个一个文件包含的类型题…

Java项目:ssm校内超市管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本系统分为管理员与普通用户两种角色。采用后端SSM框架&#xff0c;前端BootStrap&#xff08;前后端不分离&#xff09;的系统架构模式&#x…

python中调用命令行执行外部程序

&#x1f31e;欢迎来到python的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f320;本阶段属于练气阶段&#xff0c;希望各位仙友顺利完成…

STM32的三种更新固件的方式

说明&#xff1a; stm32有三种更新固件的方式&#xff0c;分别为&#xff08;1&#xff09;DFU模式&#xff08; Development Firmware Upgrade 即“开发固件升级”&#xff09;&#xff1b;&#xff08;2&#xff09;SWD/JLINK 下载 &#xff08;3&#xff09;第三方bootload…

NoSQL数据库原理与应用综合项目——HBase篇

NoSQL数据库原理与应用综合项目——HBase篇 文章目录NoSQL数据库原理与应用综合项目——HBase篇0、 写在前面1、本地数据或HDFS数据导入到HBase2、Hbase数据库表操作2.1 Java API 连接HBase2.2 查询数据2.3 插入数据2.4 修改数据2.5 删除数据3、Windows远程连接HBase4、数据及源…

springboot常用组件集成

今天与大家分享spring-mybatis、reids集成&#xff0c;druid数据库连接池。如果有问题&#xff0c;望指教。 1. 创建项目 File -> New -> project ...Spring Initializr选择项目需要的第三方组件注&#xff1a;可以参考第二次课演示的操作步骤&#xff0c;有详细的拷图…