Android 性能优化--内存优化分析总结
一、内存优化概念
1.1 为什么要做内存优化?
内存优化一直是一个很重要但却缺乏关注的点,内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。在你认真跟踪下来可能会发现内存出现问题的地方仅仅只是一个表现的地方,并非深层次的原因,因为内存问题相对比较复杂,它是一个逐渐挤压的过程,正好在你出现问题的代码那里爆了,所以针对应用的内存问题开发者必须多加关注。
1.2 内存问题表现形式
-
内存抖动:锯齿状、GC频繁导致卡顿
-
内存泄露:可用内存逐渐减少、频繁GC
-
内存溢出:OOM、程序异常
二、常用内存分析工具
要解决内存问题,就必须要有强大的内存分析工具,让我们更快更方便的定位内存问题。目前主流的内存分析工具主要有 LeakCanary、Memory Profiler、MAT。
2.1 LeakCanary
LeakCanary是 Square 开源的一个内存泄露监控框架,在应用运行时出现的内存泄露会被 LeakCanary 监控记录。

LeakCanary 内存泄漏的 trace 分析,主要看 Leaking:NO 到 Leaking:YES 这段的 trace,可以发现 TextView 出现了内存泄漏,因为它持有了被销毁的 Activity 的上下文 Context。
更具体的 trace 分析,具体可以查看官方文档 Fixing a memory leak。
使用 LeakCanary 虽然很方便,但是也有一定弊端:
-
直接引用依赖使用的 LeakCanary 一般用于线下调试,应用发布到线上时需要关闭
-
应用调试时有时候会引起卡顿
所以 一般使用 LeakCanary 只是一种简便定位内存泄露的方式,但如果需要更好的做内存优化,比如定位内存抖动、Bitmap 优化等还是需要其他的分析工具,主要常用的有 Memory Profiler 和 MAT。
2.2 NativeSize、Shallow Size、Retained Size、Depth
后续说明 Memory Profiler 和 MAT 时,会经常出现几个比较重要的指标:Shallow Size 和 Retained Size。在 Memory Profiler 还会提供 Native Size 和 Depth。
当您拿到一段 Heap Dump 之后,Memory Profiler 会展示出类的列表。对于每个类,Allocations 这一列显示的是它的实例数量。而在它右边则依次是 Native Size、Shallow Size 和 Retained Size:
我们用下图来表示某段 Heap Dump 记录的应用内存状态。注意红色的节点,在这个示例中,这个节点所代表的对象从我们的工程中引用了 Native 对象;这种情况不太常见,但在 Android 8.0 之后,使用 Bitmap 便可能产生此类情景,因为 Bitmap 会把像素信息存储在原生内存中来减少 JVM 的内存压力。
先从 Shallow Size 讲起,这列数据其实非常简单,就是 对象本身消耗的内存大小
Depth 是从 GC Root 到达这个实例的最短路径,图中的这些数字就是每个对象的深度 (Depth)。
一个对象离 GC Root 越近,它就越有可能与 GC Root 有多条路径相连,也就越可能在垃圾回收中被保存下来。
以红色节点为例,如果从其左边来的任何一个引用被破坏,红色节点就会变成不可访问的状态并且被垃圾回收回收掉。而对于右边的蓝色节点来说,如果您希望它被垃圾回收,那您需要把左右两边的路径都破坏才行。
值得警惕的是,如果您看到某个实例的 Depth 为 1 的话,这意味着它直接被 GC Root 引用,同时也意味着它永远不会被自动回收。
下面是一个示例 Activity,它实现了 LocationListener 接口,高亮部分代码 requestLocationUpdates() 将会使用当前 Activity 实例来注册 locationManager。如果您忘记注销,这个 Activity 就会泄漏。它将永远都待在内存里,因为位置管理器是一个 GC Root,而且永远都存在:

您能在 Memory Profiler 中查看这一情况。点击一个实例,Memory Profiler 将会打开一个面板来显示谁正在引用这个实例:

我们可以看到位置管理器中的 mListener 正在引用这个 Activity。您可以更进一步,通过引用面板导航至堆的引用视图,它可以让您验证这条引用链是否是您所预期的,也能帮您理解代码中是否有泄露以及哪里有泄露。
2.3 Memory Profiler
Memory Profiler 是内置在 Android Studio 适用于查看实时内存情况 的内存分析工具。
2.3.1 Memory Profiler 界面说明
官方文档:使用 Memory Profiler 查看 Java 堆和内存分配
2.3.2 Memory Profiler 查找内存抖动
查找内存抖动还是比较简单的,运行的程序在 Memory Profiler 会呈现为在短时间内内存上下波动频繁触发 GC 回收。
内存抖动比较常见的地方:
-
自定义View的 onMeasure()、onLayout()、onDraw() 直接 new 创建对象
-
列表比如 RecyclerView 的 onBindViewHolder() 直接 new 创建对象
-
有循环的代码中创建对象
用一个简单的案例模拟内存抖动:
publicclassMainActivityextendsAppCompatActivity {
@SuppressWarnings("HandlerLeak")privateHandler mHandler = newHandler() {@OverridepublicvoidhandleMessage(Message msg) {// 模拟内存抖动for (int i = 0; i < 100; i++) {String[] args = newString[100000];}
mHandler.sendEmptyMessageDelayed(0, 30);}};
@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(newView.OnClickListener() {@OverridepublicvoidonClick(View v) {mHandler.sendEmptyMessage(0);}});}
}
复制代码
案例非常简单,就是点击按钮时频繁的创建对象。在真机上运行上面的程序也许不会出现锯齿状的内存波动,但是会有非常频繁的 GC 回收,如下图:

那应该怎么具体的定位到是哪里发生的内存抖动呢?

按照上面的步骤操作:
-
位置①:在程序处于运行时,点击 Record 按钮录制内存情况,然后点击 Stop 停止录制,会显示上图
-
位置②:我们可以点击 Allocations 按降序从大到小或升序从小到大查看分配对象的数量,一般我们会选择降序从大到小看数量最多的对象。上图对象数量最多的是 String 对象
-
位置③:在 Instance View 随便选择一个 String 对象,会显示下面的 Allocation Call Stack,它会显示这个对象的调用栈位置
-
位置④: 从 Allocation Call Stack 可以看到,String 对象是在 MainActivity 的第 18 行 handleMessage() 创建的,从而也定位到内存抖动的位置
上面的操作还有一些小技巧:
-
在位置①操作前,为了排除干扰,一般在录制前会先手动 GC 后再录制变化的内存;在 Android 8.0 以上的设备可以实时的拖动 Memory Profiler 选择查看的内存波动范围
-
位置②上例是直接查看 Arrange by class,但实际项目中更多的会是选择 Arrange by package 查看自己项目包名下的类
2.3.3 Memory Profiler 查找内存泄露
上面讲到内存泄露的表现是会出现内存抖动,因为出现内存泄露时可用内存不断减少,系统需要内存时获取内存不足就会 GC,所以产生内存抖动。
出现内存泄露时 Memory Profiler 会呈现一个类似阶梯型的内存上升趋势,而且内存没有降下来:

上图的内存泄漏比较明显,实际项目开发中出现内存泄漏时可能不会特别明显,运行时间比较久才能发现内存是在缓慢上升的。这时候就需要 dump heap 帮助定位。
接下来会使用 Handler 内存泄露的案例简单讲解怎么使用 Memory Profiler 分析内存泄露。
public classHandlerLeakActivityextendsAppCompatActivity{private static finalStringTAG = HandlerLeakActivity.class.getSimpleName();
privateHandler handler = newHandler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 0) {Log.i(TAG, "handler receive msg");}}};
@Overrideprotected void onCreate(@NullableBundle savedInstanceState) {super.onCreate(savedInstanceState);handler.sendEmptyMessageDelayed(0, 10 * 1000);}
}
复制代码
上面代码非常简单,就是启动 app 后,每次进入 HandlerLeakActivity 就使用 Handler 延迟 10s 发送消息,在 10s 内退出界面,不断重复操作。
1、重复多次可能内存泄露的操作,Memory Profiler 堆转储出 hprof 文件(建议在操作前先 GC 排除干扰)
2、在 Memory Profiler 查看查看堆转储文件 hprof:

可以发现经过手动 GC 后,Allocations 显示有 5 个 HandlerLeakActivity,堆转储 Instance View 下也仍显示有多个 Activity 实例,说明已经内存泄露,具体的内存泄露定位可以在 Instance View 泄露的实例类对象中点击查看,Instance View 下面的 Reference 会显示具体的引用链。
在新版本的 Memory Profiler 提供了 Activity/Fragment Leaks 复选框,选中它可以直接找到可能内存泄露的位置:

2.4 MAT
2.4.1 MAT简介
-
强大的Java Heap分析工具,查找内存泄漏及内存占用
-
生成整体报告、分析问题等
-
线下深入使用
官网下载地址:www.eclipse.org/mat/downloa… ,这个地址是不是有你熟悉的单词,嗯,没错啦,MAT是Eclipse中的一个插件,因为现在开发过程中很多人都使用了IDEA或者Android Studio,所以你不想下载Eclipse的话呢,你可以去下载MAT的独立版,解压之后里面有一个MemoryAnalyzer.exe的可执行文件,直接点击就可以使用了。
这个工具很多时候我们需要结合Android Studio的堆转储能力配合使用,但是需要注意,AS3.0之后生成的hprof文件不是标准的hprof文件了,需要使用命令转换一下:hprof-conv 原文件路径 转换后文件路径
2.4.2 MAT用法简介
①、Overview:概览信息
Top Consumers
-
通过图表展示出占用内存比较多的对象,此栏在做降低内存占用时比较有帮助
-
Biggest Objects:相对详细的信息
Leak Suspects
-
快速查看内存泄露的可疑点

②、 Histogram:直方图
-
Class Name:具体检索某一个类
-
Objects:某一个具体的Class有多少实例
-
Shallow Heap:某单一实例自己占了多少内存
-
Retained Heap:在这个引用链之上这些对象总共占了多少内存

Group by packge:将类对象以包名形式展示

List objects
-
with outgoing references:自身引用了哪些类
-
with incoming references:自身被哪些类引用

③、dominator_tree
-
每个对象的支配树
-
percentage:占所有对象的百分比

在条目上右键它也有List objects,它和Histogram之间有啥区别呢?主要区别就是下面两点:
-
Histogram:基于类的角度分析
-
dominator_tree:基于实例的角度分析
④、OQL:对象查询语言,类似于从数据库中检索内容

⑤、thread_overview:详细的展示线程信息,可以查看出当前内存中存在多少线程

