Android内存泄漏分析实例

内存泄漏简介

    Java可以保证当没有引用指向对象的时候,对象会被垃圾回收器回收,与c语言自己申请的内存自己释放相比,java程序员轻松了很多,但是并不代表java程序员不用担心内存泄漏。当java程序发生内存泄漏的时候往往具有隐蔽性。


定义

内存泄漏用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。从程序员的角度来看“内存泄漏”,其实就是一个对象的生命周期超出了程序员所预期的长度,那么这个对象就泄漏了。


Android开发中的内存泄漏

Android应用程序本身系统分配的内存很少,一旦发生泄漏,程序很快就会变得非常卡顿,直至OOM崩溃本文过两个介绍内存泄漏分析工具MAT,以及内存分析的技巧。


需要工具

备内存泄漏的分析工具,可以安装eclipse插件mat。如使AndroidStudio开发,可以将hprof文件导出再另外使用mat进行分析,不过会缺少某些功能,但是也够用了,本文适应的场景是AndroidStudio+MAT


Android开发中常见的内存泄漏

•对象没有反注册
•数据库cursor没有关闭
•Bitmap没有回收
•ListViewitem没有复用
•Handler在Activity中定义为非static的匿名内部类

人为制造一个内存泄漏

自定义一个ActivityManager,提供两个方法,分别用来注册与反注册Activity

package com.example.qinghua_liu.myapplication;

importandroid.app.Activity;
importandroid.util.Log;
importjava.util.ArrayList;
importjava.util.List;


publicclass ActivityManager{
   
private ListmActivities=newArrayList<>();
   
private static ActivityManagersInstance;

   
private ActivityManager(){
    };

   
public static ActivityManagerinstance() {
       
if (sInstance==null){
           
sInstance=newActivityManager();
       
}
       
return sInstance;
    }

   
public void registActivity(Activityactivity) {
       
mActivities.add(activity);
       
Log.d("TAG","mActivities.size()="+mActivities.size());
    }

   
public void unRigistActivity(Activityactivity) {
       
mActivities.remove(activity);
    }

}


MainActivityonCreateonDestroy中分别调用registActivityregistActivity方法进行注册与反注册。但Main2Activity记了反注册

public classMainActivityextendsAppCompatActivity{

   Button mBtn;

@Override
protectedvoid onCreate(BundlesavedInstanceState){
   
super.onCreate(savedInstanceState);
   
setContentView(R.layout.activity_main);

mBtn = (Button)findViewById(R.id.mybutton);

mBtn("newActivity");
mBtnView.OnClickListener(){
    @Override
   
public void onClick(Viewv) {
        Intent it =
newIntent(MainActivity.this,
                Main2Activity.
class);
       
try {
           
startActivity(it);
        }
catch (Exceptione) {
           
Log.e(TAG,e.toString());
       
}
  
}
});
   
ActivityManager.instance().registActivity(this);

}

@Override
protectedvoid onDestroy(){
   
super.onDestroy();
   
ActivityManager.instance().unRigistActivity(this);
}

}


public classMain2ActivityextendsActivity{

private Object[] mObjs= new Object[10000];

@Override
protectedvoid onCreate(BundlesavedInstanceState){
   
super.onCreate(savedInstanceState);
   
setContentView(R.layout.activity_main);

//模拟快速消耗大量内存,使效果明

for (inti=0;i<mObjs.length;i++){
   
mObjs[i] =new Object();
}

ActivityManager.instance().registActivity(this);

}

@Override
protectedvoid onDestroy(){
   
super.onDestroy();
   
//ActivityManager.instance().unRigistActivity(this);// if forget unRigist
}

}


AndroidStudio Monitor Debug

Android内存泄漏分析实例_第1张图片


Monitor界面可以大概现内存,CPUGPUNetWork的占用消耗情况。

    详细分析还要依靠MatAPP操作多次叫起和销毁MainActivity以及Main2Activity

      导出Memory hprof 文件:

      AS Monitor Debug界面按DumpJava Heap,工具会自动生成一份hprof后缀文件。


hprof文件分析

Android Studio 生成的hprof文件 MAT还不能直接使用,需用转换成标准的hprof件。

转换可借助AndroidSdkPlatform toolhprof具体命令下图:

Android内存泄漏分析实例_第2张图片



MAT分析工具

经转换得到的hprof文件可以由MAT直接打开了。

Android内存泄漏分析实例_第3张图片

MAT分析

Matdump一个内存快照出来,然后从分析报告中点击“Leaksuspects”这里会列出可能泄漏的对象,其中你会发现com.example.qinghua_liu.myapplication.Main2Activity的身影Main2Activity个类49实例CodeAuthor该一下子就会发现,原来Main2Activity漏了。发现它泄漏之后,如何找出是哪一个对象持有Main2Activity象的引用呢?


找出引用链

