zoukankan      html  css  js  c++  java
  • Android Handler内存泄露

    前言

    由于Android采取了单线程UI模型,开发者无法在子线程中更新UI,为此Android为我们提供了Handler这个工具,帮助开发者切换到主线程更新UI。在我们开发Android应用程序进行异步操作时,我们经常会使用到Handler类。通常会写出如下代码
    1. private Handler mHandler = new Handler(){
    2.  @Override
    3.  public void handleMessage(Message msg){
    4.  //do something
    5.  }
    6.  }
    其实上面的代码是会产生内存泄漏的,如果你有使用Android lint工具的话,它会给我们提示一个警告
    In Android, Handler classes should be static or leaks might occur。
    翻译过来就是:在android中,Handler这个类应该被定义成静态的,否则可能出现内存泄漏的情况
    先说下什么是内存泄漏:
    内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

    发生内存泄漏的原因

    说是内存泄漏,那到底如何发生内存泄漏的呢?又在哪里发生的内存泄漏?接下来我们探究一下到底是如何发生内存泄漏的。
    1.当一个Android应用程序启动的时候,frameworks会自动为这个应用程序在主线程创建一个Looper对象。这个被创建的Looper对象它的主要的工作就是不断地处理消息队列中的消息对象。在Android应用程序中,所有主要的框架事件(例如Activity的生命周期方法,按钮的点击事件等等)都包含在消息对象里面,然后被添加到Looper要处理的消息队列中,主线程的Looper一直存在于整个应用程序的生命周期中。
    2.当一个Handler在主线程中被初始化。那它就一直都和Looper的消息队列相关联着。当消息被发送到Looper关联的消息队列的时候,会持有一个Handler的引用,以便于当Looper处理消息的时候,框架可以调用Handler的handleMessage(Message msg)。
    3.在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。静态内部类则不会持有外部类的引用。
    上面的代码确实是很难以发现内存泄漏的问题的。那我们来看看下面的代码,会更加容易发现问题。
    1.  public class MainActivity extends AppCompatActivity {
    2.   
    3.  private Handler mLeakHandler = new Handler(){
    4.  @Override
    5.  public void handleMessage(Message msg) {
    6.   }
    7.  };
    8.    @Override
    9.  protected void onCreate(Bundle savedInstanceState) {
    10.  super.onCreate(savedInstanceState);
    11.  setContentView(R.layout.activity_main);
    12.  //发送延时消息
    13.  mLeakHandler.postDelayed(new Runnable() {
    14.  @Override
    15.  public void run() {
    16.   }
    17.  }, 1000 *60 *10);
    18.  finish();
    19.  }
    20.  }

    解决办法

    要解决这样的一个问题,有如下两种方式:
    方法一:通过程序
    1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

    2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler方法:removeCallbacks(Runnable r)和removeMessages(int what),在页面销毁时把消息对象从消息队列移除就行了

    代码如下:

    1.  @Override
    2.  public void onDestroy() {
    3.  // 移除所有消息
    4.  handler.removeCallbacksAndMessages(null);
    5.  // 或者移除单条消息
    6.  handler.removeMessages(what);
    7.  }

    方法二:将Handler声明为静态类
    静态类不持有外部类的对象,所以你的Activity可以随意被GC回收。代码如下:

    1.  static class NoLeakHander extends Handler {
    2.  @Override
    3.  public void handleMessage(Message msg) {
    4.    }
    5.  }
    以上代码中Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以就没法在Hander中操作UI了,你需要在Handler中增加一个对Activity的弱引用(WeakReference):

    首先理解一下相关概念:
    强引用(Strong Reference):默认引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。在内存空 间不足时, Java虚拟机宁愿
    抛出OutOfMemory的错误,使程序异常终止,也不会强引用的对象来解决内存不足问题。
    如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
    弱引用(WeakReference):在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
    虚引用(PhantomReference):如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

    加入弱引用后的代码:
    1.  public class MainActivity extends AppCompatActivity {
    2.  private NoLeakHandler mHandler;
    3.  @Override
    4.  protected void onCreate(Bundle savedInstanceState) {
    5.  super.onCreate(savedInstanceState);
    6.  mHandler = new NoLeakHandler(this);
    7.  Message message = Message.obtain();
    8.  mHandler.sendMessageDelayed(message, 10 * 60 * 1000);
    9.  }
    10.   
    11.  
    12. private static class NoLeakHandler extends Handler {
    13.  //持有弱引用MainActivity,GC回收时会被回收掉.
    14.  private WeakReference<MainActivity> mActivity;
    15.  public NoLeakHandler(MainActivity activity) {
    16.  mActivity = new WeakReference<>(activity);
    17.  }
    18.   
    19.  @Override
    20.  public void handleMessage(Message msg) {
    21.  super.handleMessage(msg);
    22.  }
    23.  }
    24.  }

    Handler类中发送消息-post()和postDelay()方法精炼详解

    一、前期知识储备

    (1)上官方文档:参见Handler类中的描述

     

     

    首先,post和postDelay都是Handler的方法,用以在子线程中发送Runnable对象的方法;

    其次,Android中post()方法可以直接在非UI线程中更新UI,不同与Handelr的Send类方法,需要进行切换;

    最后,两个方法在实现UI线程事件的时间上有所区别,postDelayed()方法用以延期执行,post则是立即执行;

    (2)Handler类的post类方法和send类方法联系与区别

    ①post类方法,以匿名内部类的形式发送Runnable对象,在Runnable对象重写的run()方法中直接对UI进行更新;

    1.  new Thread(new Runnable() {
    2.  @Override
    3.  public void run() {
    4.  /**
    5.  耗时操作
    6.  */
    7.  handler.post(new Runnable() {
    8.  @Override
    9.  public void run() {
    10.  /** 更新UI */
    11.  }
    12.  });
    13.  }
    14.  }).start();

    三种切回主线程的实例:

    1.  final Handler handler = new Handler();
    2.  new Thread(new Runnable() {
    3.  @Override
    4.  public void run() {
    5.  // 素描算法处理 耗时操作
    6.  final Bitmap bitmap1 = SketchUtil.testGaussBlur(finalBitmap,1,1);
    7.  final Bitmap bitmap2 = SketchUtil.testGaussBlur(finalBitmap,10,10);
    8.  final Bitmap bitmap3 = SketchUtil.testGaussBlur(finalBitmap,20,20);
    9.  
    10.  // 三种切回主线程更新UI的方法
    11.  imageView.post(new Runnable() {
    12.  @Override
    13.  public void run() {
    14.  imageView.setImageBitmap(bitmap1); // 素描图
    15.  }
    16.  });
    17.  
    18.  runOnUiThread(new Runnable() {
    19.  @Override
    20.  public void run() {
    21.  orignView.setImageBitmap(bitmap2); // 素描图
    22.  }
    23.  });
    24.   
    25.  handler.post(new Runnable() {
    26.  @Override
    27.  public void run() {
    28.  threeView.setImageBitmap(bitmap3); // 素描图
    29.  }
    30.  });
    31.  }
    32.  }).start();

    注意:使用handler方法切回主线程时,注意handler的实例化要放在主线程中,而不能在新开的子线程中,否则报错:

    RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

    这是因为,Handler在哪里创建,就获得哪里的Looper。主线程创建的Handler,即默认使用主线程的Looper。

    常见常用的post()类方法汇总:

    post(Runnable)

    postAtTime(Runnable,long)

    postDelayed(Runnable long)

    ②send类方法,比如sendMessage()方法,使用该方法发送构造好的Message,然后用Handler的handleMessage()方法接收发送出来的消息,在方法中对UI进行更新;

    1.  private Handler handler = new Handler(){
    2.  @Override
    3.  public void handleMessage(Message msg) {
    4.  super.handleMessage(msg);
    5.  switch (msg.what) { //判断标志位
    6.  case 1:
    7.  /**
    8.  获取数据,更新UI
    9.  */
    10.  break;
    11.  }
    12.  }
    13.  };
    14.   
    15.   
    16.  public class WorkThread extends Thread {
    17.  
    18.  @Override
    19.  public void run() {
    20.  super.run();
    21.  /**
    22.  耗时操作
    23.  */
    24.   
    25.  //从全局池中返回一个message实例,避免多次创建message(如new Message)
    26.  Message msg =Message.obtain();
    27.  msg.obj = data;
    28.  msg.what=1; //标志消息的标志
    29.  handler.sendMessage(msg);
    30.  }
    31.  }

    常见常用的send类方法汇总:

    sendEmptyMessage(int)

    sendMessage(Message)

    sendMessageAtTime(Message,long)

    sendMessageDelayed(Message,long)

    分析:谷歌为Android系统提供了一系列的post类方法用以发送Runnable对象,又提供了一系列的send类方法用以发送Message对象,其实二者并不矛盾也不重复,打开post类方法的源码,就会发现最终发送的Runnable对象也会转变成Message对象进行发送。谷歌提供两类方法应该是分别处理不同的场景,发送的消息较为复杂时,且每种消息对应一种UI的更新时选择使用send类方法;而当子线程中只发出一种消息时,则直接使用post方法发送消息,且直接在post方法的内部实现UI的更新。

    (3)Message的构造

    1.  public final class Message implements Parcelable {
    2.  public int what;
    3.  public int arg1;
    4.  public int arg2;
    5.  public Object obj;
    6.  ...
    7.  }

    Message类中有这几个成员变量描述消息,其中what是我们定义的消息码,为了让接收者能知道消息是关于什么的。arg1和arg2用于发送一些integer类型的值。obj用于传输任意类型的值。

    (4)Handler接收消息的方法-handleMessage()方法

    Handler是Android系统为处理异步消息机制所提供的类,我们在实际开发中最常使用handler的场景即是在子线程中处理耗时操作后利用Handler的post类方法或者send类方法来发送消息;而当使用的是send类方法时,需要使用Handler来接收消息,同时使用Handler的handleMessage()方法来处理消息。

    1.  private static class MyHandler extends Handler{
    2.   
    3.  //对Activity的弱引用
    4.  private final WeakReference<HandlerActivity> mActivity;
    5.    public MyHandler(HandlerActivity activity){
    6.         mActivity = new WeakReference<HandlerActivity>(activity);
    7.  }
    8.   
    9. @Override
    10.  public void handleMessage(Message msg) {
    11.  HandlerActivity activity = mActivity.get();
    12.  if(activity==null){
    13.  super.handleMessage(msg);
    14.  return;
    15.  }
    16.  switch (msg.what) {
    17.  case DOWNLOAD_FAILED:
    18.  Toast.makeText(activity, "下载失败", Toast.LENGTH_SHORT).show();
    19.  break;
    20.  case DOWNLOAD_SUCCESS:
    21.  Toast.makeText(activity, "下载成功", Toast.LENGTH_SHORT).show();
    22.  Bitmap bitmap = (Bitmap) msg.obj;
    23.  activity.imageView.setVisibility(View.VISIBLE);
    24.  activity.imageView.setImageBitmap(bitmap);
    25.  break;
    26.  default:
    27.  super.handleMessage(msg);
    28.  break;
    29.  }
    30.  }
    31.  }

    这里为避免出现内存泄漏,使用了静态内部类和弱引用结合的方式来实现。

    二、上代码,看具体实现

    view.post(new Runnable() {

        @Override

        public void run() {

    view.performClick();//需要处理是事件,Android开发中常见的是UI操作

    }});

    注:post的方法意在main主线程执行完后立即调用。

    view.postDelayed(new Runnable() {

        @Override

        public void run() {

    view.performClick();//需要处理是事件,Android开发中常见的是UI操作

    }},3000);//延迟3秒注:postDelayed的方法意在延迟执行,在main主线程执行完后延迟3秒后开始调用。

    三、和Handler结合使用—开发中常见

    实际开发中,最常见的就是和Handler结合使用,开启异步任务。

    下面实现从网络中加载一张图片,并且显示在UI界面上

    (1)在 Activity 中创建 handler 对象,调用工作线程执行;

    1.  public class MainActivity extends AppCompatActivity {
    2.   
    3.  ImageView threadIv;
    4.  ImageView runnableIv;
    5.  SendThread sendThread;
    6.  PostRunnable postRunnable;
    7.  private final MyHandler handler = new MyHandler(this);
    8.   
    9.  @Override
    10.  protected void onCreate(Bundle savedInstanceState) {
    11.  super.onCreate(savedInstanceState);
    12.  setContentView(R.layout.activity_main);
    13.  threadIv = (ImageView) findViewById(R.id.thread_iv);
    14.  runnableIv = (ImageView) findViewById(R.id.runnable_iv);
    15.   
    16.  sendThread = new SendThread(handler);
    17.  sendThread.start();
    18.   
    19.  postRunnable = new PostRunnable(handler);
    20.  postRunnable.setRefreshUI(new PostRunnable.RefreshUI() {
    21.  @Override
    22.  public void setImage(byte[] data) {
    23.         runnableIv.setImageBitmap(getBitmap(data));
    24.  }
    25.  });
    26.  new Thread(postRunnable).start();
    27.  }
    28.  
       
    29.  /**
    30.  为避免handler造成的内存泄漏
    31.  1、使用静态的handler,对外部类不保持对象的引用
    32.  2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用
    33.  */
    34.  private static class MyHandler extends Handler {
    35.  private final WeakReference<Activity> mActivityReference;
    36.   
    37.  MyHandler(Activity activity) {
    38.  this.mActivityReference = new WeakReference<Activity>(activity);
    39.  }
    40.  
       
    41.  @Override
    42.  public void handleMessage(Message msg) {
    43.  super.handleMessage(msg);
    44.  MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity
    45.  switch (msg.what) { //获取消息,更新UI
    46.  case 1:
    47.  byte[] data = (byte[]) msg.obj;
    48.  activity.threadIv.setImageBitmap(activity.getBitmap(data));
    49.  break;
    50.  }
    51.  }
    52.  }
    53.   
    54. private Bitmap getBitmap(byte[] data) {
    55.  return BitmapFactory.decodeByteArray(data, 0, data.length);
    56.  }
    57.   
    58.  @Override
    59.  protected void onDestroy() {
    60.  super.onDestroy();
    61.  //避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列
    62.  handler.removeCallbacks(postRunnable); //清除runnable对应的message
    63.  //handler.removeMessage(what) 清除what对应的message
    64.  }
    65.  }

    (2)实现 runnable 接口,通过 post(Runnable)通信,并通过给定的回调接口通知 Activity 更新

    1.  public class PostRunnable implements Runnable {
    2.  private Handler handler;
    3.  private RefreshUI refreshUI;
    4.  byte[] data = null;
    5.  public PostRunnable(Handler handler) {
    6.  this.handler = handler;
    7.  }
    8.   
    9.  @Override
    10.  public void run() {
    11.  /**
    12.  * 耗时操作
    13.  */
    14.  final Bitmap bitmap = null;
    15.  HttpClient httpClient = new DefaultHttpClient();
    16. HttpGet httpGet = new HttpGet("http://i3.17173cdn.com/2fhnvk/YWxqaGBf/cms3/FNsPLfbkmwgBgpl.jpg");
    17.  HttpResponse httpResponse = null;
    18.  try {
    19.  httpResponse = httpClient.execute(httpGet);
    20.  if (httpResponse.getStatusLine().getStatusCode() == 200) {
    21.  data = EntityUtils.toByteArray(httpResponse.getEntity());
    22.  }
    23.  } catch (IOException e) {
    24.  e.printStackTrace();
    25.  }
    26.   
    27.  //返回结果给UI线程
    28.  handler.post(new Runnable() {
    29.  @Override
    30.  public void run() {
    31.  refreshUI.setImage(data);
    32.  }
    33.  });
    34.  }
    35.   
    36.  public interface RefreshUI {
    37.  public void setImage(byte[] data);
    38.  }
    39.  
       
    40.  public void setRefreshUI(RefreshUI refreshUI) {
    41.  this.refreshUI = refreshUI;
    42.  }
    43.  }

    文章后期更改过,将文章主旨内容从post()方法和postDelayed()方法的联系与区别转到Android中使用Handler进行异步消息的处理,以几段代码为例,分析了Handler的发送消息的方法:①post类方法;②send类方法;及Handler类接收消息和处理消息的方法handleMessage();最后模拟了从网上下载图片进而在UI界面中进行更新。

    提供一些其他Android消息机制分析,帮助理解读者理解:

    ①Handler是Android消息机制的上层接口,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行,该线程既可以是主线程,也可以是子线程,要看构造Handler时使用的构造方法中传入的Looper位于哪里;

    ②Handler的运行需要底层的MessageQueue和Looper的支撑,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper;

    ③上述代码中的第一个Handler-mainHandler,实例化的时候,直接在onCreate()方法中new出了实例,其实是其已经在主线程中了,主线程-ActivityThread,ActivityThread被创建时就会初始化Looper,这就是主线程中默认可以直接使用Handler的原因;

    ④上述代码中的第二个Handler-workHandler,它在实例化的时候,参数传入了 mHandlerThread.getLooper() ,注意,这个Handler使用的就不是主线程的Looper了,而是子线程的Looper,HandlerThread在调用start()方法之后,就可以获取到子线程的Looper,然后将其传入workHandler的构造方法中,那么此时的workHandler就会运行在子线程中,用于处理耗时操作。

    ⑤Handler的工作原理:Handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper,那么就会报错“Can`t create handler inside thread that has not called Looper.prepare()”解决方法有两个:为当前线程创建Looper即可,像上述代码中workHandler,或者在一个有Looper的线程中创建Handler也行,就像上述代码中的mainHandler一样;

    ⑥调用Handler的post方法会将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法来发送一个消息,这个消息同样会在Looper中去处理。其实post方法最终也是通过send方法来完成的。每当Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable的run方法或者Handler的handleMessage方法就会被调用。注意Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了;

    ⑦Looper的工作原理:Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。注意关注一些重要的Looper的方法:

    • Looper.prepare()-为当前线程创建一个Looper;
    • Looper.loop()-开启消息循环,只有调用该方法,消息循环系统才会开始循环;
    • Looper.prepareMainLooper()-为主线程也就是ActivityThread创建Looper使用;
    • Looper.getMainLooper()-通过该方法可以在任意地方获取到主线程的Looper;
    • Looper.quit() Looper.quitSafely()-退出Looper,自主创建的Looper建议在不使用的时候退出

    ⑧ActivityThread主线程通过ApplicationThread和AMS进行进程间通信

     
  • 相关阅读:
    Linux 安装Samba服务器
    常见状态码StatusCode
    urllib详细版
    set uniion()
    python urljoin
    'gbk' codec can't decode byte 0xad in position 12: illegal multibyte sequence
    php 的windows集成开发环境
    图文并茂解释数字证书(转)
    Web安全测试指南--权限管理
    互联互通协议安全设计
  • 原文地址:https://www.cnblogs.com/Alex80/p/14216496.html
Copyright © 2011-2022 走看看