Android 有效的解决内存泄漏的问题实例详解

Android 有效的解决内存泄漏的问题

Android内存泄漏,我想做Android 应用的时候遇到的话很是头疼,这里是我在网上找的不错的资料,实例详解这个问题的解决方案

前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题。在网上找了很多资料,有很多都是互相抄的,没有实际的作用。

本文的内存泄漏检测工具是:LeakCanary  github地址:https://github.com/square/leakcanary

什么是内存泄漏?

内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。

怎样会导致内存泄漏?

资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor

构造Adapter时,没有使用 convertView 重用

Bitmap对象不在使用时调用recycle()释放内存

对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放

 内存泄漏有什么危害?

内存泄漏对于app没有直接的危害,即使app有发生内存泄漏的情况,也不一定会引起app崩溃,但是会增加app内存的占用。内存得不到释放,慢慢的会造成app内存溢出。所以我们解决内存泄漏的目的就是防止app发生内存溢出。

1、新建线程引起的Activity内存泄漏

例子:

package rxnet.zyj.com.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class Activity6 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_6);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new Thread(new Runnable() {
      @Override
      public void run() {
        try {<br>          //模拟耗时操作
          Thread.sleep( 15000 );
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();

  }
}

  运行上面的代码后,点击finish按钮,过一会儿发生了内存泄漏的问题。

 为什么Activity6会发生内存泄漏?

进入Activity6 界面,然后点击finish按钮,Activity6销毁,但是Activity6里面的线程还在运行,匿名内部类Runnable对象引用了Activity6的实例,导致Activity6所占用的内存不能被GC及时回收。

 如何改进?

Runnable改为静态非匿名内部类即可。

package rxnet.zyj.com.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class Activity6 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_6);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new Thread( new MyRunnable()).start();

  }

  private static class MyRunnable implements Runnable {

    @Override
    public void run() {
      try {
        Thread.sleep( 15000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

}

 2、Activity添加监听器造成Activity内存泄漏

package rxnet.zyj.com.myapplication;

import android.app.Activity;
import android.os.Bundle;

public class LeakActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NastyManager.getInstance().addListener(this);
  }
}

  这个是在开发中经常会犯的错误,NastyManager.getInstance() 是一个单例,当我们通过 addListener(this) 将 Activity 作为 Listener 和 NastyManager 绑定起来的时候,不好的事情就发生了。

如何改进?

想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。

package rxnet.zyj.com.myapplication;

import android.app.Activity;
import android.os.Bundle;

public class LeakActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NastyManager.getInstance().addListener(this);
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    NastyManager.getInstance().removeListener(this);
  }
}

  3、Handler 匿名内部类造成内存溢出?

先看着一段代码

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;

  private final Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      Log.d("mmmmmmmm" , "handler " + msg.what ) ;
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new Thread(new Runnable() {
      @Override
      public void run() {
        handler.sendEmptyMessage( MESSAGECODE ) ;
        try {
          Thread.sleep( 8000 );
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        handler.sendEmptyMessage( MESSAGECODE ) ;
      }
    }).start() ;

  }
}

  这段代码运行起来后,立即点击 finish 按钮,通过检测,发现 HandlerActivity 出现了内存泄漏。当Activity finish后,延时消息会继续存在主线程消息队列中8秒钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。Handler 是个很常用也很有用的类,异步,线程安全等等。如果有下面这样的代码,会发生什么呢? handler.postDeslayed ,假设 delay 时间是几个小时… 这意味着什么?意味着只要 handler 的消息还没有被处理结束,它就一直存活着,包含它的 Activity 就跟着活着。我们来想办法修复它,修复的方案是 WeakReference ,也就是所谓的弱引用。垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉。

如何避免

使用静态内部类

使用弱引用

