美文网首页
Android Handler笔记

Android Handler笔记

作者: AArman | 来源:发表于2021-05-04 16:15 被阅读0次

消息机制模型

  1. Message:需要传递的消息,可以传递数据;
  2. MessageQueue:消息队列,并不是队列,实际上是一个单链表的数据结构来 管理和维护Message,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);因为有延时消息的存在。所以没设计成队列,因为并不能完全保证先进先出,而是需要根据时间来判断,所以Android中采用了链表的形式来实现这个队列,也方便了数据的插入
  3. Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  4. Looper:不断循环执行(Looper.loop),负责关联线程以及消息的分发,从 MessageQueue 中获取 Message 分发给 Handler。

Handler 的面目

Handler handler = new Handler() {
    @Overridse
    public void handleMessage(Message msg) {
    }
};
Message msg = new Message();
handler.sendMessage(msg);
  1. 创建Handler时会 获取当前线程的 Looper(Looper.myLooper();)(一个Handler仅一个),Looper 内部创建 MessageQueue;
  2. sendMessage(msg)或post(Runnable) 会将本Handler的引用打包到Message.target并发送给 Looper 的消息队列 MessageQueue 中(通过单向链表的数据结构来存储消息);
  3. Looper 的loop方法中拿到当前的消息队列,创建一个死循环不断取消息queue.next();,拿到就分发给 Handler 执行 msg.target.dispatchMessage(msg);,执行完之后会将用完的消息回收掉msg.recycleUnchecked();;
  4. MessageQueuenext()有for(;;)取消息,当前时间没有消息就处于阻塞状态nativePollOnce(ptr, nextPollTimeoutMillis);,取到了消息即退出循环。
//之前必须调用 prepare ; 
public static @Nullable Looper myLooper() {
   return sThreadLocal.get(); 
}
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}

ActivityThread 和Handler的关系

  1. 通过在main方法中运行一个死循环(Looper.loop())让 main 一直执行。
  2. 本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件。这些事件最后通通被作为了Message发送到了MessageQueue中。
  3. 主线程做耗时操作本质上不是阻塞了主线程,而是阻塞了Looper的loop方法。导致loop方法无法处理其他事件,导致出现了ANR
public static void main(String[] args) {
  AndroidOs.install();

  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false, startSeq);//建立Binder通道 (创建新线程)

  if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
  }

  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程Looer会不会消耗cpu资源
  • 这就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
  • 这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
MessageQueue没有消息时候会怎样?阻塞之后怎么唤醒呢?说说pipe/epoll机制?
  1. 当消息不可用或者没有消息的时候就会阻塞在next方法,而阻塞的办法是通过pipe/epoll机制。
  2. epoll机制是一种IO多路复用的机制,具体逻辑就是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的。在Android中,会创建一个Linux管道(Pipe)来处理阻塞和唤醒。
  • 当消息队列为空,管道的读端等待管道中有新内容可读,就会通过epoll机制进入阻塞状态。
  • 当有消息要处理,就会通过管道的写端写入内容,唤醒主线程。
同步屏障

2.自定义Handler时如何避免内存泄漏

一般非静态内部类持有外部类的引用的情况下,造成外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。为了避免这个问题,我们可以自定义的Handler声明为静态内部类形式,然后通过弱引用的方式,让Handler持有外部类的引用,从而可避免内存泄漏问题。

以下是代码实现

private WeakReference<MainActivity> activityWeakReference;
private MyHandler myHandler;

static class MyHandler extends Handler {
private MainActivity activity;

MyHandler(WeakReference<MainActivity> ref) {
this.activity = ref.get();
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//需要做判空操作
if (activity != null) {
activity.mTextView.setText("new Value");
}
break;
default:
Log.i(TAG, "handleMessage: default ");
break;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//在onCreate中初始化
activityWeakReference = new WeakReference<MainActivity>(this);
myHandler = new MyHandler(activityWeakReference);

myHandler.sendEmptyMessage(1);
mTextView = (TextView) findViewById(R.id.tv_test);
}

相关文章

网友评论

      本文标题:Android Handler笔记

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