三、实战内存抖动解决
3.1 内存抖动简介
-
定义:内存频繁分配和回收导致内存不稳定
-
表现:频繁GC、内存曲线呈锯齿状
-
危害:导致卡顿、严重时会导致OOM
3.2 内存抖动导致OOM
-
频繁创建对象,导致内存不足及碎片(不连续)
-
不连续的内存片无法被分配,导致OOM
3.3 实战分析
这一部分我会模拟一次内存抖动,并通过Profiler分析内存情况,定位到具体内存抖动的代码。
首先先来创建一个布局文件activity_memory.xml,里面就一个按钮,用来触发模拟内存抖动的那部分代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/btn_memory"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="模拟内存抖动"/>
</LinearLayout>
复制代码
然后定义一个MemoryShakeActivity页面,加载刚才的布局,并且在页面中定义一个Handler,当点击模拟内存抖动的按钮时,我们定时执行handleMessage中的模拟抖动的代码,整个代码都是很容易能看懂的那种:
/*** 说明:模拟内存抖动页面*/publicclassMemoryShakeActivityextendsAppCompatActivityimplementsView.OnClickListener {@SuppressLint("HandlerLeak")privatestaticHandler mHandler = newHandler(){@OverridepublicvoidhandleMessage(@NonNull Message msg) {super.handleMessage(msg);//模拟内存抖动的场景,每隔10毫秒执行一次,循环执行100次,每次通过new分配大内存for (int i=0;i<100;i++){String[] obj = newString[100000];}mHandler.sendEmptyMessageDelayed(0,10);}};@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);findViewById(R.id.btn_memory).setOnClickListener(this);}@OverridepublicvoidonClick(View view) {if (view.getId() == R.id.btn_memory){mHandler.sendEmptyMessage(0);}}@OverrideprotectedvoidonDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);}
}
复制代码
然后跑起来,我截了两张图给大家看一下,第一张是没有执行模拟抖动的代码之前的,第二张是执行之后的:


从上面两张图中可以清晰的看到第一张内存比较平稳,第二张内存图有锯齿状出现,突然出现了频繁的GC,看到下面好多小垃圾桶了没,这个时候可以初步判定应该是出现了内存抖动现象,因为比较符合它的特征,然后在面板上拖动一段距离它就会将这段时间内的内存分配情况给我们展示出来:

首先双击Allocations,然后将这一列按照从大到小的顺序排列好,然后你会发现String数组居然有这么多,它占用的内存大小也是最高的(值得关注的点我都用矩形标出了),此时我们就应该锁定这个目标,为什么String类型的数组会有这么多,这里很有可能是有问题的。然后排查究竟是哪里导致的这个问题,很简单点击String[]这一行,在右侧Instance View面板中随便点击一行,下方Allocation Call Stack面板中就出现了对应的堆栈信息,上面也列出了具体哪个类的哪一行,右键jupm to source就可以跳转到指定的源码位置,这样就找到了内存抖动出现的位置,然后我们分析代码作相应的修改即可。
流程总结:
-
使用Memory Profiler初步排查;
-
使用Memory Profiler或CPU Profiler结合代码排查
内存抖动解决技巧:找循环或者频繁调用的地方
四、实战内存泄露解决
4.1 内存泄露简介
定义:内存中存在已经没有用的对象
表现:内存抖动、可用内存逐渐变少
危害:内存不足、GC频繁、OOM
4.2 实战分析
这里还是通过代码来真实的模拟一次内存泄露的场景,对于一般的APP程序来说,最大的问题往往都是在Bitmap上,因为它消耗的内存比较多,拿它来模拟会看的比较明显。好首先来看布局文件activity_memoryleak.xml,里面就一个ImageView控件:
<?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:orientation="vertical"><ImageViewandroid:id="@+id/iv_memoryleak"android:layout_width="50dp"android:layout_height="50dp" />
</LinearLayout>
复制代码
然后定义了一个模拟处理某些业务的Callback回调接口,和一个统一管理这些回调接口的Manager类:
//模拟回调处理某些业务场景publicinterfaceCallBack {voiddpOperate();
}//统一管理CallbackpublicclassCallBackManager {publicstatic ArrayList<CallBack> sCallBacks = new ArrayList<>();publicstaticvoidaddCallBack(CallBack callBack) {sCallBacks.add(callBack);}publicstaticvoidremoveCallBack(CallBack callBack) {sCallBacks.remove(callBack);}
}
复制代码
然后在我们的模拟内存泄露的页面上设置Bitmap,并设置回调监听:
/*** 说明:模拟内存泄露页面*/
public classMemoryLeakActivityextendsAppCompatActivityimplementsCallBack{@Overrideprotected void onCreate(@NullableBundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memoryleak);ImageView imageView = findViewById(R.id.iv_memoryleak);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.big_bg);imageView.setImageBitmap(bitmap);CallBackManager.addCallBack(this);}@Overridepublic void dpOperate() {}
}
复制代码
OK,我们的代码就写完了,现在来实际运行一下,然后将这个页面连续打开关闭多次,看看这段代码会不会造成内存泄露呢?

