(二)Android 性能优化 Memory Profiler

(九)订阅/取消订阅不匹配(比如Service业务监听/广播反注册)

  • Service注册监听如果不取消,Service会一致持有页面引用,导致内存泄漏。
  • BroadcastReceiver不止被Activity引用,还可能会被AMS等系统服务、管理器等之类的引用,导致BroadcastReceiver无法被回收,而BroadcastReceiver中又持有着Activity的引用(即:onReceive方法中的参数Context),会导致Activity也无法被回收(虽然Activity回调了onDestroy方法,但并不意味着Activity被回收了),从而导致严重的内存泄漏。
  • 解决方案:及时取消订阅。

(十)任务不及时取消(Service/WebView/Retrofit/Rxjava等)

  • 任务未关闭,肯定会有内存泄漏,更可怕的是,如果在正常的销毁流程中做了一些重置工作,而再后续有任务继续反馈工作时,可能会空指针等各种异常。
  • 解决方案:任务及时取消,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)实操环境

Android Studio 4.0  Gradle version 6.1.1  Android API version 30

(2)打开路径

  • profiler打开位置:View -> Tool Windows -> Profiler
  • 当然也可以直接运行程序并启用profiler监控:Run -> Profiler

(3)工具预览

profiler整体概览

(4)详情页面

memory-profiler详情页

窗口详细说明:

  • 窗口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)查看内存分配

在时间轴上拖动以选择要查看哪个区域的分配

memory-profiler查看内存分配

对各个窗口进行说明:

  • 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)捕获堆转储

memory-profiler捕获堆转储

窗口说明:

  • 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”);
}
}

private static class StaticHandler extends Handler {
private WeakReference reference;

public StaticHandler(Activity activity) {
super(activity.getMainLooper());
reference = new WeakReference<>(activity);
}

@Override
public void handleMessage(@NonNull Message msg) {
Log.d(TAG, "StaticHandler handleMessage: " + msg.what);
Activity activity = reference.get();
if (null != activity) {
Log.d(TAG, “StaticHandler handleMessage: ui update”);
((TextView) activity.findViewById(R.id.memory_type_result)).
setText(“MEMORY_NORMAL_STATIC_WEAK”);
}
}
}

(二)MemoryProfilerActivity界面如下:

写在最后

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料

如果你熟练掌握以下列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包,最后觉得有帮助、有需要的朋友可以点个赞


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包**,最后觉得有帮助、有需要的朋友可以点个赞

[外链图片转存中…(img-FnqE89UW-1715481756113)]

[外链图片转存中…(img-SSpecVIl-1715481756114)]

[外链图片转存中…(img-qIMjUIHu-1715481756114)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值