当前位置: 首页 > news >正文

Android性能优化—卡顿分析与布局优化

一、什么是卡顿?或者说我们怎么感知APP卡顿?

这里面涉及到android UI渲染机制,我们先了解一下android UI是怎么渲染的,android的View到底是如何一步一步显示到屏幕上的?

android系统渲染页面流程:

1)通过 LayoutInflater 将 View 组件解析成 View 对象,对象中封装了组件位置 、显示图片等信息,加载到内存中;

2)CPU 将 View 对象进行计算处理,最终得到该组件对应的多维向量图形 ( 使用向量表示的图形 ) ;

3)GPU 接收上述多维向量图形,GPU 将该向量图进行栅格化,将向量图转为位图 ( 矢量图转为像素图 ) ,计算出对应屏幕上每个像素点显示的值,将图像数据写入到 Back Buffer;

4)Android系统每隔大概16.6ms发出VSYNC信号,触发对UI进行下一帧的渲染,显示屏会使用Frame Buffer跟Back buffer进行交互,拿到最新的一帧数据的渲染到屏幕上。

这个渲染过程如果每次渲染都成功,就能够达到一个流畅的画面,如果16.6ms内CPU和GPU无法处理完一帧画面,就会导致Frame Buffer没能交换,导致上一帧被重复显示,即丢了一帧,当丢帧频率越高时,用户越能感觉画面卡顿。

这里的16.6ms刷新一帧是由人眼对于每秒60帧的刷新频率感觉是很流畅的,计算出来即一帧16.6ms,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。 

二、Systrace&CPU Profiler卡顿分析:

Systrace是Android平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。Systrace主要用来分析绘制性能方面的问题。在发生卡顿时,通过这份报告可以知道当前整个系统所处的状态,从而帮助开发者更直观的分析系统瓶颈,改进性能。

也可以使用上一节说的CPU Profiler进行卡顿分析,CPU Profiler不仅能分析出代码卡顿时间,还能精准的定位到代码内容。连接:http://t.csdn.cn/WGzhA

三、App层面监控卡顿:

目前业界两种主流有效的app监控方式如下:

1)利用UI线程的Looper打印的日志匹配;

2)使用Choreographer.FrameCallback。

1、Looper日志检测卡顿

Android主线程更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。简单来说,Android使用消息机制进行UI更新,UI线程有个Looper,在其loop方法中会不断取出message,调用其绑定的Handler在UI线程执行。如果在handler的dispatchMesaage方法里有耗时操作,就会发生卡顿。

public static void loop() {//......for (;;) {//......Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}msg.target.dispatchMessage(msg);if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}//......}
}

只要检测 msg.target.dispatchMessage(msg) 的执行时间,就能检测到部分UI线程是否有耗时的操作。注意到系统源码的这行代码的执行前后,有两个logging.println函数,如果设置了logging,会分别打印出>>>>> Dispatching to和<<<<< Finished to 这样的日志,这样我们就可以通过两次log的时间差值,来计算dispatchMessage的执行时间,从而设置阈值判断是否发生了卡顿。这里我们可以自定义LogMonitor,并设置到Looper中,替换系统的LogMonitor来实现,这种方式也是 BlockCanary 的实现原理。

系统Looper和Printer 接口:

public final class Looper {private Printer mLogging;public void setMessageLogging(@Nullable Printer printer) {mLogging = printer;}
}public interface Printer {void println(String x);
}

自定义BlockCanary:

public class BlockCanary {public static void install() {LogMonitor logMonitor = new LogMonitor();Looper.getMainLooper().setMessageLogging(logMonitor);}
}

自定义LogMonitor:

public class LogMonitor implements Printer {private StackSampler mStackSampler;private boolean mPrintingStarted = false;private long mStartTimestamp;// 卡顿阈值private long mBlockThresholdMillis = 3000;//采样频率private long mSampleInterval = 1000;private Handler mLogHandler;public LogMonitor() {mStackSampler = new StackSampler(mSampleInterval);HandlerThread handlerThread = new HandlerThread("block-canary-io");handlerThread.start();mLogHandler = new Handler(handlerThread.getLooper());}@Overridepublic void println(String x) {//从if到else会执行 dispatchMessage,如果执行耗时超过阈值,输出卡顿信息if (!mPrintingStarted) {//记录开始时间mStartTimestamp = System.currentTimeMillis();mPrintingStarted = true;mStackSampler.startDump();} else {final long endTime = System.currentTimeMillis();mPrintingStarted = false;//出现卡顿if (isBlock(endTime)) {notifyBlockEvent(endTime);}mStackSampler.stopDump();}}private void notifyBlockEvent(final long endTime) {mLogHandler.post(new Runnable() {@Overridepublic void run() {//获得卡顿时 主线程堆栈List<String> stacks = mStackSampler.getStacks(mStartTimestamp, endTime);for (String stack : stacks) {Log.e("block-canary", stack);}}});}private boolean isBlock(long endTime) {return endTime - mStartTimestamp > mBlockThresholdMillis;}
}

自定义StackSampler:

public class StackSampler {public static final String SEPARATOR = "\r\n";public static final SimpleDateFormat TIME_FORMATTER =new SimpleDateFormat("MM-dd HH:mm:ss.SSS");private Handler mHandler;private Map<Long, String> mStackMap = new LinkedHashMap<>();private int mMaxCount = 100;private long mSampleInterval;//是否需要采样protected AtomicBoolean mShouldSample = new AtomicBoolean(false);public StackSampler(long sampleInterval) {mSampleInterval = sampleInterval;HandlerThread handlerThread = new HandlerThread("block-canary-sampler");handlerThread.start();mHandler = new Handler(handlerThread.getLooper());}/*** 开始采样 执行堆栈*/public void startDump() {//避免重复开始if (mShouldSample.get()) {return;}mShouldSample.set(true);mHandler.removeCallbacks(mRunnable);mHandler.postDelayed(mRunnable, mSampleInterval);}public void stopDump() {if (!mShouldSample.get()) {return;}mShouldSample.set(false);mHandler.removeCallbacks(mRunnable);}public List<String> getStacks(long startTime, long endTime) {ArrayList<String> result = new ArrayList<>();synchronized (mStackMap) {for (Long entryTime : mStackMap.keySet()) {if (startTime < entryTime && entryTime < endTime) {result.add(TIME_FORMATTER.format(entryTime)+ SEPARATOR+ SEPARATOR+ mStackMap.get(entryTime));}}}return result;}private Runnable mRunnable = new Runnable() {@Overridepublic void run() {StringBuilder sb = new StringBuilder();StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();for (StackTraceElement s : stackTrace) {sb.append(s.toString()).append("\n");}synchronized (mStackMap) {//最多保存100条堆栈信息if (mStackMap.size() == mMaxCount) {mStackMap.remove(mStackMap.keySet().iterator().next());}mStackMap.put(System.currentTimeMillis(), sb.toString());}if (mShouldSample.get()) {mHandler.postDelayed(mRunnable, mSampleInterval);}}};
}

2、Choreographer.FrameCallback检测卡顿

Android系统每隔16ms发出VSYNC信号,来通知界面进行重绘、渲染,每一次同步的周期约为16.6ms,代表一帧的刷新频率。通过Choreographer类设置它的FrameCallback函数,当每一帧被渲染时会触发回调FrameCallback.doFrame (long frameTimeNanos) 函数。frameTimeNanos是底层VSYNC信号到达的时间戳 。