修改后代码是这样的。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;
  private static Handler handler ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    handler = new MyHandler( this ) ;

    new Thread(new Runnable() {
      @Override
      public void run() {
        handler.sendEmptyMessage( MESSAGECODE ) ;
        try {
          Thread.sleep( 8000 );
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        handler.sendEmptyMessage( MESSAGECODE ) ;
      }
    }).start() ;

  }

  private static class MyHandler extends Handler {
    WeakReference<HandlerActivity> weakReference ;

    public MyHandler(HandlerActivity activity ){
      weakReference = new WeakReference<HandlerActivity>( activity) ;
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      if ( weakReference.get() != null ){
        // update android ui
        Log.d("mmmmmmmm" , "handler " + msg.what ) ;
      }
    }
  }
}

  这个Handler已经使用了静态内部类,并且使用了弱引用。但是这个并没有完全解决 HandlerActivity 内存泄漏的问题,罪魁祸首是线程创建的方式出了问题,就像本文的第一个例子一样。改进的方式,是把Runnable类写成静态内部类。

最终完整的代码如下:

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;
  private static Handler handler ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //创建Handler
    handler = new MyHandler( this ) ;

    //创建线程并且启动线程
    new Thread( new MyRunnable() ).start();
  }

  private static class MyHandler extends Handler {
    WeakReference<HandlerActivity> weakReference ;

    public MyHandler(HandlerActivity activity ){
      weakReference = new WeakReference<HandlerActivity>( activity) ;
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      if ( weakReference.get() != null ){
        // update android ui
        Log.d("mmmmmmmm" , "handler " + msg.what ) ;
      }
    }
  }

  private static class MyRunnable implements Runnable {

    @Override
    public void run() {
      handler.sendEmptyMessage( MESSAGECODE ) ;
      try {
        Thread.sleep( 8000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      handler.sendEmptyMessage( MESSAGECODE ) ;
    }
  }
}

  等等,还没完呢?

上面这个代码已经有效的解决了Handler,Runnable 引用Activity实例从而导致内存泄漏的问题,但是这不够。因为内存泄漏的核心原因就是这个某个对象应该被系统回收内存的时候,却被其他对象引用,造成该内存无法回收。所以我们在写代码的时候,要始终绷着这个弦。再回到上面这个问题,当当前Activity调用finish销毁的时候,在这个Activity里面所有线程是不是应该在OnDestory()方法里,取消线程。当然是否取消异步任务,要看项目具体的需求,比如在Activity销毁的时候,启动一个线程,异步写log日志到本地磁盘,针对这个需求却需要在OnDestory()方法里开启线程。所以根据当前环境做出选择才是正解。

所以我们还可以修改代码为:在onDestroy() 里面移除所有的callback 和 Message 。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;

public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;
  private static Handler handler ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //创建Handler
    handler = new MyHandler( this ) ;

    //创建线程并且启动线程
    new Thread( new MyRunnable() ).start();
  }

  private static class MyHandler extends Handler {
    WeakReference<HandlerActivity> weakReference ;

    public MyHandler(HandlerActivity activity ){
      weakReference = new WeakReference<HandlerActivity>( activity) ;
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      if ( weakReference.get() != null ){
        // update android ui
        Log.d("mmmmmmmm" , "handler " + msg.what ) ;
      }
    }
  }

  private static class MyRunnable implements Runnable {

    @Override
    public void run() {
      handler.sendEmptyMessage( MESSAGECODE ) ;
      try {
        Thread.sleep( 8000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      handler.sendEmptyMessage( MESSAGECODE ) ;
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。
    handler.removeCallbacksAndMessages( null );
  }
}

4、AsyncTask造成内存泄漏

package rxnet.zyj.com.myapplication;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class Activity2 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new AsyncTask<String,Integer,String>(){

      @Override
      protected String doInBackground(String... params) {
        try {
          Thread.sleep( 6000 );
        } catch (InterruptedException e) {
        }
        return "ssss";
      }

      @Override
      protected void onPostExecute(String s) {
        super.onPostExecute(s);
        Log.d( "mmmmmm activity2 " , "" + s ) ;
      }

    }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;

  }
}

  为什么?

上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

   怎么解决?

自定义静态AsyncTask类A

syncTask的周期和Activity周期保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。

