Android消息机制

Handler机制的作用是用在不同线程之间的通信,通常是在子线程完成耗时操作,通过handler将有关的ui操作切换到主线程。

本质上是一个线程开启无限循环并持续监听其他线程给他发的消息,如果没有消息自身就堵塞(相当于wait)

looper是要被调用的,android-main方法的调用:https://www.jianshu.com/p/302fe75d6778(未看)

大致的流程

img

先来大致梳理下整个流程:

  1. 应用程序启动的时候,在主线程中会默认调用了 Looper.prepare()方法,初始化Looper对象并绑定到当前线程中,并在Looper内部维护一个MessageQueue

  2. 接着调用handler.sendMessage()发送消息,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息

  3. 主线程调用Looper.looper()开启循环,不断轮询消息队列,通过MessageQueue.next()取出消息

  4. 取出的message不为空则调用msg.target.dispatchMessage()传递分发消息,目标handler收到消息后会执行handler.handlerMessage()方法处理消息

Looper:

//构造函数:不允许别人创建 同时创建了一个MessageQueue对象
private Looper(boolean quitAllowed) {
       mQueue new MessageQueue(quitAllowed);
       mThread Thread.currentThread();
  }

 

  1. ActivityThread.main()方法中系统已经帮我们创建好Looper对象,而子线程的looper是我们自己创建的。

主线程中的Looper的prepare与子线程不一样:

从prepare()可以看出而调用prepare的线程如果是第二次构建looper对象会抛出异常

//Looper类中拥有一个ThreadLocal<Looper>池
//ThreadLocal中装着所有线程的Looper对象
 // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<LoopersThreadLocal new ThreadLocal<Looper>();

//Looper类中拥有主线程的looper对象!
private static Looper sMainLooper;

//所有子线程要构建looper之前需要调用
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));
  }

   /**
    * Initialize the current thread as a looper, marking it as an
    * application's main looper. See also: {@link #prepare()}
    *
    * @deprecated The main looper for your application is created by the Android environment,
    *   so you should never need to call this function yourself.
    */
   @Deprecated
   public static void prepareMainLooper() {
       prepare(false);
       synchronized (Looper.class) {
           if (sMainLooper != null) {
               throw new IllegalStateException("The main Looper has already been prepared.");
          }
           sMainLooper myLooper();
      }
  }
//现在的版本
public static void prepare() {
       prepare(true);
    }
//主线程中不需要自己创建Looper
public static void main(String[] args) {
 ......
 Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
 ......
 Looper.loop();//开启消息轮询
 ......
}

//子线程
public class LooperThread extends Thread {
   @Override
   public void run() {
       // 将当前线程初始化为Looper线程
       Looper.prepare();
  // ...其他处理,如实例化handler
   
   // 开始循环处理消息队列
   Looper.loop();
}

Loop()函数:

   @SuppressWarnings("AndroidFrameworkBinderIdentity")
   public static void loop() {
     
     //这里面根据当前线程从ThreadLocal中返回looper对象
       final Looper me myLooper();
       if (me == null) {
           throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
      }
       if (me.mInLoop) {
           Slog.w(TAG, "Loop again would have the queued messages be executed"
                   " before this one completed.");
      }

       me.mInLoop true;

       // Make sure the identity of this thread is that of the local process,
       // and keep track of what that identity token actually is.
       Binder.clearCallingIdentity();
       final long ident Binder.clearCallingIdentity();

       // Allow overriding a threshold with a system prop. e.g.
       // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
       final int thresholdOverride =
               SystemProperties.getInt("log.looper."
                       Process.myUid() "."
                       Thread.currentThread().getName()
                       ".slow", 0);

       me.mSlowDeliveryDetected false;

     //可以看出 每次循环都loopOnce获取消息队列中的对象
       for (;;) {
           if (!loopOnce(me, ident, thresholdOverride)) {
               return;
          }
      }
  }

img

LoopOnce函数:挑重点

@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
       final long ident, final int thresholdOverride) {
   Message msg me.mQueue.next(); // might block 具体看messagequeuezhong
   if (msg == null) {
       // No message indicates that the message queue is quitting.
       return false; //如果执行到这一步将会退出,主线程结束
  }

   -------
   -------
  //分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,
  //这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写
  //的handler的handleMessage方法。
  msg.target.dispatchMessage(msg);
 
 /*
   //Handle system messages here
   public void dispatchMessage(Message msg) {
       //优先调用callback方法
       if (msg.callback != null) {
           handleCallback(msg);
       } else {
           if (mCallback != null) {
               if (mCallback.handleMessage(msg)) {
                   return;
               }
           }
           //最后会回调我们重写的handleMessage 方法
           handleMessage(msg);
       }
   }
   
    private static void handleCallback(Message message) {
       message.callback.run();
   }
 */
     
   msg.recycleUnchecked();

   return true;
}

