在android studio中分析内存泄漏的工具有以下:MemoryMonitor 以及Allocation Tracker,还有Dump Java Heap,手动GC工具;as位置如下:
鼠标悬浮在上面就可以知道各个图标的名字。
-
首先是MemoryMonitor:
1是连接设备,2是运行的modle,三就是实时显示内存变化区。
-
然后是Dump Java Heap:
在1处选择Package Tree View。然后在2区按照包名可以找到Activity和其他类,右边是当前类的实例,下边是和这个类有关的引用。当创建了几个Activity实例,Activity被销毁之后,如果手动GC,该实例的个数没有减少,那么说明可能已经发生了内存泄漏。
-
最后是Allocation Tracker:
可以检测在一段时间内内存的分配情况,下面的饼图就很直观。
除了上面的工具还有一个神器:LeakCanary可以检测出代码中的内存泄漏;具体使用文档里有介绍。
下面就用一个小例子说明一下这些工具的使用。
首先放上代码
主Activity:
package com.github.lzyzsd.memorybugs;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static TextView sTextView;
private Button mStartBButton;
private Button mStartAllocationButton;
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sTextView = (TextView) findViewById(R.id.tv_text);
sTextView.setText("Hello World!");
mStartBButton = (Button) findViewById(R.id.btn_start_b);
mStartBButton.setOnClickListener(this);
mStartAllocationButton = (Button) findViewById(R.id.btn_allocation);
mStartAllocationButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_b:
startB();
break;
case R.id.btn_allocation:
startAllocationLargeNumbersOfObjects();
break;
}
}
private void startB() {
finish();
startActivity(new Intent(this, ActivityB.class));
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("post delayed may leak");
}
}, 5000);
Toast.makeText(this, "请注意查看通知栏LeakMemory", Toast.LENGTH_SHORT).show();
}
private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "请注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
Rect rect = new Rect(0, 0, 100, 100);
System.out.println("-------: " + rect.width());
}
}
}
运行程序后,界面如下:
现在看起来没有问题,但当点击STARTACTIVITYB按钮之后,就会转入一个空的activity,大概过3-5秒通知栏就会弹出一个消息,点击消息就可以进入LeakCanary,然后我们就可以看到:
每项展开后就可以从上到下看到:静态变量sTextView引用了mContext导致MainActivity不能及时回收而造成内存泄漏。那解决方法就是把代码中修饰sTextView的static去掉。其实现在的android studio到达2.2后已经十分的智能。在sTextView它就提示了这里会导致内存泄漏:
所以有时候也要看看它的建议。
然后我们再多次点击第二个按钮,在Memory Monitor里就可以看到由于突然创建大量对象不断的垃圾回收,所以会看打内存抖动的现象:
此时如果打开start allocation tracking:
按照图中的步骤(第二步是点击最外层),可以看到在短时间就创建了很多StringBuilder和Rect对象。所以我们可以把循环里Rect对象的创建和输出语句里的字符拼接移到onCreate()方法里。如下:
- 成员变量
private String mWidth;
- onCreate()
Rect rect = new Rect(0, 0, 100, 100);
mWidth = "-------: " + rect.width();
- 点击方法中
private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "请注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
System.out.println(mWidth);
}
}
这样之后,再多次点击按钮没有出现内存抖动了。//昨天之后,经同学提醒,System.out.println()输出语句在线程中还是会影响性能,所以还是把它改成log日志打印了。接下来我们把焦点聚集到Handler上面,老师在这里提示说可能会发生内存泄漏,然而刚刚的操作并没有产生这个问题,那我们试试把延迟的5000ms改成15000ms,如下:
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("post delayed may leak");
}
}, 15000);
Toast.makeText(this, "请注意查看通知栏LeakMemory", Toast.LENGTH_SHORT).show();
运行程序之后,点击按钮跳转ActivityB,过几秒之后,通知栏就出现了消息,点进去如下图:
一层一层分析下来原来是Runnable持有了外部类Activity实例的引用,那我们就来把内部类实现对Activity的弱引用,修改代码如下:
public static class MyRunnable implements Runnable{
WeakReference<MainActivity> mMainActivityWeakReference;
MyRunnable(MainActivity mainActivity){
mMainActivityWeakReference = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void run() {
System.out.println("post delayed may leak");
}
}
其他地方做小修改之后,运行程序,哎呀,好像通知栏还是有提示,不过这次的错误好像不一样:
是handler的问题,突然间我就想起来之前的老师就给我们说过handler会造成内存泄漏,果然知识还是要经常复习才能不会忘记。然而这里是Handler里面的一个匿名内部类Callback造成的,所以只需要让对Activity实现弱引用。
static class MyHandlerCallback implements Handler.Callback{
WeakReference<MainActivity> mMainActivityWeakReference;
MyHandlerCallback(MainActivity mainActivity){
mMainActivityWeakReference = new WeakReference<>(mainActivity);
}
@Override
public boolean handleMessage(Message msg) {
return false;
}
}
再一次运行就没有错误了,最后再看看自定义控件类,在onDraw()方法中会有提示,意思是说,这个方法会频繁被调用所以不要在这个方法中创建对象,我们就把两个创建对象的语句移到构造函数前,没有了黄色的提示强迫症得到极大的满足,自此我找的错误就这些了,还望指教。