使OQL对象查询语言查询出泄漏的对象,和SQL非常相似,语法简单易懂,却非常强大。select* from com.example.qinghua_liu.myapplication.Main2Activity筛选出Main2Activity一类对象。

Android内存泄漏分析实例_第4张图片

 后选择“excludeweak/soft references”筛选出除了软引用和弱引用之外(即强引用)的对象。

Android内存泄漏分析实例_第5张图片

后找出的GCRoot,从下图可以看出,原来Activity对象被ActivityManager里面的ArrayListhold住了,所以接下来的工作就是Main2Activity检查反注册,内存泄漏的原因很快就可以清楚了。

Android内存泄漏分析实例_第6张图片

链接

Eclipse Mat 工具下载地址

http://www.eclipse.org/mat/


Handler使用过程中可能引发的内存泄

我们UI编程时一般会使Handler进行UI更新,环境会有下面一段温馨的提示ThisHandler class should be static or leaksmightoccur

    另外还有段详细英文描述,大概意思就是:

   一Handler被声明为内部类,那么可能导致它的外部类不能够被垃圾回收。如果Handler是在其他线程(通常是workerthread)使用LooperMessageQueue(消息队列),而不是main线程(UI线程),那么就没有这个问题。如果Handler使用LooperMessageQueue在主线程(mainthread),你需要对Handler的声明做如下修改:
    声Handlerstatic类;在外部类中实例化一个外部类的WeakReference(弱引用)并且在Handler初始化时传入这个对象给你的Handler;将所有引用的外部类成员使用WeakReference对象。(与Bitmap工作线程弱引用相似?)


解决方案

private static class CopyFileHandlerextendsHandler{
   
WeakReference<MainActivity>mActivity;
   
public CopyFileHandler(MainActivityactivity) {
       
mActivity=newWeakReference<>(activity);
    }

   
public void handleMessage(Messagemsg){
       
final MainActivityactivity =mActivity.get();
       
if(null!=activity&&null!=activity.btn){
       
//handle you message here!
           
activity.btn.setText("clickme");
           
activity.btn.setClickable(true);
       
}
   
}
}

private CopyFileHandlermHandler;

@Override

protected void onCreate(BundlesavedInstanceState){

mHandler=newCopyFileHandler(this);

}

private void startCopyFileThread(){
   
Log.d(TAG,"startCopyDBThread");
   
new Thread(newRunnable() {
        @Override
       
public void run(){
           
//DOSOMETHING LIKE: copyDBFile();
           
Messagemsg=mHandler.obtainMessage();
           
mHandler.sendMessageDelayed(msg,1000);
       
}
    }).start();
}


为什么会内存泄漏

这与几个关键词有关:内部类、Handler的消息循环(Looper)、Java垃圾回收机制。需要强调一下,并不是每次使用Handler都会引发内存泄漏,这里面有一定的几率,需要满足特定条件才会引起泄漏。
  内部类会有一个指向外部类的(硬)引用。垃圾回收机制中约定,当内存中的一个对象的引用计数为0时,将会被回收。
  Handler作为Android上的异步消息处理机制,它的工作是需要Looper和MessageQueue配合的。简单的说,要维护一个循环体(Looper)处理消息队列(MessageQueue)。每循环一次就从MessageQueue中取出一个Message,然后回调相应的消息处理函数。

  如果循环体中有消息未处理(Message排队中),那么Handler会一直存在,那么Handler的外部类(通常是Activity)的引用计数一直不会是0,所以那个外部类就不能被垃圾回收。很多人会遇到activity的onDestroy方法一直不执行就是这个原因。


另一个解决方案的尝试

提示描述中提到了也可以让Handlerworkerthread中使用LooperMessageQueue


private class WorkThreadextendsThread{
        @Override
       
public void run(){
           
super.run();
           
Looper.prepare();
           
testHandler=newHandler(){
               
publicvoid handleMessage(Messagemsg){
                   
mybutton1.post(newRunnable(){
                       @Override
                       
publicvoid run(){
                           
mybutton1.setText("clickme");
                           
mybutton1.setClickable(true);
                        }
                    });
                }
            };
           
Looper.loop();
        }
    }


private WorkThreadmThread;

@Override
protectedvoid onCreate(BundlesavedInstanceState){

   mThread=newWorkThread();
    if(Thread.State.NEW==mThread.getState()){
       
mThread.start();
    }

}

@OnClick({R.id.hello,R.id.mybutton1,R.id.radioButton})
publicvoid onClick(Viewview) {
   
switch (view.getId()){
       
case R.id.mybutton1:
           
mybutton1.setText("clicked");
           
mybutton1.setClickable(false);
           
testHandler.sendEmptyMessageDelayed(1,3000);
           
break;
       
case R.id.radioButton:
           
break;
    }
}






你可能感兴趣的:(Android,android,内存泄露,实例,MAT,Handler)