Android多线程:HandlerThread的原理及使用

前言

在Andorid实现多线程的方式中, HandlerThread 的使用并不常见,最近开始扎实Android基础,我们都知道,若是在子线程中创建Handler实例并调用 sendMessage() 方法时,子线程由于并不会创建 LopperMessageQueue 对象,等同于消息没有入队(MessageQuue),消息也无法实现出队循环(Looper),故在子线程发送的消息任务无法执行,这时候需要调用方法 Looper.prepare()和Looper.loop() 实现消息的入队、出队、循环分发给指定的Handler。

为了解决这个问题,Android封装了自己的HandlerThread,在内部调用方法 Looper.prepare()和Looper.loop() ,方便了开发人员的使用。

下面我们来揭开HandlerThread的神秘面纱:

1.是什么

HandlerThread是Android封装好的异步消息处理类,其原理即是继承自Thread并封装了Handler

2.为什么

  • 保证多线程并发需要更新UI线程时的线程安全
  • 不需要使用任务线程(继承自Thread)+ Handler的复杂组合,方便了开发人员使用创建新线程与其他线程通信的过程。

3.怎么做

原理:继承的Thread类 + 封装的Handler类

  • 继承的Thread类:快速创建一个带有Looper的工作线程
  • 封装的Handler类:快速创建Handler与其他线程通信

4.使用步骤

1)创建ThreadHandler实例对象mThreadHandler
2)开启线程mThreadHandler.start()
3)创建工作线程的Handler实例,workHandler,并实现handleMessage方法
4)使用工作线程workHandler向工作线程的消息队列发送消息
workHandler.sendMessage(msg);
5)停止线程
mThreadHandler.quit()
mThreadHandler.quitSafely()

  • demo 示例完整代码
    模拟窗口卖票,每个窗口有6张票,工作线程workHandler发送一条消息则开启一个窗口开始买票
public class HandlerThreadActivity extends AppCompatActivity {

    private int j = 1;
    private String note;
    private TextView note_text;
    private Handler workHandler = new Handler();
    private HandlerThread handlerThread;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        initView();
        initHandlerThread();
    }

    private void initHandlerThread() {

        //step1: 创建HandlerThread实例,传入的参数为线程名称
        handlerThread = new HandlerThread("based on yourself");

        //step2: 手动调用start()开启线程
        handlerThread.start();

        //step3:
        //创建Handler,关联HandlerThread的Looper
        //复写handleMessage根据消息更新UI布局,处理消息的线程即是创建的线程handlerThread
        workHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(final Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        break;
                    case 2:
                        for (int i = 1; i < 7; i++) {
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            note = msg.obj + "卖出" + i + "张票";
                            note_text.setText(note);
                            Log.d("workHandler----", note);
                        }
                        break;
                }
            }
        };
    }

    private void initView() {
        note_text = findViewById(R.id.note_text);
        Button startThread = findViewById(R.id.start_thread);
        startThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //step4: 点击一次Button,工作线程workHandler向工作线程队列发送消息
                Message msg = Message.obtain(); //不用new一个Message,采用obtain
                msg.what = 2;
                msg.obj = "窗口" + j;
                workHandler.sendMessage(msg);
                j++;
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //step5: 停止handlerThread
        handlerThread.quit(); //结束线程,效率高
        handlerThread.quitSafely(); //结束线程
    }
}
  • 效果展示


    屏幕录制.gif

Log日志


日志截图.png
  • 结论
    从demo展示中和打印的Log中看到,第一次点击Button窗口1开始卖票,窗口1卖第3张票时,再次点击Button,HandlerThread不会停止当前窗口卖票,而是等待当前卖完6张票之后后再开启窗口2执行卖票任务。

5.源码分析

稍后补充,嘻嘻嘻。

6.应用场景

使用HandlerThread处理本地的 I/O操作(数据库,文件,SharePreferences),推荐使用postAtFrontOfQueue(),快速将读取操作加入队列的前端执行,必要时更新主线程UI。示例场景,从数据库读取数据显示在ListView中。

总结

  • HandlerThread 继承自 Thread,在复写的 run() 方法中,调用了Looper.prepare()和Looper.loop(),方便在创建的子线程中使用Handler时需要自己手动调用Looper.prepare()和Looper.loop()。
  • HandlerThread 内部处理消息队列时是按顺序 串行执行 ,即在处理完一条消息任务后再处理下一条消息任务,不适合处理 网络请求 需要并行处理的耗时任务,更适合处理本地的需要按序执行的 I/O操作
  • 缺点:若其中一条任务处理时间过长,仍然需要等待此条任务处理完成之后才处理下一条任务,会导致后续任务延时执行,造成 线程阻塞 的现象。
  • 一个线程对应一个Looper
  • Looper.prepare() 自动创建一个Looper对象,Looper创建时,自动创建MessageQueue对象,并赋值mThread为当前Thread,ThreadLocal<Looper>中存入创建的Looper对象
  • Looper.loop() 开启消息循环

思考

  • 什么是线程阻塞,造成原因有哪些?
  • HandlerThread需要手动调用quit()/quitSafely(),为什么新建的子线程不用手动调用释放Looper和MessageQueue呢?

参考

大神Carson_ho关于HandlerThread的系列教程

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