Android中的异步消息机制分为四个部分:Message、Handler、MessageQueue和Looper。

其中,Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象。

Handler是处理者,主要用于发送消息和处理消息。发送消息的方法是sendMessage;处理消息的方法是handleMessage(),Message字段携带的信息在该方法中用作判别。

MessageQueue是消息队列,存放所有Handler发送的消息。

Looper是消息队列的“管家”,将消息从消息队列中一条条取出,并分派到Handler的handleMessage()方法中。

————————————————————————————————————————————————————————————————————————————————

异步消息处理的流程为:

①首先,需要在主线程中创建一个Handler对象,并重写handleMessage()方法。

②当子线程处理完耗时操作,需要将处理结果反馈到UI中时,先创建一个Message对象,并让其what字段携带一个int值,然后通过Handler对象发送出去。

③之后该消息会被添加到MessageQueue中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler对象中的handleMessage()方法中。由于Handler对象是在主线程中创建的,所以可以在handleMessage()方法中安心地进行UI操作。

————————————————————————————————————————————————————————————————————————————————

通过一个例子来验证一下:活动MainActivity中有一个按钮和一个TextView。TextView初始化显示“Hello World!”,之后点击按钮,进行耗时操作;耗时操作结束后,TextView显示“Nice to meet you”。根据以上的分析,我无比自然地写出了以下代码:

package com.shaking.androidthreadtest;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final int UPDATE_TEXT=1;
private String data;
private TextView textView; private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE_TEXT:
textView.setText(data);
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
Button button=findViewById(R.id.button);
textView=findViewById(R.id.text_view);
button.setOnClickListener(this);
} @Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
//假设此处进行了耗时操作,最终得到结果字符串data
data="Nice to meet you";
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
}
}

首先,这么写,是肯定没有错误的!程序也可以正常运行。但是IDE给出了警告:“This Handler class should be static or leaks might occur”。

这个警告的意思是:我们使用Handler这个类时,应该将其声明为静态,否则会导致内存泄露。

那么,为什么会发生内存泄露呢?原因是:

第一:当我们通过Handler对象的sendMessage()方法发送一个Message对象时,该Message对象持有对该Handler对象的引用(正是依靠这个引用,Looper在消息队列中取出该Message对象后,才能准确地将该Message对象分派回该Handler对象!)。

第二,我们在主线程中创建Handler对象时,为了重写其handleMessage()方法,使用了匿名内部类的方式来创建该Handler对象。而匿名内部类和非静态内部类都是隐性地持有一个对外部类的引用!所以,该Handler对象持有外部类MainActivity的引用。

以上两个结合在一起,问题就来了:Message对象持有Handler对象引用,Handler对象持有MainActivity的引用。所以,MainActivity该活动永远无法被内存回收,直到Message被回收为止!如果Message对象在子线程中被发送至消息队列,然后一直没有被处理,该活动所在的主线程也会一直挂着,而不会被内存回收。所以,会导致内存泄露。

————————————————————————————————————————————————————————————————————————————————

知道了原因,那么解决方法是什么?其实之前的警告,已经给出了解决方案。那就是通过静态内部类的方式创建Handler对象,因为静态内部类不会持有对外部类对象的引用。

这时候,我又自然而然地创建一个静态内部类,继承自Handler类,然后重写其handleMessage方法。

 private static class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) { }
}

但是,此处又出现了一个问题!如果我不持有对外部类的引用了,那么我怎么使用外部类的方法和对象?毕竟我是要在handleMessage()方法中进行UI操作的。

对于这种使用了静态内部类来避免内存泄露,同时又需要调用外部类的方法的情况:可以使用弱引用!即我们在该内部类中声明一个对外部类对象的弱引用。这样即可以调用外部类的方法,又不会导致内存泄露。

具体修改后的代码,如下:

package com.shaking.androidthreadtest;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.lang.ref.WeakReference; public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final int UPDATE_TEXT=1;
private String data;
private TextView textView; private static class MyHandler extends Handler{
//使该内部类持有对外部类的弱引用
private WeakReference<MainActivity> weakReference;
//构造器中完成弱引用初始化
MyHandler(MainActivity activity){
weakReference=new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
//通过弱引用的get()方法获得外部类对象的引用
MainActivity activity=weakReference.get();
activity.textView.setText(activity.data);
}
}
//创建Handler对象
private MyHandler handler=new MyHandler(this); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
Button button=findViewById(R.id.button);
textView=findViewById(R.id.text_view);
button.setOnClickListener(this);
} @Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
//假设此处进行了耗时操作,最终得到结果字符串data
data="Nice to meet you";
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
}
}

完美解决以上所有问题!6~

最后推荐直接使用最后的解决方案:静态内部类+弱引用。

Android异步消息机制的更多相关文章

  1. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

  2. Android 异步消息处理机制前篇(二):深入理解Message消息池

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 上一篇中共同探讨了ThreadLocal,这篇我们一起看下常提到的Message消息池到底是怎么回事,废话少说吧,进入正题. 对于稍有经验的开发人员 ...

  3. Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.概述 我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这 ...

  4. Android 基础 十一 Android的消息机制

    Handler是Android消息机制的上层接口,这使得在开发应用过程中我们只需要和Handler交互即可.Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去 ...

  5. Android异步消息传递机制源码分析

    1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.p ...

  6. Android异步消息处理机制(多线程)

    当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去执行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用. ...

  7. 聊一聊Android的消息机制

    聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信. 从技术实现上来说,消息机制还是比 ...

  8. 【转载】Android异步消息处理机制详解及源码分析

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  9. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

随机推荐

  1. python抽象篇:面向对象

    1.面向对象概述 面向过程编程:根据操作数据的函数或语句块来设计程序的. 函数式编程:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象编程:数据和功能结合起来,用称为对象的东西包 ...

  2. trait与policy模板应用简单示例

    trait与policy模板应用简单示例 accumtraits.hpp // 累加算法模板的trait // 累加算法模板的trait #ifndef ACCUMTRAITS_HPP #define ...

  3. Leetcode题解(十五)

    42.Trapping Rain Water 题目 这道题目参考http://www.cnblogs.com/felixfang/p/3713197.html 观察下就可以发现被水填满后的形状是先升后 ...

  4. Ubuntu下比较通用的makefile实例

    本文转自http://blog.chinaunix.net/uid-20608849-id-360294.html  笔者在写程序的时候会遇到这样的烦恼:一个项目中可能会有很多个应用程序,而新建一个应 ...

  5. poj2635The Embarrassed Cryptographer(同余膜定理)

    The Embarrassed Cryptographer Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 15069   A ...

  6. 多项式求和,素数判定 HDU2011.2012

    HDU 2011:多项式求和 Description 多项式的描述如下: 1 - 1/2 + 1/3 - 1/4 + 1/5 - 1/6 + ... 现在请你求出该多项式的前n项的和.   Input ...

  7. 0_Simple__cdpSimplePrint + 0_Simple__cdpSimpleQuicksort

    CUDA动态并行的简单实践,以及利用CUDA动态并行实现快排算法(有单线程的递归调用) ▶ 源代码:动态并行递归调用线程块 #include <iostream> #include < ...

  8. JavaNIO阻塞IO

    package com.java.NIO; import java.io.IOException; import java.net.InetSocketAddress; import java.nio ...

  9. Grunt打包seajs项目

    在使用seajs时,常常将若干脚本分为多次require进来,这样开发中比较方便,但是,会增加http请求次数,在生产环境中需要进行打包合并.压缩等操作. 以Grunt构建工具为例,对一个seajs项 ...

  10. DOM 对象

    DOM  ==  document object model   document 对象是唯一同时属于  BOM 和 DOM  的   rows 是一种DOM集合,不是数组,所以没有sort() 函数 ...