设计模式学习笔记
设计模式系列学习视频
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- 任务未关闭,肯定会有内存泄漏,更可怕的是,如果在正常的销毁流程中做了一些重置工作,而再后续有任务继续反馈工作时,可能会空指针等各种异常。
- 解决方案:任务及时取消,follow宿主的生命周期,不要让其苟活……
(十一)执行频率高的地方或循环中创建对象(onMeasure/onDraw)
- 在循环体内创建对象、自定义View的onDraw()创建对象、大量初始化Bitmap、频繁创建大内存对象。
- 解决方案:(1) 尽量避免在循环体内创建对象,应该把对象创建移到循环体外;(2)自定义View的onDraw()方法会被频繁调用,在这里面不应该频繁的创建对象;(3)对于能够复用的对象,可以使用对象池将它们缓存起来。
(十二)Toast显示
- Toast里面有一个LinearLayout,这个Context就是作为LinearLayout初始化的参数,它会一直持有Activity,Toast显示是有时间限制的(异步任务),如果Toast还在显示Activity就销毁了,由于Toast显示没有结束不会结束生命周期,这个时候Activity就内存泄漏了。
- 解决方案:(1)不要直接Toast,自己封装一个ToastUtil,使用ApplicationContext来调用(或者通过getApplicationContext来调用);(2)还有一种通过toast变量的cancel来取消这个显示
小憩一下,正式开始
为什么选择 memory profiler?
Memory Profiler 是 Android Profiler中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄露和内存抖动。它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。 Memory Profiler 加入了自动检查 Activity 和 Fragment 中的内存泄漏的功能。使用这一功能非常的简单。
实操体验
(1)实操环境
- 可选项,用自己的环境和代码也一样
- SamplePop代码下载
- SamplePop环境如下:
Android Studio 4.0 Gradle version 6.1.1 Android API version 30
(2)打开路径
- profiler打开位置:View -> Tool Windows -> Profiler
- 当然也可以直接运行程序并启用profiler监控:Run -> Profiler
(3)工具预览
(4)详情页面
窗口详细说明:
- 窗口1:分别由以下几个功能按钮组成
性能分类切换按钮,包括:cpu、memeory、network、energy。
用于强制执行垃圾回收事件的按钮
用于捕获堆转储]的按钮
用于指定分析器多久捕获一次内存分配的下拉菜单:
Full:捕获内存中的所有对象分配
Sampled:定期对内存中的对象分配进行采样
None:停止跟踪应用的内存分配
- 窗口2:页面调整按钮集合,包括:缩小,放大、重置、暂停、开始等。
- 窗口3:事件时间轴,显示Activity的生命周期不同状态,用户交互事件,如点击,旋转等。
- 窗口4:内存计数图例:
Java:从 Java 或 Kotlin 代码分配的对象的内存
Native:从 C 或 C++ 代码分配的对象的内存
Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存
Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存
Code:您的应用用于处理代码和资源(如 dex 字节码、 dex 代码、.so 库和字体)的内存
Others:您的应用使用的系统不确定如何分类的内存
Allocated:您的应用分配的 Java/Kotlin 对象数。此数字没有计入 C 或 C++ 中分配的对象
- 窗口5:内存使用量时间轴,它会显示以下内容:
1.一个堆叠图表,显示每个内存类别当前使用多少内存,如左侧的 y 轴以及顶部的彩色键所示
2.一条虚线,表示分配的对象数,如右侧的 y 轴所示
3.每个垃圾回收事件的图标。
(5)查看内存分配
在时间轴上拖动以选择要查看哪个区域的分配
对各个窗口进行说明:
- 1.选定范围:可以拖动选择想分析的数据部分
- 2.选择要检查的堆:
image heap:系统启动映像,包含启动期间预加载的类。此处的分配保证绝不会移动或消失
zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的
app heap:您的应用在其中分配内存的主堆
JNI heap:显示 Java 原生接口 (JNI) 引用被分配和释放到什么位置的堆
- 3.选择如何安排分配:
Arrange by class:根据类名称对所有分配进行分组。这是默认选项
Arrange by package:根据软件包名称对所有分配进行分组
Arrange by callstack:将所有分配分组到其对应的调用堆栈
- 4.过滤器:搜索过滤,matchCase区分大小写,Regex使用正则表达式
- 5.类名窗口,点击可以进入Instance View(实例窗口)
- 6.实例窗口,点击进入Call Stack(实例分配的线程)
- 7.线程窗口,显示该实例被分配到何处以及在哪个线程中,右键 -> Jump to Source
从上图可以查看对象分配的以下信息:
- 分配了哪些类型的对象以及它们使用多少空间。
- 每个分配的堆栈轨迹,包括在哪个线程中。
- 对象在何时被取消分配。
(6)捕获堆转储
窗口说明:
- 1.执行堆转储:我在MemoryProfilerActivity页面做了一些操作后,back回到MainActivity,等待1s后,执行堆转储(本次主要分析MemoryProfilerActivity的泄漏问题)
- 2.页面泄漏过滤:筛选Activity 和 Fragment 实例存在内存泄露的分析数据,过滤器显示的数据类型包括:
已销毁但仍被引用的 Activity 实例(t调试技巧:旋转屏幕/应用切换) 没有有效的 FragmentManager 但仍被引用的 Fragment 实例
- 3.本项目类过滤:只显示本项目的类
- 4.类窗口详细信息:
Allocations:堆中的分配数
Native Size : 此对象类型使用的原生内存总量(以字节为单位),你会在此处看到采用 Java 分配的某些对象的内存,因为 Android 对某些框架类(如 Bitmap)使用原生内存
Shallow Size:此对象类型使用的 Java 内存总量(以字节为单位)
Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)
- 5.实例窗口详细信息:
Depth:从任意 GC 根到选定实例的最短跳数
Native Size:原生内存中此实例的大小
Shallow Size:Java 内存中此实例的大小
Retained Size:此实例所支配内存的大小
- 6.实例引用:显示对应实例对象的每个引用,可以右键 -> Jump to Source/Go to Instance(跳转到相对应的实例进行查看)
捕获堆转储后,您可以查看以下信息:
- 您的应用分配了哪些类型的对象,以及每种对象有多少。
- 每个对象当前使用多少内存。
- 在代码中的什么位置保持着对每个对象的引用。
- 对象所分配到的调用堆栈
(7)示例代码
(一)Handler内存泄漏演练,先看下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: ");
setContentView(R.layout.activity_memory_profiler);
mResultTv = findViewById(R.id.memory_type_result);
((RadioGroup) findViewById(R.id.memory_handler_type)).
setOnCheckedChangeListener(mOnCheckedChangeListener);
}
private RadioGroup.OnCheckedChangeListener mOnCheckedChangeListener =
new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i) {
case R.id.handler_no_static:
memoryTestType = MEMORY_ERROR_NO_STATIC;
break;
case R.id.handler_inner_class:
memoryTestType = MEMORY_ERROR_INNER_CLASS;
break;
case R.id.handler_static_weak:
memoryTestType = MEMORY_NORMAL_STATIC_WEAK;
break;
case R.id.handler_no_static_clear_message:
memoryTestType = MEMORY_NORMAL_MESSAGE_CLEAR;
break;
default:
break;
}
}
};
public void onMemoryHandlerMonitor(View view) {
Log.d(TAG, "onMemoryHandlerMonitor: ");
monitorHandler();
}
//模拟Handler泄漏(memory profiler以这个来演示实操)
private static final int MESSAGE_DELAY = 5 * 1000;
private static final int MEMORY_ERROR_NO_STATIC = 1;
private static final int MEMORY_ERROR_INNER_CLASS = 2;
private static final int MEMORY_NORMAL_STATIC_WEAK = 3;
private static final int MEMORY_NORMAL_MESSAGE_CLEAR = 4;
//只要动态修改此参数就行
private int memoryTestType = MEMORY_ERROR_NO_STATIC;
TextView mResultTv;
private Handler mHandler;
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy: ");
if (MEMORY_NORMAL_MESSAGE_CLEAR == memoryTestType) {
mHandler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
//Button click 触发模拟操作
private void monitorHandler() {
Log.d(TAG, "monitorHandler: ");
switch (memoryTestType) {
case MEMORY_ERROR_NO_STATIC:
//泄漏1 : 使用no-static内部类
mHandler = new NoStaticHandler();
break;
case MEMORY_ERROR_INNER_CLASS:
//泄漏2:使用匿名内部类
mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
Log.d(TAG, "inner class handler handleMessage: " + msg.what);
mResultTv.setText(“MEMORY_ERROR_INNER_CLASS”);
}
};
break;
case MEMORY_NORMAL_STATIC_WEAK:
//不泄露3:静态+弱引用(为了保证Handler中消息队列中的所有消息都能被执行,
//推荐使用 静态内部类 + 弱引用的方式)
mHandler = new StaticHandler(this);
break;
case MEMORY_NORMAL_MESSAGE_CLEAR:
//不泄露4:destroy()方法对handler进行消息队列清空,结束Handler生命周期
mHandler = new NoStaticHandler();
break;
default:
Log.e(TAG, “monitorHandler: default no handle”);
return;
}
//执行事件延迟队列发送
mHandler.sendEmptyMessageDelayed(1, MESSAGE_DELAY);
}
//no-static内部类:自定义Handler子类
class NoStaticHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "NoStaticHandler handleMessage: " + msg.what);
mResultTv.setText(memoryTestType == MEMORY_ERROR_NO_STATIC ?
“MEMORY_ERROR_NO_STATIC” : “MEMORY_NORMAL_MESSAGE_CLEAR”);
}
总结
【Android 详细知识点思维脑图(技能树)】
我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
最后,赠与大家一句话,共勉!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!