美文网首页
Android 开发艺术探索笔记(十四) 之 Android 消

Android 开发艺术探索笔记(十四) 之 Android 消

作者: innovatorCL | 来源:发表于2018-04-08 23:41 被阅读32次

    一、Android 消息机制概述

    Android 消息机制是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueue (消息队列)Looper(消息循环) 的支撑。Handler 主要是将一个任务切换到某个指定的线程中去执行。

    二、Handler 的使用栗子

    public class MainActivity extends Activity implements Button.OnClickListener {
    
        private TextView statusTextView;
    
        //uiHandler在主线程中创建,所以自动绑定主线程的 Looper
        private Handler uiHandler = new Handler();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            statusTextView = (TextView)findViewById(R.id.statusTextView);
            Button btnDownload = (Button)findViewById(R.id.btnDownload);
            btnDownload.setOnClickListener(this);
            System.out.println("Main thread id " + Thread.currentThread().getId());
        }
    
        @Override
        public void onClick(View v) {
            DownloadThread downloadThread = new DownloadThread();
            downloadThread.start();
        }
    
        class DownloadThread extends Thread{
            @Override
            public void run() {
                try{
                    System.out.println("DownloadThread id " + Thread.currentThread().getId());
                    System.out.println("开始下载文件");
                    //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                    Thread.sleep(5000);
                    System.out.println("文件下载完成");
                    //文件下载完成后更新UI
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("Runnable thread id " + Thread.currentThread().getId());
                            MainActivity.this.statusTextView.setText("文件下载完成");
                        }
                    };
                    uiHandler.post(runnable);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    Handler 是在创建的时候绑定实例化所在的线程的 Looper,所以当 Handler 发送消息的时候,所绑定的 Looper 就会在绑定的线程中取消息,从而将任务切换到制定的线程中执行。

    三、Handler 机制分析

    • 1. ThreadLocal:一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定的线程才能获取存储的数据,对于其他线程来说则无法获取到数据。

    举个栗子:

    public class MainActivity extends AppCompatActivity {
    
        private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mBooleanThreadLocal.set(true);
            Log.i("TAG","[MainThread] 的值:"+mBooleanThreadLocal.get());
    
            new Thread("Thread#1"){
                @Override
                public void run() {
                    mBooleanThreadLocal.set(false);
                    Log.i("TAG","[Thread#1] 的值:"+mBooleanThreadLocal.get());
                }
            }.start();
    
    
            new Thread("Thread#2"){
                @Override
                public void run() {
                    Log.i("TAG","[Thread#2] 的值:"+mBooleanThreadLocal.get());
                }
            }.start();
        }
    }
    

    打印结果:

    04-06 08:30:01.403 2824-2824/com.innovator.handlertest I/TAG: [MainThread] 的值:true
    04-06 08:30:01.408 2824-2849/com.innovator.handlertest I/TAG: [Thread#1] 的值:false
    04-06 08:30:01.411 2824-2850/com.innovator.handlertest I/TAG: [Thread#2] 的值:null

    可以看到在哪个线程设置了值后,在对应线程取出的数据不受其他线程的影响。原因就是:不同线程访问同一个 ThreadLocal 对象的 get(),ThreadLocal 内部会从各自的线程中取出一个数组,然后在从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。显然不同线程的数组是不同的。

    • 2. MessageQueue 的工作原理

    Android 中的消息队列是指 MessageQueue,它主要包含两个操作:enqueueMessage(插入消息)next(读取删除消息)。原理主要是单链表的插入和删除操作。

    • 3. Looper 工作原理

      Looper 在 Android 消息机制中扮演着消息循环的角色,具体来说就是它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。

      我们都知道 Handler 工作需要 Looper,没有 Looper 的线程就会报错。创建 Looper 的方法如下:

      new Thread("Thread#3"){
              @Override
              public void run() {
                  // 在子线程中创建 Looper
                  Looper.prepare();
                  Handler handler = new Handler();
                  Runnable runnable = new Runnable() {
                      @Override
                      public void run() {
                          Log.i("TAG","Runnable 运行在:"+Thread.currentThread().getName());
                      }
                  };
      
                  handler.post(runnable);
                  Looper.loop();
                  Looper.myLooper().quit();
              }
          }.start();
      

    打印结果:

    Runnable 运行在:Thread#3

    可见 Runnable 是运行在 Handler 实例化的线程,Looper.loop(); 是一个死循环操作,一直阻塞读取消息,所以如果在子线程中使用 Looper,记得要在处理完所有事情后调用 quit() 终止消息循环,否则子线程就一直处于等待的状态。

    • 4. Handler 工作原理

      Handler 的工作包括发送和接收两个过程,消息的发送可以通过 post() 或者 sendMessage() 来实现,这个过程其实是将一个消息插入队列,然后 MessageQueue 的 next() 就会返回这条消息给 Looper,Looper 收到消息后就开始处理,最终消息由 Looper 交由 Handler 处理,即 Handler 的 dispatchMessage() 被调用。

    Handler 的 diapatchMessage()

    public void dispatchMessage(Message msg){
        if(msg.callback != null){
            handleCallback(msg);
        }else{
            if(mCallback != null){
                if(mCallback.handleMessage(msg)){
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    

    首先检查 Message 的 callback 是否为 null,不为 null 就通过 handleCallback(msg); 来处理消息。 Message 的 callback 是一个 Runnable 对象,就是 Handler 的 post() 的参数,其实就是执行这里面的 run() 方法的内容。

    其次就是检查 mCallBack 是否为空,不为空就调用 mCallBackhandleMessage()

    所以在实例化 Handler 的时候有两种构造方法:

    • 使用 Callback 创建 Handler 实例,但不派生 Handler 的子类
    mMainHandler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message message) {
                    return false;
                }
            });
    
    • 派生 Handler 的子类
    mMainHandler = new Handler() {
    
                @Override
                public void handleMessage(Message msg) {
                    
                }
    
            };
    

    四、Handler、Looper、MessageQueue的关系

    Looper 负责的就是创建一个 MessageQueue,然后进入一个无限循环体。在这个循环体中不断从该 MessageQueue 中读取消息,而消息的创建者就是一个或多个 Handler 。

    Looper 的 prepare():

    public static final void prepare() {  
            if (sThreadLocal.get() != null) {  
                throw new RuntimeException("Only one Looper may be created per thread");  
            }  
            // 保证了一个线程只有一个 Looper,一个 MessageQueue
            sThreadLocal.set(new Looper(true));  
    } 
    

    这里可以看到 Looper 里面有一个 ThreadLocal 的成员变量,所以 Looper 能保证每个线程存放的 Looper 对象都不一样。

    Looper 的构造方法:

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

    可以看到 Looper 实例化了一个 MessageQueue 对象并保存了当前的线程对象。到这里我们就更能确定 Looper 只与绑定的线程有关,这也是为了后面 Handler 能将任务切换到指定的线程做铺垫。

    具体的流程:

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

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

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

    4、Handler 的sendMessage() 方法,会给 msg 的 target 赋值为
    handler 自身,然后加入 MessageQueue 中。

    5、在构造 Handler 实例时,我们会重写 handleMessage() 方法,也就是 msg.target.dispatchMessage(msg) 最终调用的方法。

    看完这么多分析后,我可以推断我之前想到的一个问题了:一个线程能拥有多个 Handler 吗?这些 Handler 会互相干扰吗?

    答案:一个线程可以拥有多个 Handler,并且这些 Handler 都是共用同一个线程的 Looper 和 MessageQueue,而且不会互相干扰,因为 Looper 取消息后的回调是 dispatchMessage(),这里的 msg.target 是之前发送消息的那个 Handler。

    五、参考资料

    相关文章

      网友评论

          本文标题:Android 开发艺术探索笔记(十四) 之 Android 消

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