Handler

1、定义

一套 Android 消息传递机制

2、作用

在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

3、意义

  • 问:为什么要用 Handler消息传递机制
  • 答:多个线程并发更新UI的同时 保证线程安全

4、 使用方式

  • Handler的使用方式 因发送消息到消息队列的方式不同而不同
  • 共分为2种:使用Handler.sendMessage()、使用Handler.post()
    Message:携带数据:
    what:用户定义的消息代码,以便收件人可以识别
    arg1和arg2用来携带整型数据
    obj用来携带任意对象
    ①发送普通消息
public class MainActivity extends AppCompatActivity {
Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Toast.makeText(MainActivity.this, "收到消息了", Toast.LENGTH_SHORT).show();
            }
        }
    };

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

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                handler.sendMessage(message);
            }
        }, 2000);
    }
}

②handler+HTTPURLConnection

public class MainActivity extends AppCompatActivity {

    private String smartUrl = "https://gank.io/api/data/%E7%A6%8F%E5%88%A9/20/1";

    Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Toast.makeText(MainActivity.this, "收到消息了", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    String json = (String) msg.obj;
                    SmartBean smartBean = new Gson().fromJson(json, SmartBean.class);
                    smartBean.getResults();
                    break;
            }
        }
    };

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

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                handler.sendMessage(message);
            }
        }, 2000);
        new Thread(() -> {
            try {
                URL url = new URL(smartUrl);
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                int responseCode = con.getResponseCode();
                if (responseCode == 200) {
                    InputStream is = con.getInputStream();
                    InputStreamReader isr = new InputStreamReader(is, "gbk");
                    char[] chars = new char[1024];
                    StringBuilder sb = new StringBuilder();
                    int len = 0;
                    while ((len = isr.read(chars)) != -1) {
                        sb.append(chars, 0, len);
                    }

                    String json = sb.toString();
                    Message message = Message.obtain();
                    message.what = 2;
                    message.obj = json;
                    handler.sendMessage(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

    }
}

5、 相关概念

名称 定义 作用
UI线程 主线程(Main),用于UI更新 当数据变化,需要展示界面时,就要更新到UI线程
子线程 又叫工作线程,主要处理耗时操作 因为UI线程逻辑处理时间有限,耗时操作要放到子线程,避免ANR
Handler 线程通信的对象,线程之间任务的处理者 添加到消息队列里,接受Looper派发过来的对象
Message 消息体对象 封装要传送的对象,类似一个载体
Looper 轮训机制的对象 消息获取:不断从消息队列里拿出数据,发送给handler处理,每一个线程只能有一个Looper对象
MessageQueue 消息队列,一个数据结构(先进先出) 维护和保存发送过来的消息

那么和Handler 、 Looper 、Message、MessageQueue有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。
主线程创建一个handler对象后维护一个Looper,Looper创建一个MessageQueue,通过死循环一直检测MQ是否有消息进来,如果有通知handler处理消息,并且handler就是负责往MQ发消息的对象

6、 图解

handler.png

7、 源码分析

  1. 新建一个handler获取到Looper对象和消息队列
new Handler()
        --->this(null, false); 
        --->Looper mLooper = Looper.myLooper();
        --->MessageQueue mQueue =  mLooper.mQueue

通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。

  1. 发送消息
    两种方法:post(6种)和send(7种)
    最终都是handler.enququMessage --> MessageQueue.enququMessage
handler.sendMessage(message);
        --->sendMessageAtTime()
        --->enqueueMessage(queue, msg, uptimeMillis)
        --->msg.target = this;  ---  Handler对象
        --->queue.enqueueMessage(msg, uptimeMillis)  --  消息存到消息队列并得到其维护
  1. Looper:对于Looper主要是prepare()和loop()两个方法。

首先看prepare()方法

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量,实现了线程数据的隔离。将一个Looper的实例放入了ThreadLocal,并且判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例
注意:一个线程中只能有一个Looper对象,只能有一个消息队列MessageQueue

下面看Looper的构造方法:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

在构造方法中,创建了一个MessageQueue(消息队列)。

loop()方法:

final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。

        //拿到该looper实例中的mQueue(消息队列)
        final MessageQueue queue = me.mQueue;
        // 进入了我们所说的无限循环。
        for (; ; ) {
            // 取出一条消息,如果没有消息则阻塞。
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            。。。。

            msg.target.dispatchMessage(msg);
            ---handler

        }

Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target(handler)属性的dispatchMessage()去处理。

  1. handler.dispatchMessage():
        if (msg.callback != null) {
            handleCallback(msg); --- >回调自身Runnable中run方法,使用post方法的时候
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); --- >子类必须重写
        }

        
        回到最后处理的方法:
            private static void handleCallback(Message message) {
                message.callback.run();
            }
        
            /**
             * Subclasses must implement this to receive messages.
             */
            public void handleMessage(Message msg) {
            }

使用调用 msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。

4、子线程创建handler

其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行(HandlerThread)。

new Thread() {
            private Handler handler;

            public void run() {

                Looper.prepare();

                handler = new Handler() {
                    public void handleMessage(android.os.Message msg) {
                        Log.e("TAG", Thread.currentThread().getName());
                    };
                };
                Looper.loop();
            }
        }

5、 总结

  1. Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。

  2. Handler的sendMessage方法,会给msg的target赋值为handler自身,然后将Message加入MessageQueue中。

  3. Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

  4. Looper.loop()会让当前线程进入一个无限循环,从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

  5. handler.dispatchMessage(msg)分发消息,如果是sendMessage(),会回调重写的handleMessage方法;如果是post(),会最后会回调 message.callback.run(),当前的run()方法。

  6. 在Activity中,我们并没有显式的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码(ActivityThread)中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

  7. 疑问:主线程死循环为什么不会卡死App?
    真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容