public class ChoreographerHelper {public static void start() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {long lastFrameTimeNanos = 0;@Overridepublic void doFrame(long frameTimeNanos) {//上次回调时间if (lastFrameTimeNanos == 0) {lastFrameTimeNanos = frameTimeNanos;Choreographer.getInstance().postFrameCallback(this);return;}long diff = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000;if (diff > 16.6f) {//掉帧数int droppedCount = (int) (diff / 16.6);}lastFrameTimeNanos = frameTimeNanos;Choreographer.getInstance().postFrameCallback(this);}});}}
}

通过 ChoreographerHelper 可以实时计算帧率和掉帧数,实时监测App页面的帧率数据,发现帧率过低,还可以自动保存现场堆栈信息。

Looper比较适合在发布前进行测试或者小范围灰度测试然后定位问题,ChoreographerHelper适合监控线上环境的 app 的掉帧情况来计算 app 在某些场景的流畅度然后有针对性的做性能优化。

四、布局优化 

1、Layout Inspector层级优化 

measure、layout、draw这三个过程都包含自顶向下的View Tree遍历耗时,如果视图层级太深自然需要更多的时间来完成整个绘测过程,从而造成启动速度慢、卡顿等问题。而onDraw在频繁刷新时可能多次出发,因此onDraw更不能做耗时操作,同时需要注意内存抖动。

使用Layout Inspector来检查应用的视图层次结构,

选择需要查看的进程与Activity,在id为content之下的就是我们写在XML中的布局。 

排查是否存在Layout的多层嵌套,我们应该尽量减少其层级,也可以使用 ConstraintLayout 约束布局使得布局尽量扁平化,移除非必需的UI组件。 

2、使用merge标签: 

当我们有一些布局元素需要被多处使用时,这时候我们会考虑将其抽取成一个单独的布局文件。在需要使用的地方通过 include 加载。 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"android:orientation="vertical"><!-- include layout_merge布局 --><include layout="@layout/layout_merge" />
</LinearLayout>
<!-- layout_merge -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#ffffff"android:text="测试merge" />
</LinearLayout>

这时候我们的主布局文件是垂直的LinearLayout,include的 "layout_merge" 也是垂直的LinearLayout,这时候include的布局中使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用merge标签优化。 

<!-- layout_merge -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"><TextViewandroid:background="#ffffff"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试merge" />
</merge>

修改为merge后,通过LayoutInspector能够发现,include的布局中TextView直接被加入到父布局中。 

3、使用ViewStub 标签:

当我们布局中存在一个View/ViewGroup,在某个特定时刻才需要他的展示时,可能会把这个元素在xml中定义为invisible或者gone,在需要显示时再设置为visible可见。比如在登陆时,如果密码错误在密码输入框上显示提示。

1)invisible

view设置为invisible时,view在layout布局文件中会占用位置,但是view为不可见,该view还是会创建对象,会被初始化,会占用资源。 

2)gone

view设置gone时,view在layout布局文件中不占用位置,但是该view还是会创建对象,会被初始化,会占用资源。 

如果view不一定会显示,此时可以使用 ViewStub 来包裹此View 以避免不需要显示view但是又需要加载view消耗资源。viewstub是一个轻量级的view,它不可见,不用占用资源,只有设置viewstub为visible或者调用其inflater()方法时,其对应的布局文件才会被初始化。 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"android:orientation="vertical"><ViewStubandroid:id="@+id/viewStub"android:layout_width="600dp"android:layout_height="500dp"android:inflatedId="@+id/textView"android:layout="@layout/layout_viewstub" />
</LinearLayout>
<!-- layout_viewstub -->
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#ffffff"android:text="测试viewStub" />

加载viewStub后,可以通过 inflatedId 找到layout_viewstub 中的根View。

五、过度渲染: 

过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。 

1、GPU 过度绘制检查

手机开发者选项中能够显示过度渲染检查功能,通过对界面进行彩色编码来帮我们识别过度绘制。开启步骤如下:

1. 进入开发者选项 (Developer Options)。

2. 找到调试 GPU 过度绘制(Debug GPU overdraw)。

3. 在弹出的对话框中,选择显示过度绘制区域(Show overdraw areas)。 

Android 将按如下方式为界面元素着色,以确定过度绘制的次数: 

1. 真彩色:没有过度绘制
2. 蓝色:过度绘制 1 次
3. 绿色:过度绘制 2 次
4. 粉色:过度绘制 3 次
5. 红色:过度绘制 4 次或更多次 

有些过度绘制是不可避免的。在优化应用的界面时,应尝试达到大部分显示真彩色或仅有 1 次过度绘制(蓝色)的视觉效果。 

2、解决过度绘制问题:

可以采取以下几种策略来减少甚至消除过度绘制:

1. 移除布局中不需要的背景:

默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容。但是,当布局具有背景时,其有可能会导致过度绘制。移除不必要的背景可以快速提高渲染性能。不必要的背景可能永远不可见,因为它会被应用在该视图上绘制的任何其他内容完全覆盖。例如,当系统在父视图上绘制子视图时,可能会完全覆盖父视图的背景。

2.使视图层次结构扁平化:

可以通过优化视图层次结构来减少重叠界面对象的数量,从而提高性能。

