Android中的消息机制

作者: answer_05 | 来源:发表于2016-10-08 16:59 被阅读0次

    提到Android中的消息机制,首先想到的必定是Handler,Looper和MesageQueue,我们在开发中接触到的最多的就是Handler,至于Looper和MessageQueue我们是知道的,但是并不会对其进行操作,最多的用法就是在子线程请求数据,然后在UI线程创建一个Handler,用创建的Handler在子线程发送消息,然后在UI线程的接受消息并更新UI。然而我们还是会碰到一些问题的,比如为什么不能再子线程创建Handler(其实是可以的),以及为什么多线程能够解决UI线程的阻塞问题。
    Android应用在启动时,默认会有一个主线程(UI线程),在这个UI线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。一个完整的消息队列必须包含Handler ,Looper,MessageQueue,Handler是需要我们创建的,而UI线程的线程循环Looper是在ActivityThread.main()方法中创建的,贴出部分源码如下:

    public static void main(String[] args) {
       //省略代码
          Looper.prepareMainLooper(); //1.创建消息队列
    
          ActivityThread thread = new ActivityThread();
          thread.attach(false);
    
          if (sMainThreadHandler == null) {
              sMainThreadHandler = thread.getHandler();//创建UI线程中的Handler
          }
    
          if (false) {
              Looper.myLooper().setMessageLogging(new
                      LogPrinter(Log.DEBUG, "ActivityThread"));
          }
    
          // End of event ActivityThreadMain.
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          Looper.loop(); //2.执行消息循环
    
          throw new RuntimeException("Main thread loop unexpectedly exited");
      }
    

    执行过ActivityThread.main()方法后应用程序就启动了,Looper会一直在消息队列中拉取消息,然后处理消息,使得整个系统运行起来。那么消息又是如何产生的,又是如何从队列中获取消息并且处理消息的?答案就是Handler。例如在子线程发出消息到UI线程,并且在UI线程处理消息,其中更新UI的Handler是属于UI线程。然而更新UI的Handler为什么必须属于UI线程,我们打开Handler的相关的源代码看一下:

    public Handler(Callback callback, boolean async) {
          if (FIND_POTENTIAL_LEAKS) {
              final Class<? extends Handler> klass = getClass();
              if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                      (klass.getModifiers() & Modifier.STATIC) == 0) {
                  Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                      klass.getCanonicalName());
              }
          }
    //1.获取到Looper
          mLooper = Looper.myLooper(); 
          if (mLooper == null) {
              throw new RuntimeException(
                  "Can't create handler inside thread that has not called Looper.prepare()");
          }
    //2.获取到消息队列
          mQueue = mLooper.mQueue; 
          mCallback = callback;
          mAsynchronous = async;
      }
    

    从Handler的构造函数可以看到,通过Looper.myLooper()得到Looper,然后我们看一下myLooper这个方法:

    /** * Return the Looper object associated with the current thread.  
    Returns   null if the calling thread is not associated with a Looper. */
    public static @Nullable Looper myLooper() {    
    return sThreadLocal.get();
    }
    

    可以看到这里返回了一个和当前线程关联的Looper,而这个Looper又是在什么地方和sThreadLocal关联起来的呢?

        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
    

    可以看到在我们上面提到的prepareMainLooper() 方法中,调用了prepare()方法,在prepare()方法中创建了一个Looper并且和sThreadLocal关联起来( sThreadLocal.set(new Looper(quitAllowed));),通过这一个步骤消息就和线程关联了起来,不同线程之间就不能彼此访问对方的消息队列了。消息队列通过Looper和线程关联起来,而Handler又和Looper关联起来,而UI线程中的Looper是在ActivityThread.main()方法中创建的,当我们单独开启一个线程的时候,由于并没有创建Looper,所以在创建Handler的时候会抛出“Can't create handler inside thread that has not called Looper.prepare()”的异常,知道了这个异常的原因,我们就可以解决了这个异常了。

      new Thread(){
                Handler mHandler = null;
    
                @Override
                public void run() {
                    super.run();
                    //1.为当前线程传建一个Looper,并且和线程关联起来
                    Looper.prepare();
                    mHandler = new Handler();
                    //2.进行消息的循环
                    Looper.loop();
                }
            }.start();
    

    总结一下,每个Handler都会关联一个消息队列,每个消息队列都会被封装进Looper中,每个Looper都会关联一个线程,最终就相当于每个消息队列都会关联一个线程。Handler就是一个消息的处理器,将消息投递给消息队列,然后从消息队列中取出消息,并进行处理。默认情况下,只有UI线程中有一个Looper,是在ActivityThread.main()方法中创建的。这也就解释了为什么更新UI的Handler为什么要在UI线程创建,就是因为Handler要和UI线程的消息队列关联起来,这样handleMessage()方法才会在UI线程执行,此时更新UI才是线程安全的。至于Handler是在哪一步关联的Looper的,我们可以打开Handler的构造方法:

     /**
         * Default constructor associates this handler with the {@link Looper} for the
         * current thread.
         *
         * If this thread does not have a looper, this handler won't be able to receive messages
         * so an exception is thrown.
         */
         //默认的构造器,和当前的线程关联起来,若果当前线程没有Looper,则会出现异常
        public Handler() {
            this(null, false);
        }
    

    那我们能不能再分线程创建Handler,然后发送消息到主线程呢?继续看Handler的其他构造函数:

    
        /**
         * Use the provided {@link Looper} instead of the default one.
         *
         * @param looper The looper, must not be null.
         */
        public Handler(Looper looper) {
            this(looper, null, false);
        }
    

    我们可以传入一个非当前线程的Looper,来向传入的Looper所在的线程的消息队列发送消息:

    public class MainActivity extends AppCompatActivity {
    
        private static final  String TAG = MainActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            testForMianToMain();
        }
    
        
        private void testForMianToMain() {
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    //这里不是在主线程创建的Handler,但是我传入的是主线程的Looper,既然是主线程的Looper,那么消息队列就一定是主线程的
                    MainHandler mainHandler = new MainHandler(getMainLooper());
                    Message mainMessage = new Message();
                    mainMessage.obj="Main To Main";
                    mainHandler.sendMessage(mainMessage);
                }
            }.start();
        }
    
        class MainHandler extends Handler{
            public MainHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.e(TAG,msg.obj.toString());
            }
        }
    }
    
    

    补充:为什么只能在主线程更新UI?先看一下如果不在主线程更新UI会发生什么错误,会报出如下错误:"Only the original thread that created a view hierarchy can touch its views.",这个错误是在哪里定义的呢?打开源码,可以看到是在ViewRootImpl.java这个类中定义的,ViewRootImpl类是View内部类AttachInfo的一个成员变量,是连接Window和View的桥梁,View在更新页面的时候会调用 checkThread()这个方法检查是否是在主线程,若不在主线程(因为View更新只能在主线程),则会报出"Only the original thread that created a view hierarchy can touch its views."这个错误。

    相关文章

      网友评论

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

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