文档章节

Handler消息传递机制分析

t
 tommwq
发布于 11/15 01:17
字数 1046
阅读 39
收藏 1

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

Handler的用途和用法

写过Android程序的人大概都会遇到ANR(Application Not Responding)。如果程序在一段时间内没有响应,系统就会弹出一个对话框,让用户选择继续等待还是强制关闭应用。为了避免ANR,我们需要把耗时的逻辑放到后台线程里执行。但是后台线程无法更新界面。那么当任务完成后,如何根据结果更新界面呢?Handler就可以承担这个职责。下面的例子展示了Handler的用法:

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
                handler.sendMessage(message);
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                textView.setText(text);
            }
        }
    };

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

为什么handleMessage()可以更新界面呢?因为handlerMessage()是在主线程中调用的。主线程中存在一个无限循环,不断的将消息分派给Handler处理。执行这个无限循环的对象就是Looper。

Looper和MessageQueue

Looper是处理消息的主循环,Activity的消息全部由Looper进行分派。下面的代码是从ActivityThread.main()截取的,从这里可以看到,Android应用的主循环就是Looper.loop()。

public static void main(String[] args) {

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

下面的代码是从Looper.java中截取的,部分函数有删减。可以看到每个线程有一个Looper对象,它的方法looper()就是线程中处理消息的主循环。looper()不断从MessageQueue中获取消息,交给Message.target.dispatchMessage()。Message.target是一个Handler,dispatchMessage()方法里会调用handleMessage()。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
        sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;

        for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                        return;
                }

                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                msg.recycleUnchecked();
        }
}

Looper通过MessageQueue.next()方法得到消息,这是一个阻塞方法,大致的流程如下:

Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
                nativePollOnce(ptr, nextPollTimeoutMillis);

                long now = SystemClock.uptimeMillis();
                Message msg = get_next_message();

                if (now < msg.when) {
                        nextPollTimeoutMillis = msg.when - now;
                } else {
                        return msg;
                }

                nextPollTimeoutMillis = 0;
        }
}

nativePollOnce()是一个本地方法,实际作用大致相当于sleep()。和sleep()不同的是,如果在nextPollTimeMills之前收到了新消息,nativePollOnce()会立即返回。这是通过内部调用的epoll系统调用实现的。

有的读者可能会奇怪,消息是通过Handler.sendMessage()发送的,Message对象如何传递到Looper.mQueue中的呢?在Handler的构造函数中,如果没有传入Looper对象,Handler会将当前线程的Looper对象和Looper的mQueue成员保存起来。在Handler.sendMessage()方法中,消息会传递给MessageQueue。

void sendMessage(Message message) {
        enqueueMessage(queue, message);
}

void enqueueMessage(MessageQueue queue, Message message) {
        queue.enqueueMessage(message);
}

Handler内存泄漏

前面提到Message.target是一个Handler对象。如果这个Handler定义为Activity的内部类(本文的第一个例子就是这样),当Activity退出时,如果Looper的消息队列中还有Message对象,那么Message.target会持有Activity的引用(通过内部类),导致Activity无法回收,这就是所谓的Handler内存泄漏问题。要解决这个问题需要做到两点,一是将Handler定义为静态内部类或非内部类。二是在退出Activity时清空消息队列。下面的例子展示了这两点。

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activity;

        public MyHandler(MainActivity aActivity) {
            activity = new WeakReference<>(aActivity);
        }

        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                MainActivity mainActivity = activity.get();
                if (mainActivity != null) {
                    mainActivity.textView.setText(text);
                }
            }
        }
    };

    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new MyHandler(this);
        backgroundThread.start();
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
    }
}

© 著作权归作者所有

t

tommwq

粉丝 3
博文 35
码字总数 34798
作品 0
广州
私信 提问
Android Handler异步通信:深入详解Handler机制源码

前言 在开发的多线程应用场景中,机制十分常用 今天,我将手把手带你深入分析 机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 在多线程的应用场景中,将工作线程中需更新的操作信息 ...

carson_ho
2018/05/21
0
0
Handler机制原理(一)宏观理论分析与Message源码分析

预热 在写这篇文章前我不止一次的问自己,网上分析Handler机制原理的文章那么多,为啥还要画蛇添足啊?不是说前人们写的文章不好,我就是觉得他们写的不细,有些点不讲清楚,逻辑很难通顺的,...

吴七禁
2017/11/07
0
0
Android开发——子线程操作UI的几种方法

在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法: 在看方法之前需要了解一下Android中的消息机制。 转载请标明出处...

SEU_Calvin
2016/08/04
0
0
android基础知识02——线程安全3:Message,MessageQueue,Handler,Looper

android的UI操作不是线程安全的,同时也只有主线程才能够操作UI,同时主线程对于UI操作有一定的时间限制(最长5秒)。为了能够做一些比较耗时的操作(比如下载、打开大文件等),android提供...

迷途d书童
2012/03/23
792
0
进程/线程概念和Android异步通讯机制

1、操作系统中线程、进程概念 进程是资源分配和调度的独立单位,进程将内存地址空间、程序、数据等资源组织起来,使操作系统容易管理这些资源。 线程是CPU调度和分派的基本单位,线程必须依赖...

JouTzaShin
2013/11/17
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

ForkJoinPool线程池

1. 拆分线程池的使用场景是什么? 答: 是对一组连续的数据进行耗时操作,例如 一个 大小为 10000 的集合 进行操作。 例子: 对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序...

杨凯123
8分钟前
2
0
在多列上使用group by

我理解GROUP BY x的观点 但GROUP BY x, y如何运作的,它是什么意思? #1楼 Group By X表示将所有具有相同X值的组合放入一组中 。 Group By X, Y表示将所有具有相同值的值放在一个组中的X和Y...

技术盛宴
24分钟前
2
0
线程池ThreadPoolExecutor的内部类Worker的感想和思考

Worker依然是一个Runnable,封装了一个创建自己的原因对象,就是firstTask变量,和自己将要执行的所在线程thread变量。 thread成员变量可以直接被外部类ThreadPoolExecutor所获得,当调用add...

萧默
今天
2
0
Git推送错误“ [[远程拒绝]主机->主机(分支当前已签出)”)

昨天,我发布了一个有关如何将Git存储库从我的一台计算机克隆到另一台计算机的问题 , 如何从另一台计算机“ git clone”? 。 现在,我可以成功地将Git存储库从源(192.168.1.2)克隆到目标...

javail
今天
4
0
Selenium 4.0 Alpha更新日志

早在2018年8月,整个测试自动化社区就发生了一件重大新闻:Selenium的创始成员Simon Stewart在班加罗尔Selenium会议上正式确认了Selenium 4的发布日期和一些重要更新。 Selenium 4.0 Alpha版...

八音弦
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部