Loop循环不会卡死的原因:

主线程的任何代码都是被looper从队列中取出来的执行的,也就是说主线程的是接受所有其他线程给他发送消息来执行动作的,生命周期的回调也是通过系统服务系统服务ActivityManagerService通过Binder发送IPC调用给APP进程App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。而Binder线程是在主线程进入无限循环的时候创建的,而当调用messageQueue方法的next如果没有消息的时候,主线程会释放当前cpu资源并且进入相当于wait状态(之后版本实现方法不一样)


 

其中LooperThreadLocal息息相关:不妨先看一下ThreadLocal<T>

ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。

不同的线程执行相对应的函数的时候都会调用Looper类中的sThreadLoacl.get获得对应的线程实例

public get() {
   Thread Thread.currentThread();
   ThreadLocalMap map getMap(t);
   if (map != null) {
       ThreadLocalMap.Entry map.getEntry(this);
       if (!= null) {
           @SuppressWarnings("unchecked")
           result = (T)e.value;
           return result;
      }
  }
   return setInitialValue();
}

//为线程设定Looper, prepare中调用
public void set(value) {
       Thread Thread.currentThread();
       ThreadLocalMap map getMap(t);
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
  }

 

小结一下:

Looper中管理这一个ThreadLocal对象,用该对象来实现不同线程间的looper对象切换以及使用其他功能

Handler

作用:用于同一个进程间的线程通信,

handler为了防止内存泄漏会有检查

//可以随时获得主线程的handler
public static Handler getMain() {
   if (MAIN_THREAD_HANDLER == null) {
       MAIN_THREAD_HANDLER new Handler(Looper.getMainLooper());
  }
   return MAIN_THREAD_HANDLER;
}

//Callback,这也表明了callback函数是运行在当前线程上的,不可以执行ui工作
public interface Callback {
       /**
        * @param msg A {@link android.os.Message Message} object
        * @return True if no further handling is desired
        */
       boolean handleMessage(@NonNull Message msg);
  }

构造方法:新建一个Handler会得到当前线程的looper对象以及里面的MessageQueue,并且获得一个传进来的callback对象

public Handler() {
       this(null, false);
}
public Handler(Callback callback, boolean async) {
       //不是static 发出可能内存泄露的警告!
       if (FIND_POTENTIAL_LEAKS) {
           final Class<? extends Handlerklass 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());
          }
      }
       //获取当前线程的Looper,还记得前面讲过 Looper.myLooper()方法了吗?
       //Looper.myLooper()内部实现可以先简单理解成:map.get(Thread.currentThread()) 
       //获取当前线程的Looper
       mLooper Looper.myLooper();
       if (mLooper == null) {
           //当前线程不是Looper 线程,没有调用Looper.prepare()给线程创建Looper对象
           throw new RuntimeException(
               "Can't create handler inside thread that has not called Looper.prepare()");
      }
       //让Handler 持有当前线程消息队列的引用
       mQueue mLooper.mQueue;
       //这些callback先不管,主要用于handler的消息发送的回调,优先级是比handlerMessage高,但是不常用
       mCallback callback;
       mAsynchronous async;
  }

Handler 在sendMessage的时候就通过mQueue引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

在准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(简称AMS)线程会通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity,看下scheduleLaunchActivity方法:

注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(在主线程进入死循环之前创建了这个线程)。

 

 

最后调用!!!

public void handleMessage(@NonNull Message msg) {
}

 


 

MessageQueue