package rxnet.zyj.com.myapplication;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class AsyncTaskActivity extends AppCompatActivity {

  private static MyTask myTask ;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_asynctask);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    myTask = new MyTask() ;
    myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;

  }

  private static class MyTask extends AsyncTask{

    @Override
    protected Object doInBackground(Object[] params) {
      try {
        //模拟耗时操作
        Thread.sleep( 15000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "";
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消异步任务
    if ( myTask != null ){
      myTask.cancel(true ) ;
    }
  }
}

5、Timer Tasks 造成内存泄漏

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class TimerActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //开始定时任务
    timer();
  }

  void timer(){
    new Timer().schedule(new TimerTask() {
      @Override
      public void run() {
        while(true);
      }
    },1000 ); // 1秒后启动一个任务
  }
}

  为什么?

这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

  怎么解决? 

在适当的时机进行Cancel。

TimerTask用静态内部类

注意:在网上看到一些资料说,解决TimerTask内存泄漏可以使用在适当的时机进行Cancel。经过测试,证明单单使用在适当的时机进行Cancel , 还是有内存泄漏的问题。所以一定要用静态内部类配合使用。

package rxnet.zyj.com.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class TimerActivity extends AppCompatActivity {

  private TimerTask timerTask ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //开始定时任务
    timer();
  }

  void timer(){
    timerTask = new MyTimerTask() ;
    new Timer().schedule( timerTask ,1000 ); // 1秒后启动一个任务
  }

  private static class MyTimerTask extends TimerTask{

    @Override
    public void run() {
      while(true){
        Log.d( "ttttttttt" , "timerTask" ) ;
      }
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消定时任务
    if ( timerTask != null ){
      timerTask.cancel() ;
    }
  }
}

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android 5.1 WebView内存泄漏问题及快速解决方法

    问题背景 在排查项目内存泄漏过程中发现了一些由WebView引起的内存泄漏,经过测试发现该部分泄漏只会出现在android 5.1及以上的机型.虽然项目使用WebView的场景并不多,但秉承着一个泄漏都不放过的精神,我们肯定要把它给解决了. 遇到的问题 项目中使用WebView的页面主要在FAQ页面,问题也出现在多次进入退出时,发现内存占用大,GC频繁.使用LeakCanary观察发现有两个内存泄漏很频繁: 我们分析一下这两个泄漏: 从图一我们可以发现是WebView的ContentViewCo

  • Android开发:浅谈MVP模式应用与内存泄漏问题解决

    最近博主开始在项目中实践MVP模式,却意外发现内存泄漏比较严重,但却很少人谈到这个问题,促使了本文的发布,本文假设读者已了解MVP架构. MVP简介 M-Modle,数据,逻辑操作层,数据获取,数据持久化保存.比如网络操作,数据库操作 V-View,界面展示层,Android中的具体体现为Activity,Fragment P-Presenter,中介者,连接Modle,View层,同时持有modle引用和view接口引用 示例代码 Modle层操作 public class TestModle

  • Android 有效的解决内存泄漏的问题实例详解

    Android 有效的解决内存泄漏的问题 Android内存泄漏,我想做Android 应用的时候遇到的话很是头疼,这里是我在网上找的不错的资料,实例详解这个问题的解决方案 前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的作用. 本文的内存泄漏检测工具是:LeakCanary  github地址:https://github.com/square/leakcanary 什么是内存泄漏? 内存泄漏

  • Vue优化:常见会导致内存泄漏问题及优化详解

    如果你在用 Vue 开发应用,那么就要当心内存泄漏的问题.这个问题在单页应用 (SPA) 中尤为重要,因为在 SPA 的设计中,用户使用它时是不需要刷新浏览器的,所以 JavaScript 应用需要自行清理组件来确保垃圾回收以预期的方式生效.因此在vue开发过程中,你需要时刻警惕内存泄漏的问题,这些内存泄漏往往会发生在使用 Vue 之外的其它进行 DOM 操作的三方库时,请确保测试应用的内存泄漏问题并在适当的时机做必要的组件清理. 下面是我开发过程中遇到,并查资料总结的内存泄漏问题,会持续更新中

  • Android 虚拟机中的内存分配与OOM问题详解

    目录 背景知识 一.Android VM的内存空间 1.查看内存的API 二.Android VM内存分配流程 小结 三.出现OOM的建议解决方案 背景知识 Android中每个App默认情况下是运行在一个独立进程中的, 而这个独立进程正是从Zygote孵化出来的VM进程, 也就是说, 也就是说每个Android APP在运行时会启动一个Java虚拟机. 并且系统会给它分配固定的内存空间(手机厂商会根据手机的配置情况来对其进行调整). 一.Android VM的内存空间 Android是一个多任

  • Android 中倒计时验证两种常用方式实例详解

    Android 中倒计时验证两种常用方式实例详解 短信验证码功能,这里总结了两种常用的方式,可以直接拿来使用.看图: 说明:这里的及时从10开始,是为了演示的时间不要等太长而修改的. 1.第一种方式:Timer /** * Description:自定义Timer * <p> * Created by Mjj on 2016/12/4. */ public class TimeCount extends CountDownTimer { private Button button; //参数依

  • Linux内存描述符mm_struct实例详解

    Linux对于内存的管理涉及到非常多的方面,这篇文章首先从对进程虚拟地址空间的管理说起.(所依据的代码是2.6.32.60) 无论是内核线程还是用户进程,对于内核来说,无非都是task_struct这个数据结构的一个实例而已,task_struct被称为进程描述符(process descriptor),因为它记录了这个进程所有的context.其中有一个被称为'内存描述符'(memory descriptor)的数据结构mm_struct,抽象并描述了Linux视角下管理进程地址空间的所有信息

  • Android 实现夜间模式的快速简单方法实例详解

    ChangeMode 项目地址:ChangeMode Implementation of night mode for Android. 用最简单的方式实现夜间模式,支持ListView.RecyclerView. Preview Usage xml android:background="?attr/zzbackground" app:backgroundAttr="zzbackground"//如果当前页面要立即刷新,这里传入属性名称 比如 R.attr.zzb

  • Android Studio打包.so库到apk中实例详解

    Android Studio打包.so库到apk中实例详解 由于在原来的ADT的Eclipse环境中,用ndk_build工具生成了相应的各个.so库文件之后,eclipse工具就会自动把这些库导入到apk中.而Android Studio目前为止(1.1.0版本)还无法做到那么自动,但是我们可以通过以下方式进行. 首先在Android Studio工程的app目录下创建整个jni目录,jni目录里写Android.mk.Application.mk以及各类C/C++和汇编源文件.然后跟原来一样

  • Android 沉浸式状态栏与隐藏导航栏实例详解

    1 前言 一般我们在Android的APP开发中,APP的界面如下: 可以看到,有状态栏.ActionBar(ToolBar).导航栏等,一般来说,APP实现沉浸式有三种需求:沉浸式状态栏,隐藏导航栏,APP全屏 沉浸式状态栏是指状态栏与ActionBar颜色相匹配, 隐藏导航栏不用多说,就是将导航栏隐藏,去掉下面的黑条. APP全屏是指将状态栏与导航栏都隐藏,例如很多游戏界面,都是APP全屏. 所以,在做这一步时,关键要问清楚产品狗的需求,免得白费功夫. 下面,分别来介绍这三种方式的实现. 2

  • Android屏蔽软键盘并且显示光标的实例详解

    Android屏蔽软键盘并且显示光标的实例详解 如果是android4.0以下,那么 editText.setInputType(InputType.TYPE_NULL); 就够了,android4.0以上屏蔽软键盘并且有光标,需要用到. if (android.os.Build.VERSION.SDK_INT <= 10) {//4.0以下 danielinbiti editText.setInputType(InputType.TYPE_NULL); } else { this.act.ge

  • Android MotionEvent中getX()和getRawX()的区别实例详解

    Android MotionEvent中getX()和getRawX()的区别实例详解 实例代码: public class Res extends Activity implements View.OnTouchListener { Button btn = null; int x = 0; int y = 0; int rawx = 0; int rawy = 0; @Override public void onCreate(Bundle savedInstanceState) { sup

随机推荐