这是我用Profiler截取的内存图片,可以看到整个内存在经过了我的反复开关页面之后虽然有的地方出现了一个小抖动,但是整体是呈阶梯状上升的,可用内存在逐渐减少,此时基本上可以断定这个界面出现了内存泄露。Profiler工具虽然可以初步帮我们断定出现了内存泄露,但是它却无法断定具体是哪里出现了内存泄露,意思就是我们还是不知道该修改哪里的代码,所以此时需要用到强大的Java Heap工具了,来有请MAT出场。
首先需要在Profiler中点击Dump Java Heap按钮,使用堆转储功能转换成一个文件,然后点击保存按钮将文件保存到本地目录下,比如我这里保存为H盘中的memoryleak.hprof文件,然后使用hprof-conv命令将其转换为标准的hprof文件,我这里是转换后的名称是:memoryleak_transed.hprof,如下所示:

然后打开MAT工具,导入刚刚生成的转换后的文件:

点击Histogram查看内存中所有存活的对象,然后我们在Class Name中可以输入内容搜索想要查找的对象:

然后可以看到该对象的具体信息,以及数量和所占用的内存大小,我这里发现内存中居然存在6个MemoryLeakActivity对象:

然后右键List objects---->with incoming references找到所有引向它的强引用:

然后右键Path To GC Roots----->with all references,将所有引用都计算在内然后算出来这个对象和GCRoot之间的路径:

来看结果,最后是到了sCallBacks这里,而且它左下角有个小圆圈,这就是我们真正要找的位置,也就是说MemoryLeakActivity是被CallBackManager这个类的sCallBacks这个对象引用了:

根据上面找的结果到代码中去找CallBackManager的sCallBacks看看这里究竟是做了什么引发的?
publicstaticArrayList<CallBack> sCallBacks = new ArrayList<>();
复制代码
MemoryLeakActivity是被sCallBacks这个静态变量引用着,由于被static关键字修饰的变量的生命周期是和App的整个生命周期一样长的,所以当MemoryLeakActivity这个页面关闭时,我们应该将变量的引用关系给释放掉,否则就出现了上面的内存泄露的问题。所以解决这个问题也很简单了,添加如下几行代码:
@OverrideprotectedvoidonDestroy() {super.onDestroy();CallBackManager.removeCallBack(this);
}
复制代码
流程总结:
-
使用Memory Profiler初步观察(可用内存逐渐减少);
-
通过Memory Analyzer结合代码确认
五、线上内存监控方案
线上内存问题最大的就是内存泄露,对于内存抖动和内存溢出它们一般都和内存泄露导致的内存无法释放相关,如果能够解决内存泄露,则线上内存问题就会减少很多。线上内存监控其实还是比较困难的,因为我们无法使用线下的这些工具来直观的发现分析问题。
5.1 常规方案
①、设定场景线上Dump
比如你的App已经占用到单个App最大可用内存的较高百分比,比如80%,通过:Debug.dumpHprofData();这行代码可以实现将当前内存信息转化为本地文件。
整个流程如下:超过内存80%——>内存Dump——>回传文件(注意文件可能很大,保持在wifi状态回传)——>MAT手动分析
总结:
-
Dump文件太大,和对象数正相关,可裁剪
-
上传失败率高、分析困难
②、LeakCanary线上使用
-
LeakCanary带到线上
-
预设泄露怀疑点
-
发现泄露回传
总结:
-
不适合所有情况,必须预设怀疑点,限制了全面性
-
分析比较耗时,也容易OOM(实践发现LeakCanary分析过程较慢,很有可能自己在分析的过程中自身发生OOM)
5.2 LeakCanary定制
-
预设怀疑点——》自动找怀疑点(谁的内存占用大就怀疑谁,大内存对象出现问题的概率更大)
-
分析泄露链路慢(分析预设对象的每一个对象)——》分析Retain Size大的对象(减少它的分析工作量,提高分析速度)
-
分析OOM(将内存堆栈生成的所有文件全部映射到内存中,比较占用内存)——》对象裁剪,不全部加载到内存
5.3 线上监控完整方案
-
待机内存、重点模块内存、OOM率
-
整体及重点模块GC次数、GC时间
-
增强的LeakCanary自动化内存泄露分析
六、内存优化技巧
优化大方向:
-
内存泄露
-
内存抖动
-
Bitmap
优化细节:
-
LargeHeap属性(虽然有点耍流氓,但还是应该向系统申请)
-
onTrimMemory、onLowMemory(系统给的低内存的回调,可以根据不同的回调等级去处理一些逻辑)
-
使用优化过的集合:SparseArray
-
谨慎使用SharedPreference(一次性load到内存中)
-
谨慎使用外部库(尽量选择经过大规模验证的外部库)
-
业务架构设计合理(加载的数据是你能用到的,不浪费内存加载无用数据)
相关文章:
Android 性能优化--内存优化分析总结
一、内存优化概念 1.1 为什么要做内存优化? 内存优化一直是一个很重要但却缺乏关注的点,内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏&…...
buuctf web 前5题
目录 一、[极客大挑战 2019]EasySQL 总结: 二、[极客大挑战 2019]Havefun 总结: 三、[HCTF 2018]WarmUp 总论: 四、[ACTF2020 新生赛]Include 总结: 五、[ACTF2020 新生赛]Exec 总结: 一、[极客大挑战 2019]…...
stable diffusion实践操作-提示词-人物服饰
系列文章目录 stable diffusion实践操作-提示词 文章目录 系列文章目录前言一、提示词汇总1.1 人物服饰11.2 人物服饰2 前言 本文主要收纳总结了提示词-人物服饰。 一、提示词汇总 1.1 人物服饰1 耳饰帽子内裤内衣上身饰品手部[月牙耳环][头顶光环][丁字裤][胸罩][披风][太…...
Tomcat加载静态资源--防止SpringMVC拦截
最简洁方式:使用API 在配置文件下写配置类SpringMvcSupport,并且让SpringMVC扫描到此文件夹ComponentScan({"com.itheima.controller","com.itheima.config"}) SpringMvcSupport配置类如下 Configuration public class SpringMvcS…...
【AI数字人】如何基于ER-NeRF自训练AI数字人
文章目录 环境配置前期准备数据预处理音频预处理训练bug测试度量标准用指定的声音进行推理参考ER-NeRF,能够以更小的模型尺寸和更快的速度训练合成逼真的3D talking portrait。 环境配置 ER-NeRF项目开源于两个月前,相比于两年前的AD-NeRF,使用更新版本的tensorflow和各种…...
多目标应用:基于多目标哈里斯鹰优化算法(MOHHO)的微电网多目标优化调度研究MATLAB
一、微网系统运行优化模型 参考文献: [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、多目标哈里斯鹰优化算法MOHHO 多目标哈里斯鹰优化算法(Multi-Objective Harris Hawks Optimizer&#…...
[运维|中间件] 东方通TongWeb忘记密码后修改密码
参考文献 Tongweb忘记密码处理办法 修改密码 以下步骤将thanos用户密码修改为 thanos123.com 编辑twusers.properties文件 vim /path/to/TongWeb7.0/conf/security/twusers.properties将thanos的内容修改为默认的密码信息: thanos3d6391e41e9c4319$3$6774c6fc9…...
无涯教程-Android Mock Test函数
本节介绍了与 Android 相关的各种模拟测试。您可以在本地计算机上下载这些样本模拟测试,并在方便时离线解决。每个模拟测试均随附一个模拟测试键,可让您验证最终分数并为自己评分。 Mock Test I Mock Test II Mock Test III Mock Test IV Q 1 -什么是Android? A -A…...
保留网络[02/3]:大型语言模型转换器的继任者”
一、说明 在这项工作中,我们提出保留网络(RETNET)作为基础架构大型语言模型的结构,同时实现训练并行, 推理成本低,性能好。我们从理论上推导出这种联系 复发与关注之间。然后我们提出保留机制 序列建模&…...
微信小程序-生成canvas图片并保存到手机相册
wxml页面 <button class"rightbtn bottomBtnCss" catch:tap"canvasImg"><image src{{imgUrl}}/images/mine/jspj-icon.png class"restNumImg"></image><text class"btnText">生成图片</text></but…...
设计模式8:代理模式-动态代理
上一篇:设计模式8:代理模式-静态代理 目录 如何理解“动态”这两个字?动态代理简单的代码实例一个InvocationHandler代理多个接口有动态代理,为什么还要用Cglib代理? 如何理解“动态”这两个字? “动态”…...
tcp字节传输(java)-自定义包头和数据识别
1、背景 tcp传输的时候会自动拆包,因此服务端接收的数据段可能跟客户端发送过来的数据段长度不一致,比如客户端一次发送10000个字节。但是服务端接收了两次才接收完整(例如第一次接收6000字节,第二次接收4000字节)。但…...
pyspark 系统找不到指定的路径; \Java\jdk1.8.0_172\bin\java
使用用具PyCharm 2023.2.1 1:pyspark 系统找不到指定的路径, Java not found and JAVA_HOME environment variable is not set. Install Java and set JAVA_HOME to point to the Java installation directory. 解决方法:配置正确环境变量…...
UE4 Physics Constraint Actor 实现钟摆效果
放入场景,然后将一个球体放入场景 选择小球 将小球改为Movable 选择模拟物理,并将小球移除平衡点 就实现了...
UE4/UE5 动画控制
工程下载 https://mbd.pub/o/bread/ZJ2cm5pu 蓝图控制sequence播放/倒播动画: 设置开启鼠标指针,开启鼠标事件 在场景中进行过场动画制作 设置控制事件...
Springboot整合shiro
导入依赖 <!-- 引入springboot的web项目的依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> <!-- shiro --><depende…...
阻塞/非阻塞、同步/异步(网络IO)
1.阻塞/非阻塞、同步/异步(网络IO) 【思考】典型的一次 IO 的两个阶段是什么? 数据就绪 和 数据读写 数据就绪 :根据系统 IO 操作的就绪状态 阻塞 非阻塞 数据读写 :根据应用程序和内核的交互方式 同步 异步 陈硕:在处理 IO …...
为什么大家会觉得考PMP没用?
一是在于PMP这套知识体系,是一套底层的项目管理逻辑框架,整体是比较抽象的。大家在学习工作之后,会有人告诉你很多职场的一些做事的规则,比如说对于沟通,有人就会告诉如何跟客户沟通跟同事相处等等,这其实就…...
AVR128单片机 USART通信控制发光二极管显示
一、系统方案 二、硬件设计 原理图如下: 三、单片机软件设计 1、首先是系统初始化 void port_init(void) { PORTA 0xFF; DDRA 0x00;//输入 PORTB 0xFF;//低电平 DDRB 0x00;//输入 PORTC 0xFF;//低电平 DDRC 0xFF;//输出 PORTE 0xFF; DDRE 0xfE;//输出 PO…...
为什么5G 要分离 CU 和DU?(4G分离RRU 和BBU)
在 Blog 一文中,5G--BBU RRU 如何演化到 CU DU?_5g rru_qq_38480311的博客-CSDN博客 解释了4G的RRU BBU 以及 5G CU DU AAU,主要是讲了它们分别是什么。但是没有讲清楚 为什么,此篇主要回答why。 4G 为什么分离基站为 RRU 和 BBU…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
