HandlerThread

Android中为什么要采用Handler机制?Handler的作用是什么?

Android中的UI线程(主线程)是不安全的,一般来说在子线程中进行UI操作会导致UI线程的阻塞,所以Android提供一套Handler机制来实现异步消息处理。Handler机制的作用是:解决多线程并发的问题(协同其他线程工作),接收其他线程的消息并通过接收到的消息更新主UI线程的内容。

Handler机制是由那些部分构成?各个部分有什么作用?

Handler机制由MessageQueue、Looper以及Handler组成。各部分的功能如下:

MessageQueue的作用是依照队列先进先出的规则存储由其他线程的Handler发送过来的Message对象,然后Looper会轮询并获取这些Message对象。

Looper的作用是去轮询MessageQueue,当发现MessageQueue中存在Message,Looper就会将该Message传递给对应的Handler的handleMessage( )方法进行处理。(Looper就像是一个消息泵,把得到的Message泵给对应的Handler对象)

Handler的作用是发送消息给MessageQueue以及处理从Looper传递过来的Message。

子线程要实现Handler机制处理消息跟UI线程之间有什么不同?

UI线程中的MessageQueue以及Looper是默认创建好的,只要创建Handler对象实例并调用handleMessage方法即可进行消息处理,而子线程要自己去自定义Looper才能实现相同的功能。




子线程如何实现与其相关的Handler?

通过自定义的Looper去实现在子线程中处理消息,代码如下:

public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(new LinearLayout(this));

        MyThread myThread = new MyThread();

        myThread.start();

        myThread.mHandler.sendEmptyMessage(0x123);

    }


    class MyThread extends Thread {

        public Handler mHandler;

        public Looper mLooper;


        @Override

        public void run() {

            //创建Looper的实例对象,此时会创建一条与之对应的MessageQueue

            mLooper.prepare();

            //在新建的子线程当中创建Handler实例并调用handleMessage方法

            mHandler = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                    //此处不作任何操作

                }

            };

            //调用Loop()方法,让创建好的Looper去轮询MessageQueue

            // 在Loop()之后的代码将不会执行

            mLooper.loop();

        }

    }}

在子线程中使用自定义的Looper去实现Handler机制的消息处理可能会发生什么问题?

实际上,如果我们在子线程中使用自定义的Looper去实现子线程的Handler机制的消息处理功能有可能会因为线程并发的问题导致程序出现NullPointException。在如下实例代码中,程序会报出NullPointException的Error,Error的根源就是在UI线程中创建Handler实例时传入的myThread.mLooper对象,由于UI线程与子线程的并发问题,在UI线程使用这个myThread.mLooper对象时子线程有可能还没给这个对象创建出实例(创建出Looper和MessageQueue),从而使程序抛出NullPointException,实例代码如下:

public class MainActivity extends AppCompatActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(new LinearLayout(this));

        MyThread myThread = new MyThread();

        myThread.start();

        Handler handler = new Handler(myThread.mLooper) {

            @Override

            public void handleMessage(Message msg) {

                //此处不作任何操作

            }

        };

        handler.sendEmptyMessage(0x123);

    }


    class MyThread extends Thread {

        public Handler mHandler;

        public Looper mLooper;


        @Override

        public void run() {

            //创建Looper的实例对象,此时会创建一条与之对应的MessageQueue

            mLooper.prepare();

            //在新建的子线程当中创建Handler实例并调用handleMessage方法

            mHandler = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                    //此处不作任何操作

                }

            };

            //调用Loop()方法,让创建好的Looper去轮询MessageQueue

            // 在Loop()之后的代码将不会执行

            mLooper.loop();

        }

    }}

在主线程中创建Handler对象的时候传入myThread.mLooper的作用是为该Handler对象指定一个Looper,myThread.mLooper已经替代UI线程中默认的Looper,也就是说该Hanlder对象发送消息以后会由myThread.mLooper将消息泵给myThread.mHandler的handleMessage方法进行处理。

上述的程序逻辑并无错误,但将其运行一遍会发现程序会报出NullpointException。

