Android中Handler引起的内存泄露问题解决办法

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。

复制代码 代码如下:

public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
}

但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告

复制代码 代码如下:

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread's MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下。

1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。

2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。

3.在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。关于这一内容可以查看细话Java:”失效”的private修饰符

确实上面的代码示例有点难以察觉内存泄露,那么下面的例子就非常明显了

复制代码 代码如下:

public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

// Go back to the previous Activity.
    finish();
  }
}

分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。

注意上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。

要解决这种问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下:

复制代码 代码如下:

public class SampleActivity extends Activity {

/**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

@Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

private final MyHandler mHandler = new MyHandler(this);

/**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

// Go back to the previous Activity.
    finish();
  }
}

其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。

时间: 2015-01-15

Android handler 详解(面试必问)

handler在Android中被称为"消息处理者",在多线程中比较常用. Handler为Android提供了一种异步消息处理机制,当向消息队列中发送消息 (sendMessage)后就立即返回,而从消息队列中读取消息时会阻塞,其中从消息队列中读取消息时会执行Handler中的public void handleMessage(Message msg) 方法,因此在创建Handler时应该使用匿名内部类重写该方法,在该方法中写上读取到消息后的操作,使用Handler的 obtainM

详解Android中Handler的使用方法

在Android开发中,我们经常会遇到这样一种情况:在UI界面上进行某项操作后要执行一段很耗时的代码,比如我们在界面上点击了一个"下载"按钮,那么我们需要执行网络请求,这是一个耗时操作,因为不知道什么时候才能完成.为了保证不影响UI线程,所以我们会创建一个新的线程去执行我们的耗时的代码.当我们的耗时操作完成时,我们需要更新UI界面以告知用户操作完成了.所以我们可能会写出如下的代码: package ispring.com.testhandler; import android.app.

浅谈Android应用的内存优化及Handler的内存泄漏问题

一.Android内存基础 物理内存与进程内存 物理内存即移动设备上的RAM,当启动一个Android程序时,会启动一个Dalvik VM进程,系统会给它分配固定的内存空间(16M,32M不定),这块内存空间会映射到RAM上某个区域.然后这个Android程序就会运行在这块空间上.Java里会将这块空间分成Stack栈内存和Heap堆内存.stack里存放对象的引用,heap里存放实际对象数据. 在程序运行中会创建对象,如果未合理管理内存,比如不及时回收无效空间就会造成内存泄露,严重的话可能导致

详解Android中Handler的内部实现原理

本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文<详解Android中Handler的使用方法>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功

Android 中Handler引起的内存泄露

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } } 但是,其实上面的代码可能导致内存泄露,当你使用Androi

解决Android使用Handler造成内存泄露问题

一.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收:另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收. Android中使用Handler造成内存泄露的原因 private Han

Android 消息机制以及handler的内存泄露

Handler 每个初学Android开发的都绕不开Handler这个"坎",为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部分人都是知其然却不知其所以然.今天看到Handler.post这个方法之后决定再去翻翻源代码梳理一下Handler的实现机制. 异步更新UI 先来一个必背口诀"主线程不做耗时操作,子线程不更新UI",这个规定应该是初学必知的,那要怎么来解决口诀里的问题呢,这时候Handler就出现在我们面前了(AsyncTask也行,不过本质

Android使用Handler和Message更新UI

在Android中,在非主线程中更新UI控件是不安全的,app在运行时会直接Crash,所以当我们需要在非主线程中更新UI控件,那么就需要用到Handler和Message来实现 Demo中,使用到一个按钮和一个TextView,点击按钮之后改变TextView的内容,按钮点击时候新建一个进程,在进程中对UI控件进行修改. public class MainActivity extends Activity implements OnClickListener { private static

Android 在子线程中更新UI的几种方法示例

本文介绍了Android 在子线程中更新UI的几种方法示例,分享给大家,具体如下: 方式一:Handler和Message ① 实例化一个Handler并重写handlerMessage()方法 private Handler handler = newHandler() { public void handleMessage(Message msg) { // 处理消息 super.handleMessage(msg); switch (msg.what) { case 1: button1.

Android中Handler与Message的简单实例

Android中Handler与Message的简单实例 前言: 虽然笔者已经学习了Android的AsyncTask来实现一部消息的处理.但是在android的学习中,经常会在一些demo中看到Handler与Message的一些使用,所以Handler与Message的学习也是有必要了.至于学多少,笔者还是比较坚持自己的看法,"用多少,学多少",毕竟已经有了AsyncTask如此方便的东西,Handler与Message也不是那么必不可缺了.(如此文的简单了解一下还是不需要花太多时

浅谈Android中使用异步线程更新UI视图的几种方法

在Android中子线程是不能更新ui的. 所以我们要通过其他方式来动态改变ui视图, 1.runOnUiThread activity提供的一个轻量级更新ui的方法,在Fragment需要使用的时候要用getActivity.runOnUiThread开启线程 这种方法最简单,方便更新一些不需要判断的通知,比如在聊天项目中动态获取未读消息数量. runOnUiThread(new Runnable() { @Override public void run() { sendMessage("[

Android 在其他线程中更新UI线程的解决方法

方法一:Activity.runOnUiThread(Runnable )(经验之道: 这个最好用, 凡是要刷新页面的地方,Activity.runOnUiThread( new Runnable()  { public void run(){更新UI}}); 方法二:子线程调用Handler的sendMessage(message)发送事件. 复制代码 代码如下: mHandler = new Handler() {     @Override     public void handleMe

Android更新UI的四种方法详解

前言 相信每位Android开发者们都知道更新UI只能在主线程中进行,若是在子线程执行任务后需要更新UI,则需要借助handler跳转到主线程中.以下介绍几种操作UI的方法. 一.使用Handler的handleMessage() Handler的构造 public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { fina

Android编程实现使用handler在子线程中更新UI示例

本文实例讲述了Android编程实现使用handler在子线程中更新UI.分享给大家供大家参考,具体如下: MainActivity代码: package com.example.ui; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextV

android使用handler ui线程和子线程通讯更新ui示例

复制代码 代码如下: package com.act262.sockettx; import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import and

android开发教程之handler异步更新ui

其实文字游戏程序很简单,就是一个view和一个Activity,在利用下handier和postInvalidate()更新UI 调用Handler.post(Runnable r)方法,Runnable运行在UI所在线程,所以可以直接调用View.invalidate() 复制代码 代码如下: packagecom.Test.androidtest; importandroid.app.Activity;importandroid.content.Context;importandroid.g

Android通过RemoteViews实现跨进程更新UI示例

一.概述 前面一篇文章Android通过AIDL实现跨进程更新UI我们学习了aidl跨进程更新ui,这种传统方式实现跨进程更新UI是可行的,但有以下弊端: View中的方法数比较多,在IPC中需要增加对应的方法比较繁琐. View的每一个方法都会涉及到IPC操作,多次IPC带来的开销问题不容小觑. View中方法的某些参数可能不支持IPC传输.例如:OnClickListener,它仅仅是个接口没有序列化. 接下来我们通过RemoteViews实现跨进程更新UI 二.实现效果图 在同一个应用中有