存在于Looper中,MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理

   boolean enqueueMessage(Message msg, long when) {
       // msg 必须有target也就是必须有handler
       if (msg.target == null) {
           throw new IllegalArgumentException("Message must have a target.");
      }
       if (msg.isInUse()) {
           throw new IllegalStateException(msg " This message is already in use.");
      }
       //插入消息队列的时候需要做同步,因为会有多个线程同时做往这个队列插入消息
       synchronized (this) {
           if (mQuitting) {
               IllegalStateException new IllegalStateException(
                       msg.target " sending message to a Handler on a dead thread");
               Log.w(TAG, e.getMessage(), e);
               msg.recycle();
               return false;
          }

           msg.markInUse();
           //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
           //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
           msg.when when;
           Message mMessages;
           boolean needWake;
           if (== null || when == || when p.when) {
               // p==null 表示当前消息队列没有消息
               msg.next p;
               mMessages msg;
               //需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
               //候队列突然有消息了,就会往管道写入字符,唤醒主线程
               needWake mBlocked;
          } else {
               // Inserted within the middle of the queue. Usually we don't have to wake
               // up the event queue unless there is a barrier at the head of the queue
               // and the message is the earliest asynchronous message in the queue.
               needWake mBlocked && p.target == null && msg.isAsynchronous();
               Message prev;
               //将消息放到队列的确切位置,队列是按照msg的when 排序的,链表操作自己看咯,从链表头开始放消息,根据时间塞进去
               for (;;) {
                   prev p;
                   p.next;
                   if (== null || when p.when) {
                       break;
                  }
                   if (needWake && p.isAsynchronous()) {
                       needWake false;
                  }
              }
               msg.next p; // invariant: p == prev.next
               prev.next msg;
          }

           // 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程,我们就不在深入探讨了
           if (needWake) {
               nativeWake(mPtr);
          }
      }
       return true;
  }

//两个版本!!!
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       //这句话很重要,让消息持有当前Handler的引用,在消息被Looper线程轮询到的时候
       //回调handler的handleMessage方法
       msg.target this;
       if (mAsynchronous) {
           msg.setAsynchronous(true);
      }
       //调用MessageQueue 的enqueueMessage 方法把消息放入队列
       return queue.enqueueMessage(msg, uptimeMillis);
  }

最后执行dispatchMessage

/**
    * Handle system messages here.
    */
   public void dispatchMessage(Message msg) {
       //优先调用callback方法
       if (msg.callback != null) {
           handleCallback(msg);
      } else {
           if (mCallback != null) {
               if (mCallback.handleMessage(msg)) {
                   return;
              }
          }
           //最后会回调我们重写的handleMessage 方法
           handleMessage(msg);
      }
  }

 

Message

message又叫task,封装了任务携带的信息和处理该任务的handler

注意事项: 1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。 2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存 3.擅用message.what来标识信息,以便用不同方式处理message。

message中的target是一个handler对象

Message中的callBack是runnable对象

Message next() 
      
       final long ptr mPtr;
       if (ptr == 0) {
           return null;
      }
       int pendingIdleHandlerCount -1; // -1 only during first iteration

       //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis 
       //才会返回
       int nextPollTimeoutMillis 0;
       for (;;) {
           if (nextPollTimeoutMillis != 0) {
               Binder.flushPendingCommands();
          }
           //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)
           //一种是等到有消息产生就会返回,
           //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回
           nativePollOnce(ptr, nextPollTimeoutMillis);
           //nativePollOnce 返回之后才能往下执行
           synchronized (this) {
               // Try to retrieve the next message. Return if found.
               final long now SystemClock.uptimeMillis();
               Message prevMsg null;
               Message msg mMessages;
             //若当前msg不空但是handler已经被释放,这时需要继续找到一条能够执行的handler
               if (msg != null && msg.target == null) {
                   // 循环找到一条不是异步而且msg.target不为空的message
                   do {
                       prevMsg msg;
                       msg msg.next;
                  } while (msg != null && !msg.isAsynchronous());
              }
               if (msg != null) {
                   if (now msg.when) {
                      // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,
                      //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                      //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                       nextPollTimeoutMillis = (int) Math.min(msg.when now, Integer.MAX_VALUE);
                  } else {
                       // 获取到消息
                       mBlocked false;
                      //链表一些操作,获取msg并且删除该节点 
                       if (prevMsg != null) 
                           prevMsg.next msg.next;
                      } else {
                           mMessages msg.next;
                      }
                       msg.next null;
                       msg.markInUse();
                       //返回拿到的消息
                       return msg;
                  }
              } else {
                   //没有消息,nextPollTimeoutMillis复位
                   nextPollTimeoutMillis -1;
              }
              .....
              .....
             
  }