怎么样克服子线程自定义Looper带来的问题呢?

想要避免这个NullPointException最简单的方法就是try/catch一下(哈哈哈,开玩笑的)。其实Android本来就有一套推荐的方案在子线程中实现Handler机制,不需要再额外地自定义Looper这么麻烦,Android推荐使用HandlerThread来在子线程中实现Handler机制。

HandlerThread是Handler类还是Thread类?它究竟是什么?

HandlerThread其实就是Thread类,它跟普通的Thread相比就是多了一个Looper,但就是多了这么一个Looper让我们省却自定义Looper和担心由于线程并发而造成Looper在未创建成功就被调用的的麻烦。

怎么使用HandlerThread?

HandlerThread的使用方法很简单,大致分为4步,直接上码:

public class MainActivity extends AppCompatActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(new LinearLayout(this));

        //第一步,创建HandlerThread对象,并为它指定名称  

        HandlerThread handlerThread = new HandlerThread("handler_thread");

        //第二步,调用start()方法开启线程

        handlerThread.start();

        //第三步,创建Handler对象,传入handlerThread.getLooper()参数

        Handler handler = new Handler(handlerThread.getLooper()) {

            @Override

            public void handleMessage(Message msg) {

                //此处不作任何操作

            }

        };

        //第四步,发送消息

        handler.sendEmptyMessage(0x123);

    }}

其实在上述的第四步发送消息的这一步其实并不只是可以用sendEmptyMessage方式,可以新开线程去发送消息,也可以调用post方法以后再发送消息,但最最重要的是要明白,在第三步中,把原本是绑定UI线程Looper的Handler设置成绑定HandlerThread的Looper。所以程序的Handler现在是与HandlerThread进行关联,这也就达到了在子线程上实现Handler机制的目的!!

从线程通信的角度来看待Handler和HandlerThread分别解决了什么?

Handler的目标是解决子线程与主线程通信的问题,而HandlerThread则是解决子线程和子线程之间的通信问题

最后总结两句:

MessageQueue、Looper和Handler是组成Handler机制的铁人三项,虽然我们平时比较少接触到MessageQueue和Looper,但是其作用,原理以及用法还是很重要的,MessageQueue负责存储由Handler发送过来的Message,Looper则不断轮询MessageQueue,将MessageQueue中的Message交由Handler进行处理,Handler根据Looper传来的Message进行信息处理或UI更新



观察HandlerThread的官方文档的两句:①Thread. Handy class for starting a new thread that has a looper.②The looper can then be used to create handler classes.

释义:HandlerThread对象start后可以获得其Looper对象,并且使用这个Looper对象实例Handler,之后Handler就可以运行在其他线程中了。

HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,然后在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。

// 子线程中创建新的Handler 没有使用HandlerThread

new Thread () {

    @Override

    public void run() {

        Looper.prepare();

        Hnadler handler = new Handler();

        Looper.loop();

    }

}

mSMTHandlerThread = new HandlerThread("SMT-Handler-thread");

        mSMTHandlerThread.start();

        mSMTHandler = new Handler(mSMTHandlerThread.getLooper());

Handler里部分源码:

public Handler() {

        throw new RuntimeException("Stub!");

    }

    public Handler(Handler.Callback callback) {

        throw new RuntimeException("Stub!");

    }

    public Handler(Looper looper) {

        throw new RuntimeException("Stub!");

    }

    public Handler(Looper looper, Handler.Callback callback) {

        throw new RuntimeException("Stub!");

    }

 当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,

该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,

quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。最后需要注意的是Looper的quit方法是基于API 1,

而Looper的quitSafely方法则是基于API 18的。

quit()和quitSafely()的区别在于,quit()会清空所有消息(无论延时或非延时),quitSafely()只清空延时消息,无论是调用了quit()方法还是quitSafely()方法,

Looper就不再接收新的消息。即在调用了Looper的quit()或quitSafely()方法之后,消息循环就终结了,

这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,线程也就结束了

mSMTHandler.removeCallbacks(timeoutRunnable);

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

推荐阅读更多精彩内容