zoukankan      html  css  js  c++  java
  • 消息传递机制、线程机制(Handler、Looper、HandlerThread、Message、Message Queue)

    参考来自:

    简单分析:http://blog.csdn.net/kesalin/article/details/37722659

    深入jni分析:http://blog.csdn.net/luoshengyang/article/details/6817933 

    了解了Looper实现的消息循环的源码,我发现其中就涉及到了管道(进程间通信的方式、通过epoll机制实现其通信)

    Looper.prepare()->

    Looper构造器->

    构建MessageQueue对象->

    nativeInit方法->

    frameworks/base/core/jni/android_os_MessageQueue.cpp的android_os_MessageQueue_nativeInit方法->

    ...

    frameworks/base/libs/utils/Looper.cpp的Looper构造器方法->

    int wakeFds[2];  
    int result = pipe(wakeFds);  
    ......  
      
    mWakeReadPipeFd = wakeFds[0];  
    mWakeWritePipeFd = wakeFds[1];

    管道是Linux系统中的一种进程间通信机制,简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。 Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个,即前面我们所创建的管道的读端,为什么还需要用到epoll呢?有点用牛刀来杀鸡的味道。其实不然,这个Looper类是非常强大的,它除了监控内部所创建的管道接口之外,还提供了addFd接口供外界面调用,外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去,当所有这些被监控的IO接口上面有事件发生时,就会唤醒相应的线程来处理,不过这里我们只关心刚才所创建的管道的IO事件的发生。

    Android的线程涉及到以下几个概念,Handler、Looper、HandlerThread、Message、Message Queue

    一个线程只有一个消息队列(通过Looper实现消息循环),但可以有多个Handler,多个Message

    默认来说只有UI线程的Looper才是开启的

    子线程(通过Thread构造)没有Looper,

    子线程(通过HandlerThread构造)有Looper并开启了,但是不能进行UI操作

    由于Android的UI操作不是线程安全的,这意味着当多个线程并发操作UI组件时,可能导致线程安全问题。因此规定只有UI线程允许更改Activity的UI组件。

    UI 操作需要向 UI 线程发送消息并在其 Looper 中处理这些消息。这就是为什么我们不能在非 UI 线程中更新 UI 的原因,在控件在非 UI 线程中构造 Handler 时,

    要么由于非 UI 线程没有 Looper,(Thread类没有Looper) 使得获取 myLooper 失败而抛出 RunTimeException,

    要么即便提供了 Looper(使用HandlerThread而非Thread),但这个 Looper 并非 UI 线程的 Looper 而不能处理控件消息。

    为此在 ViewRootImpl 中有一个强制检测 UI 操作是否是在 UI 线程中处理的方法 checkThread():该方法中的 mThread 是在 ViewRootImpl 的构造函数中赋值的,它就是 UI 线程;该方法中的 Thread.currentThread() 是当前进行 UI 操作的线程,如果这个线程不是非 UI 线程就会抛出异常CalledFromWrongThreadException。

    [java] view plaincopyprint?
    
     void checkThread() {  
    
         if (mThread != Thread.currentThread()) {  
    
             throw new CalledFromWrongThreadException(  
    
                     "Only the original thread that created a view hierarchy can touch its views.");  
    
         }  
    
     }

    Handler

    当我们用Handler的构造方法创建Handler对象时,指定handler对象与哪个具有消息处理机制的线程(具有Looper的线程)相关联,这个线程就成了目标线程,可以接受消息和计划任务了。默认的Handler handler = new Handler (){重写handlerMessage方法},这样子的无参构造器,默认handler就是主线程的Handler,即这个handler与主线程的默认Looper绑定。

    当我们用Handler的构造方法创建Handler对象时,指定handler对象与哪个具有消息处理机制的线程(具有Looper的线程)相关联,这个线程就成了目标线程,可以接受消息和计划任务了。Handler中的构造方法如下:

    public Handler(Looper looper) {  
    
            mLooper = looper;  
    
          mQueue = looper.mQueue;  
    
          mCallback = null;  
    
       } 

     

    Handler 的构造函数暂且介绍到这里,接下来介绍:handleMessage 和 dispatchMessage:

    /** 
    
          * Subclasses must implement this to receive messages. 
    
          */  
    
         public void handleMessage(Message msg) {  
    
         }  
    
         /** 
    
          * Handle system messages here. 
    
          */  
    
         public void dispatchMessage(Message msg) {  
    
             if (msg.callback != null) {  
    
                 handleCallback(msg);  
    
             } else {  
    
                 if (mCallback != null) {  
    
                     if (mCallback.handleMessage(msg)) {  
    
                         return;  
    
                     }  
    
                 }  
    
                handleMessage(msg);  
    
             }  
    
         }  


    前面提到有两种方式来设置处理消息的代码:一种是设置 Callback 回调,一种是子类化 Handler。而子类化 Handler 其子类就要实现 handleMessage 来处理自定义的消息,如前面的匿名子类示例一样。dispatchMessage 是在 Looper::Loop() 中被调用,即它是在线程的消息处理循环中被调用,这样就能让 Handler 不断地处理各种消息。在 dispatchMessage 的实现中可以看到,如果 Message 有自己的消息处理回调,那么就优先调用消息自己的消息处理回调:

    private static void handleCallback(Message message) {  
    
         message.callback.run();  
    
     }  


    否则看Handler 是否有消息处理回调 mCallback,如果有且 mCallback 成功处理了这个消息就返回了,否则就调用 handleMessage(通常是子类的实现) 来处理消息。

    Handler在多线程中有两个应用:

    1. 发送消息(sendMessage())(要在handler中重写handleMessage())

      

    Message message = Message.obtain(); 
        message.obj = data; 
        message.what = DOWNLOAD_IMG; 
        handler.sendMessage(message);

      或者:

    handler.obtainMessage(int what, Object obj).sendToTarget();

    2. 计算任务(post(Runnable runable))例如在未来处理某时间,延迟一段时间

     handler=new Handler();    
    
             Runnable r=new Runnable(){    
    
                 @Override    
    
                 public void run() {    
    
                     // TODO Auto-generated method stub    
    
                     if(isRunning){    
    
                         textView.setText("走了"+timer+"秒");    
    
                         timer++;    
    
                         handler.postDelayed(this, 1000);//提交任务r,延时1秒执行    
    
                     }    
    
                 }    
    
             };    
    
             handler.postDelayed(r, 1000);  

    实际上:消息发送和计划任务提交之后,它们都会进入某线程的消息队列中,我们可以把这个线程称之为目标线程。不论是主线程还是子线程都可以成为目标线程。上例中之所以在主线程中处理消息,是因为我们要更新UI,按照android中的规定我们必须由主线程更新UI。所以我们让主线程成为了目标线程。

    那么如何控制让某个线程成为目标线程呢?

    Looper

    这就引出了Looper的概念。Android系统中实现了消息循环机制,Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。Android系统中的通过Looper帮助线程维护着一个消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。

    Looper是用于实现消息队列和消息循环机制的。

    因此,如果是默认创建Handler那么如果线程是做一些耗时操作如网络获取数据等操作,这样创建Handler是不行的。

    每个线程都可以有自己的消息队列和消息循环,然而我们自己创建的线程默认是没有消息队列和消息循环的(及Looper),要想让一个线程具有消息处理机制我们应该在线程中先调用Looper.prepare()来创建一个Looper对象,然后调用Looper.loop()进入消息循环。

     public class Looper {  
    
         private static final boolean DEBUG = false;  
    
         private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;  
    
       
    
        // sThreadLocal.get() will return null unless you've called prepare().  
    
         private static final ThreadLocal sThreadLocal = new ThreadLocal();  
    
       
    
         final MessageQueue mQueue;  
    
         volatile boolean mRun;  
    
         Thread mThread;  
    
         private Printer mLogging = null;  
    
         private static Looper mMainLooper = null;  
    
           
    
          /** Initialize the current thread as a looper. 
    
           * This gives you a chance to create handlers that then reference 
    
           * this looper, before actually starting the loop. Be sure to call 
    
           * {@link #loop()} after calling this method, and end it by calling 
    
           * {@link #quit()}. 
    
           */  
    
         public static final void prepare() {  
    
             if (sThreadLocal.get() != null) {  
    
                 throw new RuntimeException("Only one Looper may be created per thread");  
    
             }  
    
             sThreadLocal.set(new Looper());  
    
         }  
    
     public static final void loop() {  
    
             Looper me = myLooper();  
    
             MessageQueue queue = me.mQueue;  
    
             while (true) {  
    
                 Message msg = queue.next(); // might block  
    
                 //if (!me.mRun) {  
    
                 //    break;  
    
                 //}  
    
                 if (msg != null) {  
    
                     if (msg.target == null) {  
    
                         // No target is a magic identifier for the quit message.  
    
                         return;  
    
                     }  
    
                     if (me.mLogging!= null) me.mLogging.println(  
    
                             ">>>>> Dispatching to " + msg.target + " "  
    
                             + msg.callback + ": " + msg.what  
    
                             );  
    
                     msg.target.dispatchMessage(msg);  
    
                     if (me.mLogging!= null) me.mLogging.println(  
    
                             "<<<<< Finished to    " + msg.target + " "  
    
                             + msg.callback);  
    
                     msg.recycle();  
    
                 }  
    
             }  
    
         }


    HandlerThread

    HandlerThread实际上就一个Thread,只不过它比普通的Thread多了一个Looper。并且在源码run方法里面执行了Looper.prepare()和Looper.looper()

    使用场景:子线程HandlerThread进行耗时操作(比如每隔1秒进行网络请求,然后再UI界面更新)

      private void initBackThread(){
            // 1.创建HandlerThread
            mHandlerThread = new HandlerThread("check-message-coming");
            // 2.启动HandlerThread线程
            mHandlerThread.start();
            // 3.创建Handler对象绑定该线程的Looper
            mCheckMsgHandler = new Handler(mHandlerThread.getLooper()){
           //那么这个Handler对象就是与HandlerThread这个线程绑定了(这时就不再是与UI线程绑定了,这样它处理耗时操作将不会阻塞UI) @Override
    public void handleMessage(Message msg){//hanleMessage里面的操作是在创建的子线程进行的 //模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 主线程更新数据 runOnUiThread(new Runnable() { @Override public void run() { String result = "每隔1秒更新一下数据:"+Math.random(); mInfoText.setText(result); } }); if(isUpdate){ //1s后再次发送消息进行耗时操作 mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000); } } }; }

      开启:

         mInfoText = (TextView) findViewById(R.id.tv_info_count);
            findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //开始更新
                    isUpdate = true;
                    mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);
                }
            });
    
            findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //停止更新
                    isUpdate = false;
                    mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);
                }
            });

      释放资源:

      

    @Override
        protected void onPause() {
            super.onPause();
            isUpdate = false;
            mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);
        }
    
        @Override
        protected void onDestroy(){
            super.onDestroy();
            //释放资源
            mHandlerThread.quit();
        }



  • 相关阅读:
    ES6小点心第二弹——底部浮现弹窗
    ES6小点心之通用弹窗
    从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!
    【quickhybrid】如何实现一个Hybrid框架
    【开源】canvas图像裁剪、压缩、旋转
    优雅的H5下拉刷新【minirefresh】
    前端筑基篇(一)->ajax跨域原理以及解决方案
    钉钉开放与商业化团队前端大量招人
    AJAX请求真的不安全么?谈谈Web安全与AJAX的关系。
    【quickhybrid】iOS端的项目实现
  • 原文地址:https://www.cnblogs.com/could-deng/p/5028450.html
Copyright © 2011-2022 走看看