【Android学习】消息机制Handler

0,概念

1)消息机制

Handler是Android消息机制的上层接口。

2)Handler、MessaegQueue和Loop

消息循环流程图
一个线程有一个Looper,一个MessageQueue。可以有很多个Handler,发送各自的Message到这个MessageQueue中。

3)场景

①更新UI
只有主线程可以访问UI。
②处理消息
③将任务(耗时任务)切换到某个指定的线程中执行

2,角色

1)Message(消息)

分为硬件产生的消息(例如:按钮、触摸)和软件产生的消息。

2)MessageQueue(消息队列)

①概念

Android启动程序时会建立一个MessageQueue。主要用来向消息池添加消息和取走消息。

②数据结构

单链表

③功能

消息的存储单元,不能处理消息。

④工作原理

MessageQueue有两个操作:插入和读取。

enqueueMessage(插入)

向队列中插入一条消息。

next(读取)

从消息队列读取数据并移除。

3)Looper(消息循环器)

①概念

一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。主要用来把消息分发给相应的处理者。
线程默认没有Looper,使用时需要创建Looper。
ActivityThread(主线程)被创建时会初始化Looper,故主线程中默认可以使用Looper。

以无限循环的形式查找是否有新消息,如果有就处理,否则一直等待。

注意:Looper运行在创建Handler所在的线程中。

②ThreadLocal

不是线程,是一个线程内部的数据存储类,作用是可以在每个线程中存储数据。
ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,Handler通过ThreadLocal可以轻松获取每个线程的Looper。
即时是用同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中去取各自的数组,数据之前是互不干扰的。

使用场景:
一、当某些数据是以线程为作用域并且不同线程具有不同的数据副本。
二、复杂逻辑下的对象的传递,如监听器的传递。
采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。

③实现

i>创建Looper
一、Looper.prepare(),通过Looper.loop()开启消息循环。
loop方法是一个死循环,唯一跳出方式:MessageQueue的next方法返回null。
二、prepareMainLooper方法,这个方法是给主线程创建Looper使用。
通过getMainLooper方法,可以在任何地方获取到主线程的Looper。
ii>退出Looper
建议不需要的时候,终止Looper。
一、quit
直接退出Looper。
二、quitSafely
设定退出标记,然后吧消息队列中的已有消息处理完才安全退出。

④Can’t create handler inside thread that has not called Looper.prepare()

Looper类用来为一个子线程开启一个消息循环。默认情况下Android中新诞生的非主线程没有开启消息循环(主线程诞生时系统会自动为其创建开启消息循环的Looper对象),对于非主线程需要先调用Looper.prepare()启用Looper,然后通过调用。
问题来自于在AsyncTask的onPostExecute(Integer result)方法中,即非主线程中直接new Handler。

除了启用Looper,另一种解决方式:
i>定义一个接口

public interface UIListener {

    /**
     * 获得字符串,更新UI
     * @param type UI更新类型
     * @param msg UI更新信息
     */
    public void onEvent(int type, String msg);
}

ii>I更新层,实现接口。
在onEvent方法具体实现,发送消息给handle,由handle执行toast。

    @Override
    public void onEvent(int type, String msg) {     

        switch (type) {
        case UIType.showCustomToast:

            dismissLoadingDialog();
            Message msg_toast = new Message();
            msg_toast.what = 8;
            msg_toast.obj = msg;
            handler.sendMessage(msg_toast);  
            break;

        default:
            break;
        }
    }

iii>在AsyncTask中,通过调用接口的方式,将异步线程的执行结果传到UI更新层。

                @Override
                protected void onPostExecute(Integer result) {
                    super.onPostExecute(result);

                    if (Result.NETWORK_ERROR == result) {

                        ((MainActivity) mContext).onEvent(UIType.showCustomToast, "网络异常,未进行正常更新");
                    } else if (Result.SUCCESS == result) {

                        RefreshData();
                    }
                    if (Result.ERROR == result) {

                        ((MainActivity) mContext).onEvent(UIType.showCustomToast, "新任务获取失败");
                    }
                }

4)Handler(消息处理器)

Handler是Android消息机制的上层接口。
Handler的运行需要底层的MessageQueue和Looper的支撑。
主要向消息队列发送各种消息以及处理各种消息。

5)工作原理

①Handler创建时

采用当前线程的Looper来构建内部的消息循环系统(如果当前线程没有Looper会报错,需创建Looper)。