nativePollOnce()实现原理?

 

 

总结:

  1. 每一个Thread都能够成为LooperThread 前提是首先调用prepare方法,创建自己的handler,然后调用loop()

  2. looperThread拥有自身的MessageQueue对象,loop()方法本质上是每次都调用next()方法中获得下一条消息,如果没有获得的话会阻塞并让出cpu。

  3. Looper对应的线程拥有自己handler对象之后,可以被别的线程引用该对象,改handler对象初始化的时候就已经拥有了Looper对象的messageQueue对象,从而可以根据sendMessage()方法调用enqueueMessage()将消息入队。

  4. Looper对象获得Message对象之后就调用dispatchMessage()方法进行消息分发,实质上是实现各种回调,包括message对象以及自身写好的接口。有一个特殊的CallBack叫mCallback可以在初始化的时候传入,每次都会调用。

 

 

一些问题:

1.为什么一个线程只有一个Looper、只有一个MessageQueue?

因为线程对应的Looper是在ThreadLocal里面存储,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。ThreadLocal它的作用是可以在不同的线程之中互不干扰地存储并提供数据(就相当于一个Map集合,键位当前的Thead线程,值为Looper对象)。另外,在looper创建的方法looper.prepare()中,会有一个判断如果当前线程存在Looper对象,就会报RunTimeException,所以一个线程只有一个Looper,而MQ作为Looper的成员变量自然也就只有一个。

2.如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)

3.是不是任何线程都可以实例化Handler?有没有什么约束条件?

任何线程都可以实例化Handler,handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,如果当前线程还没有初始化Looper,或者说当前线程还不是looper线程,会报RuntimeException。

4.Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

在应用程序的入口ActivityThread里面的main方法中会创建一个主线程的looper对象和一个大Handler,(这也是为什么直接在主线程拿Handler就有Looper的原因,在其他线程是要自己Looper.prepare()) Android是基于事件驱动的,通过looper.looper()不断接收事件,处理事件,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,当收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。如果它停止了,应用也就停止了。也就是说我们的代码其实就是运行在这个循环里面去执行的,当然就不会阻塞。

而所谓ANR便是Looper.loop没有得到及时处理,一旦没有消息,Linux的epoll机制则会通过管道写文件描述符的方式来对主线程进行唤醒与睡眠,Android里调用了linux层的代码实现在适当时会睡眠主线程。

拓展

消息循环(死循环)的必要性:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出 ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出。

主线程在没有事件需要处理的时候就是处于阻塞的状态。想让主线程活动起来一般有两种方式: 第一种 :是系统唤醒主线程,并且将点击事件传递给主线程; 第二种 :是其他线程使用主线程的Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。

总结 Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

looper.looper()阻塞会不会消耗大量的cpu资源

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。因此loop的循环并不会对CPU性能有过多的消耗。

5.Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

Handler.sendMessageDelayed()内部调用sendMessageAtTime()把传入的时间转化成绝对时间when(延时的时间加上系统当前的时间),然后调用MessageQueue的enqueueMessage(),采用线程安全的方式将Message插入到消息队列中,消息队列的插入是由when顺序排列,插入的新消息有三种可能成为消息队列的head: (1)消息队列为空; (2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把消息插入到队列的头部)系列的API才会满足这个条件; (3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。 接着就是Looper.looper()启动消息循环,循环开始调用messageQueue.next()从消息队列中取出一个合理的消息。如果next()返回null,则looper()直接return,本次消息循环结束。如果消息不为空则调用msg.target.dispatchMessage(msg)处理消息(msg.target就是Handler)

.next()取下一个消息的实际执行时间取决于上一个消息什么时候处理完 在MessageQueue.next()中,如果在消息队列中顺序找到了一个消息msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于msg.when,那么会计算一个timeout,以便在到执行时间时wake up;如果当前系统时间大于或等于msg.when,那么会返回msg给Looper.loop()。所以这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理。 (1)在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。 (2)即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。 (3)在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。 所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

 

关于activityThread的执行

https://blog.csdn.net/shifuhetudi/article/details/52089562

posted @ 2021-12-27 19:37  码虫垒起代码之城  阅读(150)  评论(0编辑  收藏  举报