Android 中的消息机制

前言

涉及知识点:

  • 消息机制:Handler、Looper 和 MessageQueue
  • AsyncTask 基本使用
  • 实现一个简单的 SimpleAsyncTask

消息机制

Android 中的消息机制由三大部分组成:Handler、Looper 和 MessageQueue.

Looper 就是创建一个 MessageQueue,然后进入一个死循环里面不断地读取 MessageQueue 里面的消息,Handler 就是消息的创建者,处理者。

消息机制

由图我们可以看出,消息队列被封装在了 MessageQueue 中,通过 Looper 和线程 Thread 关联起来。而 Handler 又通过 Looper 关联,因而 Handler 最终和线程、线程的消息队列关联上来了。这也就是为什么我们常说更新 UI 的 Handler 必须要在主线程中创建,因为只有在主线程中创建,Handler 才能和主线程的消息队列关联上,这样 handleMessage 才会执行在 UI 线程,这时候更新 UI 才是线程安全的。

题外话:为什么常说只能在 UI 线程更新 UI ?

子线程可以有好多个,但如果每个子线程都直接对UI元素进行操作,界面会混乱不堪,线程会面临安全问题,虽然可以通过加锁机制来解决线程的安全问题,但是加锁会降低运行效率, 所以主线程(UI线程)并没进行加锁限制多线程访问, 可能这就是“出于性能优化考虑”。

既然没有对多线程访问进行限制,而且子线程依然有进行UI操作的需求,那么该如何解决呢?

所以Android规定只能在主线程中进行UI元素的更改,你们一帮菜鸡子线程如果还执意要来修改我管辖的用户界面 就必须先通知我(主线程),我来帮你们完成 :)

——知乎用户:大大大大头啊

创建自己的消息队列

我们知道了基本的消息机制。但是,要注意的是,Android 中除了 UI 线程,创建的工作线程默认是没有消息循环和消息队列的。

在非主线程直接 new Handler(); 会报错

Uncaught handler: thread Thread-8 exiting due to uncaught exception

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

所以,如果想让自己创建的工作线程 有消息循环和消息队列,并具有消息处理机制,就需要在线程中先调用 Looper.prepare() 来创建消息队列,然后调用 Loop.loop() 进入消息循环

public class MyloopThread extends Thread{
  Handler mHandler;
  
  public void run(){
    Loop.prepare();
    
    mHandler = new Handler(){
      public void handleMessage(Message msg){
        // process incoming message here
      }
    };
    
    Loop.loop();
  }
}

AsyncTask 基本使用

我们往往使用 Thread 创建子线程进行耗时操作,但是由于不能在子线程更新 UI,一般就会使用 Handler 发送消息给 UI 线程然后再更新。这个操作起来有点麻烦,在多个任务同时执行的时候,不易于对线程进行精细控制。于是 AsyncTask 应运而生。

public abstract class AsyncTask< Param, Progress, Result > ( ) 
//三个泛型类型: < 参数类型,后台执行任务的进度类型,返回的结果类型 > 如果不需要某个类型可以设置为 void

一个异步任务一般包含以下步骤:

AsyncTask

实现一个简单的 AsyncTask

下面我们来实现一个简单的 AsyncTask,类名为 SimpleAsyncTask。与 AsyncTask 类似,提供了三个函数:onPreExecute( )、 doInBackground( )、onPostExecute( )。泛型参数为了方便只有一个 doInBackgroud( ) 函数返回值类型的泛型参数。SimpleAsyncTask 执行起来和 AsyncTask 基本一样。首先是 onPreExecute 函数在任务运行之前执行,而且运行在 UI 线程之中。doInBackgroud 运行在后台执行耗时操作,并且将结果返回。onPostExecute 含有一个参数,这个参数就是 doInBaskgroud 的返回结果,onPostExecute 执行在 UI 线程。


public abstract  class SimpleAsyncTask<Result> {

    private static final HandlerThread HT = new HandlerThread("SimpleAsyncTask",
            Process.THREAD_PRIORITY_BACKGROUND);
    static {
        HT.start();
    }

    final Handler mUIHandler = new Handler(Looper.getMainLooper());
    final Handler mAsyncHandler = new Handler(HT.getLooper());

    /**
     * @功能描述:onPreExecute 任务执行之前的初始化操作等
     */
    protected void onPreExecute(){}

    /**
     * 后台执行任务
     * @return 返回执行结果
     */
    protected abstract Result doInBackground();

    /**
     * 返回结果传递给执行在 UI 线程的 onPostExecute
     * @param result 执行结果
     */
    protected void onPostExecuted(Result result){ }

    public final SimpleAsyncTask<Result> excute () {
        onPreExecute();

        mAsyncHandler.post(new Runnable() {
            @Override
            public void run() {
                postResult(doInBackground());
            }

        });
        return this;
    }

    private void postResult(final Result result){
        mUIHandler.post(new Runnable() {
            @Override
            public void run() {
                onPostExecuted(result);
            }
        });
    }

}

在 SimpleAsyncTask 里面首先创建了一个 HandlerThread(自带消息队列的 Thread),当线程启动之后就会构建它的消息队列,所以构建完成后,直接在静态代码块里面启动了该线程。然后创建了两个 Handler,分别关联 UI 线程和 HandlerThread 的子线程 mAsyncHandler。剩下三个函数已经解释过了,有需要的时候我们可以重写这三个方法。

execute 是执行的函数,里面先调用 onPreExecute,然后 doInBackground 函数被一个 Runnbale 包装通过 mAsyncTask 提交给了 HandlerThread 线程执行,当得到结果的时候又通过 mUIHandler 将结果提交到一个 Runnable 里面,这个 Runnbale 中执行了 onPostExecute。

下面是调用的示例代码:

new SimpleAsyncTask<String>() {
    private void makeToast(String msg){
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onPreExecute() {
        makeToast("onPreExecute");
    }

    @Override
    protected String doInBackground() {
        try{
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "doInBackground finish!";
    }

    @Override
    protected void onPostExecuted(String s) {
        makeToast("onPostExecuted"+s);
    }
}.excute();

执行结果就是先 Toast:onPreExecute,延时 6 秒之后 Toast: onPostExecuted doInBackground finish!

执行结果

喜欢就点个赞,有问题就留个言,你不说话我怎么知道你来过


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

推荐阅读更多精彩内容