②Handler创建完毕后

通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理,也可以通过send方法来完成。

使用handler发送消息时有两种方式,都是将指定Runnable(包装成PostMessage)加入到MessageQueue中,然后Looper不断从MessageQueue中读取Message进行处理。

post(Runnable r)
postDelayed(Runnable r, long delayMillis):可以精确传递时间但又不阻塞队列。

postDelayed一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
直到阻塞时间到或者下一次有Message进队;

这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。

③send方法

post方法最终也是通过send方法来完成的。
当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就好被调用。

6)消息流程

①Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
②Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
③target handler调用自身的handleMessage()方法来处理Message。
消息流程
在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。
注:虚线表示关联关系(它们发生关联的桥梁是MessageQueue),实线表示调用关系。

7)Demo

package com.luo.activity;  

import android.app.Activity;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Message;  
import android.widget.Toast;  

public class MainActivity extends Activity {  

    private Handler handler;// 获取数据变更  

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

        init();  
        sendMsg(0, 1);  
    }  

    private void init() {  

        handler = new Handler() {  
            public void handleMessage(Message msg) {  

                switch (msg.what) {  
                case 0:  
                    Toast.makeText(MainActivity.this, "this is handler" + msg.arg1, Toast.LENGTH_LONG).show();  
                    break;  

                default:  
                    break;  
                }  
            }  
        };  
    }  

    public void sendMsg(int flag, int value) {  
        Message message = new Message();  
        message.what = flag;  
        message.arg1 = value;  
        handler.sendMessage(message);  
    }  
}  

3,内存泄漏

1)原因

使用内部类、匿名类来创建Handler,这样会造成内存泄露!
点击查看什么是内存泄漏

原因:当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。
而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。
如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。

2)解决方案

①通过程序逻辑来进行保护(推荐)

i>在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
ii>如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

②将Handler声明为静态类

静态类不持有外部类的对象,所以Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),具体实现如下:

static class MyHandler extends Handler {  
    @Override  
    public void handleMessage(Message msg) {  
        mImageView.setImageBitmap(mBitmap);  
    }  
} 
static class MyHandler extends Handler {  
    WeakReference<Activity > mActivityReference;  

    MyHandler(Activity activity) {  
        mActivityReference= new WeakReference<Activity>(activity);  
    }  

    @Override  
    public void handleMessage(Message msg) {  
        final Activity activity = mActivityReference.get();  
        if (activity != null) {  
            mImageView.setImageBitmap(mBitmap);  
        }  
    }  
}  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android handler机制是一种在Android中用于处理异步消息和线程通信的重要机制。其主要作用是将消息和任务抛到主线程的消息队列中,以便主线程按照一定的规则按照队列中的顺序处理这些消息和任务。 1. Handler的实例化:在Android中,创建一个Handler的实例可以通过以下两种方式实现: 直接实例化: 创建一个Hanlder对象,代码示例: Handler handler = new Handler(); 绑定Looper:在子线程中创建Handler的话,需要先通过以下代码获取Looper对象,然后再将其传入Handler的构造函数中: Looper looper = Looper.myLooper(); Handler handler = new Handler(looper); 2. Handler的使用:当一个线程需要将一个耗时的任务交给主线程去执行的时候,它可以使用Handler来完成。以下是Handler的常见用法示例: 使用Handler处理消息Handler myHandler = new Handler() { public void handleMessage(Message msg) { //在这里处理消息 } }; Message msg = myHandler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putString("my_key", "my_string"); msg.setData(bundle); myHandler.sendMessage(msg); 使用Handler更新UI: Handler myHandler = new Handler() { public void handleMessage(Message msg) { //更新UI } }; myHandler.sendEmptyMessage(0); 3. Handler的原理:在Android系统中,所有的UI操作都必须在主线程上完成。这时,Handler就扮演了一个传递任务的中介角色。它把来自子线程的消息和任务封装成Message对象,最后以一定的顺序扔到主线程的消息队列中等待执行。主线程通过Looper不断地循环读取消息队列中的消息和任务,然后依次执行。这就是Handler机制的基本原理。 4. Handler配合其他机制使用:为了更好地发挥Handler的作用,还可与一些其他机制有效配合使用,例如:AsyncTask、MessageQueue等等。通过多种机制的相互配合,可以实现复杂的异步消息处理和并发控制,提升了Android应用程序的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值