Android 中的消息机制

作者: 白帽子耗子 | 来源:发表于2017-05-16 10:13 被阅读65次

    前言

    涉及知识点:

    • 消息机制: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!

    执行结果

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


    相关文章

      网友评论

        本文标题: Android 中的消息机制

        本文链接:https://www.haomeiwen.com/subject/kjzkxxtx.html