3.降低透明度:

对于不透明的 view ,只需要渲染一次即可把它显示出来。但是如果这个 view 设置了 alpha 值,则至少需要渲染两次。这是因为使用了 alpha 的 view 需要先知道混合 view 的下一层元素是什么,然后再结合上层的 view 进行Blend混色处理。透明动画、淡入淡出和阴影等效果都涉及到某种透明度,这就会造成了过度绘制。可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制。例如,如需获得灰色文本,可以在 TextView 中绘制黑色文本,再为其设置半透明的透明度值。但是,简单地通过用灰色绘制文本也能获得同样的效果,而且能够大幅提升性能。

六、布局加载优化: 

1、异步加载 

LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。 

2、添加依赖: 

dependencies {implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
}

3、使用AsyncLayoutInflater

new AsyncLayoutInflater(this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {@Overridepublic void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {setContentView(view);//......}});

1. 使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的;

2. 所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异步线程默认没有调用 Looper.prepare );

3. AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2;

4. 不支持加载包含 Fragment 的 layout;

5. 如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局。

相关文章:

Android性能优化—卡顿分析与布局优化

一、什么是卡顿&#xff1f;或者说我们怎么感知APP卡顿&#xff1f; 这里面涉及到android UI渲染机制&#xff0c;我们先了解一下android UI是怎么渲染的&#xff0c;android的View到底是如何一步一步显示到屏幕上的&#xff1f; android系统渲染页面流程&#xff1a; 1&…...

【二分+滑动窗口优化DP】CF883 I

Problem - 883I - Codeforces 题意&#xff1a; 思路&#xff1a; 首先&#xff0c;要让最大值最小&#xff0c;很显然要二分 那么就相当于有了一个极差的限制&#xff0c;看能不能分组&#xff0c;每组至少m个元素 那么就是考虑分段DP&#xff0c;直接n^2很容易写 但是n …...

4.netty源码分析

1.pipeline调用handler的源码 //pipeline得到双向链表的头,next到尾部, 2.心跳源码 主要分析IdleStateHandler3个定时任务内部类 //考虑了网络传输慢导致出站慢的情况 //超时重新发送,然后关闭 ReadTimeoutHandler(继承IdleStateHandler 直接关闭连接)和WriteTimeoutHandler(继…...

性能优化点

Arts and Sciences - Computer Science | myUSF 索引3层&#xff08;高度为3&#xff09;一般对于数据库地址千万级别的表 大于2000万的数据进行分库分表存储 JVM整体结构及内存模型 JVM调优&#xff1a;主要为减少FULL GC的执行次数或者减少FULL GC执行时间 Spring Boot程序…...

leetcode301. 删除无效的括号(java)

删除无效的括号 leetcode301. 删除无效的括号题目描述暴力搜索 剪枝代码演示 回溯算法 leetcode301. 删除无效的括号 难度 困难 https://leetcode.cn/problems/remove-invalid-parentheses/description/ 题目描述 给你一个由若干括号和字母组成的字符串 s &#xff0c;删除最小…...

快速制作美容行业预约小程序

随着科技的不断进步&#xff0c;移动互联网的快速发展&#xff0c;小程序成为了很多行业迅速发展的利器。对于美容行业来说&#xff0c;一款美容预约小程序不仅可以方便用户进行预约&#xff0c;还可以提升美容店铺的服务质量和管理效率。下面&#xff0c;我们来介绍一下如何快…...

Golang之路---03 面向对象——结构体

结构体 结构体定义 在之前学过的数据类型中&#xff0c;数组与切片&#xff0c;只能存储同一类型的变量。若要存储多个类型的变量&#xff0c;就需要用到结构体&#xff0c;它是将多个任意类型的变量组合在一起的聚合数据类型。 每个变量都成为该结构体的成员变量。   可以理…...

【网络编程】poll

主旨思想 用一个结构体记录文件描述符集合&#xff0c;并记录用户态状态和内核态状态 函数说明 概览 #include <poll.h> struct pollfd { int fd; /* 委托内核检测的文件描述符 */ short events; /* 委托内核检测文件描述符的什么事件 */ short revents; /* 文件描述…...

配置VS Code 使其支持vue项目断点调试

起因 每个应用&#xff0c;不论大小&#xff0c;都需要理解程序是如何运行失败的。当我们写的程序没有按照自己写的逻辑走的时候&#xff0c;我们就会逐步一一排查问题。在平常开发过程中我们可能会借助 console.log 来排查,但是现在我们可以借助 VS Code 断点来调试项目。 前…...

第一百零一回 如何在组件树之间共享数据

文章目录 概念介绍使用方法示例代码 我们在上一章回中介绍了"如何实现文件存储"相关的内容&#xff0c;本章回中将介绍 如何实现组件之间共享数据。闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 数据共享是程序中常用的功能&#xff0c;本章回介绍如何…...

Golang进阶学习

Golang进阶学习 视频地址&#xff1a;https://www.bilibili.com/video/BV1Pg41187AS?p35 1、包 1.1、包的引入 使用包的原因&#xff1a; 我们不可能把所有函数放在同一个源文件中&#xff0c;可以分门别类的放在不同的文件中 解决同名问题&#xff0c;同一个文件中不可以…...

【Linux】常用的基本指令

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…...

栈溢出几种情况及解决方案

一、局部数组过大。当函数内部的数组过大时&#xff0c;有可能导致堆栈溢出。 二、递归调用层次太多。递归函数在运行时会执行压栈操作&#xff0c;当压栈次数太多时&#xff0c;也会导致堆栈溢出。 三、指针或数组越界。这种情况最常见&#xff0c;例如进行字符串拷贝&#…...

go 内存分配

关注 go 语言内存分配策略&#xff0c;主要是想了解 go 的性能。申请不同大小的内存&#xff0c;性能开销是有差别的&#xff0c;申请内存越大&#xff0c;耗时也越久&#xff0c;性能也越差。 内存分配 参考 Go1.17.13 版本源码&#xff0c;从内存分配大小上区分了 tiny、sm…...

Maven pom.xml文件中build,plugin标签的具体使用

<build> 标签 <build> 标签是 pom.xml 文件中一个重要的标签&#xff0c;用于配置 Maven 项目的构建过程。在 <build> 标签下&#xff0c;可以配置构建相关的设置&#xff0c;包括源代码目录、输出目录、插件配置等。 以下是 <build> 标签的详细使用方…...

批量插入数据、MVC三层分离

八、批量插入数据 1、使用Statement&#xff08;&#xff09; 2、使用PreparedStatement() 3、使用批量操作API 4、优化 九、MVC三层分离...

【IMX6ULL驱动开发学习】21.Linux驱动之PWM子系统(以SG90舵机为例)

1.设备树部分 首先在 imx6ull.dtsi 文件中已经帮我们定义好了一些pwm的设备树节点&#xff0c;这里以pwm2为例 pwm2: pwm02084000 {compatible "fsl,imx6ul-pwm", "fsl,imx27-pwm";reg <0x02084000 0x4000>;interrupts <GIC_SPI 84 IRQ_TYP…...

el-cascader级联选择器加载远程数据、默认开始加载固定条、可以根据搜索加载远程数据。

加载用户列表分页请求、默认请求20条数据。想添加远程搜索用户功能。原有的方法filter-method不能监听到输入清空数据的时候。这样搜索完无法返回默认的20条数据。直接监听级联选择的v-model绑定的值是无法检测到用户自己输入的。 解决思路&#xff1a; el-cascader 没有提供…...

大数据技术之Clickhouse---入门篇---SQL操作、副本

星光下的赶路人star的个人主页 积一勺以成江河&#xff0c;累微尘以崇峻极 文章目录 1、SQL操作1.1 Insert1.2 Update 和 Delete1.3 查询操作1.4 alter操作1.5 导出数据 2、副本2.1 副本写入流程2.2 配置步骤 1、SQL操作 基本上来说传统关系型数据库&#xff08;以 MySQL 为例…...

【Rust 基础篇】Rust Sized Trait:理解Sized Trait与动态大小类型

导言 Rust是一门以安全性和性能著称的系统级编程语言。在Rust中&#xff0c;类型大小的确定在编译期是非常重要的。然而&#xff0c;有些类型的大小在编译期是无法确定的&#xff0c;这就涉及到了Rust中的动态大小类型&#xff08;DST&#xff09;。为了保证在编译期可以确定类…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…...