相信小伙伴们在开发过程中,最经常使用的是LeakCanary开源框架来监控内存泄漏存在的问题点,这样效率比较高,而且迅速发现问题点,其次做相应的优化处理,但LeakCanary毕竟是开源框架,由于特殊原因,可能不允许使用LeakCanary开源框架来分析潜在的内存泄漏。因此,我们经常使用内存分析工具来检测潜在的内存泄漏的场景。
我们采用Memory Profiler和 MAT分析工具来检测应用是否存在内存泄漏。
先来源码:
MemoryMonitorActivity:
public class MemoryMonitorActivity extends BaseActivity{
@BindView(R.id.btnMemoryThrashing)
Button btnMemoryThrashing;
@BindView(R.id.btnHanlder)
Button btnHanlder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.memory_monitor_activity);
ButterKnife.bind(this);
}
@OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
public void onViewClicked(View view) {
switch (view.getId()){
case R.id.btnMemoryThrashing:
SingleManager.getInstance(this).get();
break;
case R.id.btnHanlder:
sendMsg();
break;
}
}
private void sendMsg(){
mHandler.postDelayed(runnable,5000);
}
Runnable runnable = new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what=1;
message.obj = "子线程发消息";
mHandler.sendMessage(message);
}
};
}
SingleManager:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Toast.makeText(MemoryMonitorActivity.this,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
break;
}
}
};}SingleManager:public class SingleManager {
private static SingleManager instance = null;
private Context context = null;
private SingleManager(Context context){
this.context = context;
}
public static SingleManager getInstance(Context context){
if (instance==null){
instance = new SingleManager(context);
}
return instance;
}
public void get(){
LogUtils.e(" logcat context: "+ context );
}
}
从源码可以看出,单例模式、Handler、匿名内部类引起的内存泄漏的例子,这次先不说引起内存泄漏的原因,使用Memory Profiler工具来如何进行分析。
![](https://img.haomeiwen.com/i27762813/d5678eb1326d3ac7.png)
从上图可以看出,MemoryMonitorActivity界面已经退出或者销毁了,但MemoryMonitorActivity实例仍然存在,而且它实例对象本身大小为0.69K,MemoryMonitorActivity包括引用大小为142.7K,这就是典型了activity的内存泄漏,存在内存浪费。但通过上面,我们还是无法定位造成activity的内存泄漏真正的原因是什么呢。接下来继续看下图:
![](https://img.haomeiwen.com/i27762813/88a615cece5e0823.png)
![](https://img.haomeiwen.com/i27762813/0100109293a6806e.png)
通过上面可以看出:
1.Context持有外部MemoryMonitorActivity引用,导致GC无法回收。
2.runnable持有外部MemoryMonitorActivity引用,从源码可以看出runnable是一个内部类,持有外部引用MemoryMonitorActivity的类,导致GC不能回收。
3.Handler持有外部MemoryMonitorActivity引用,导致GC无法回收。
以上三点,activity存在内存泄漏真正原因就是持有引用,GC没有及时回收,存在内存浪费。
那接下来怎么优化它呢,别着急。我们知道如何使用Memory Profiler来检测应用是否存在内存泄漏。接下来我们使用MAT来分析下内存占用情况。
![](https://img.haomeiwen.com/i27762813/056071254d72152f.png)
我们点击Top Consumers看看,如下:
![](https://img.haomeiwen.com/i27762813/428cb349691e0197.png)
从上图可以看出,由于MemoryMonitorActivity界面已经销毁了,仍然存在很多实例,因此它已经存在内存泄露了。我们需要熟悉Memory Profiler和 MAT工具的使用,可以进行综合分析来定位内存泄露的真正原因。
接下来我们就需要优化代码了。
优化后源码如下:
MemoryMonitorActivity:
public class MemoryMonitorActivity extends BaseActivity{
@BindView(R.id.btnMemoryThrashing)
Button btnMemoryThrashing;
@BindView(R.id.btnHanlder)
Button btnHanlder;
private MyHandler myHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.memory_monitor_activity);
ButterKnife.bind(this);
myHandler = new MyHandler(this);
}
@OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
public void onViewClicked(View view) {
switch (view.getId()){
case R.id.btnMemoryThrashing:
SingleManager.getInstance(this).get();
break;
case R.id.btnHanlder:
sendMsg();
break;
}
}
private void sendMsg(){
myHandler.postDelayed(runnable,5000);
}
/**
* 匿名内部类持有外部类,需要实例化一个Runnable对象,在onDestroy方法runnable设置null即可
*/
Runnable runnable = new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what=1;
message.obj = "子线程发消息";
myHandler.sendMessage(message);
}
};
/**
* 使用静态handler的弱引用方式来避免内存泄露
*/
private static class MyHandler extends Handler{
private final WeakReference<MemoryMonitorActivity> memoryMonitorActivity;
public MyHandler(MemoryMonitorActivity activity){
memoryMonitorActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MemoryMonitorActivity activity = memoryMonitorActivity.get();
super.handleMessage(msg);
if (activity==null)return;
switch (msg.what){
case 1:
Toast.makeText(activity,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
break;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (runnable!=null){
runnable=null;
}
if (myHandler!=null){
myHandler.removeCallbacksAndMessages(null);
myHandler=null;
}
}
}
SingleManager:
public class SingleManager {
private static SingleManager instance = null;
private Context context = null;
private SingleManager(Context context){
this.context = context;
}
public static SingleManager getInstance(Context context){
if (instance==null){
// FIXME: 2021/10/30 使用context.getApplicationContext()和app生命周期一样长的进行避免内存泄露
instance = new SingleManager(context.getApplicationContext());
}
return instance;
}
public void get(){
LogUtils.e(" logcat context: "+ context );
}
}
![](https://img.haomeiwen.com/i27762813/ff7d776c6027a628.png)
![](https://img.haomeiwen.com/i27762813/57e77903994d18a8.png)
经过代码优化,通过Memory Profiler和 MAT工具分析结果,MemoryMonitorActivity不存在内存泄露了。
作为Android开发者,我们需要必备这个基本技能,需要熟悉Memory Profiler和 MAT内存工具的使用,开发过程中会经常使用到的。
网友评论