Android 消息分发机制

  • Android 中针对耗时的操作,放在主线程操作,轻者会造成 UI 卡顿,重则会直接无响应,造成 Force Close。同时在 Android 3.0 以后,禁止在主线程进行网络请求。
  • 针对耗时或者网络操作,那就不能在主线程进行直接操作了,需要放在子线程或者是工作线程中进行操作,操作完成以后,再更新主线程即 UI 线程。这里就涉及到一个问题了,在子线程执行完成以后,怎么能更新到主线程即 UI 线程呢,针对以上问题,就需要用到 Android 的消息机制了,即: Handler, Message, MessageQueue, Looper 全家桶

文章结构

  1. 用法
  2. 常见问题
    1. Can't create handler inside thread that has not called Looper.prepare()
    2. 内存泄漏
  3. 从源码角度解析为什么可以跨线程进行 UI 更新

用法

  1. 继承 Handler 类,复写 handleMessage() 方法, MyHandler 类的构造方法传入当前 Activity ,然后就可以对 ActivityUI 进行更新了。当前例子只是弹出一个 Toast
    static class MyHandler extends Handler {
        // 弱引用,防止内存泄漏
        WeakReference<MainActivity> weakReference;

        MyHandler(MainActivity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WHAT:
                    MainActivity mainActivity = weakReference.get();
                    if (mainActivity != null) {
                        Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }
  1. MainActicity onCreate,初始化 Handler
    MyHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
    }
  1. 点击 Button 按钮,模拟在子线程进行耗时操作,方法执行完成以后,通过 mhandler 发送一个 Message
    public void sendMessage(View view) {
        //模拟耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    Message message = Message.obtain();
                    message.what = WHAT;
                    mHandler.sendMessage(message);
                }
            }
        }).start();

    }
  1. MyHandlerhanldeMessage() 方法中,接收到该 message ,并弹出 Toast 提醒
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WHAT:
                    MainActivity mainActivity = weakReference.get();
                    if (mainActivity != null) {
                        Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
  1. 以上代码,实现了在子线程执行耗时操作,执行完成以后更新 UI
    完整代码如下:
public class MainActivity extends AppCompatActivity {

    public static final int WHAT = 1000;

    MyHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

    public void sendMessage(View view) {
        //模拟耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    Message message = Message.obtain();
                    message.what = WHAT;
                    mHandler.sendMessage(message);
                }
            }
        }).start();

    }

    static class MyHandler extends Handler {
        // 弱引用,防止内存泄漏
        WeakReference<MainActivity> weakReference;

        MyHandler(MainActivity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WHAT:
                    MainActivity mainActivity = weakReference.get();
                    if (mainActivity != null) {
                        Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

常见问题

  1. Can't create handler inside thread that has not called Looper.prepare().
  • 在子线程 new 出来的 Handler 发送 message ,会出现该问题
    log如下:
06-04 14:55:38.365 16567-16818/cc.lijingbo.paypasswordview E/AndroidRuntime: FATAL EXCEPTION: Thread-1018
                                                                             Process: cc.lijingbo.paypasswordview, PID: 16567
                                                                             java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                                                 at android.os.Handler.<init>(Handler.java:200)
                                                                                 at android.os.Handler.<init>(Handler.java:114)
                                                                                 at cc.lijingbo.paypasswordview.MainActivity$1.run(MainActivity.java:51)
                                                                                 at java.lang.Thread.run(Thread.java:818)

造成 crash 的代码如下:

    public void sendMessage(View view) {
        //模拟耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    Message message = Message.obtain();
                    message.what = WHAT;
                    new Handler().sendMessage(message);
                }
            }
        }).start();

    }
  • 如何避免: 假如是更新 UI 的话,只能使用 UI 线程的 Handler 来发送消息。
  1. 内存泄漏
  • 假如在子线程执行了耗时操作,这时用户操作进入了其他的 acitvity, 那么 MainActivity 就会被内存回收的,但是这个时候发现 Handler 还在引用着 MainActivity,内存无法及时回收,造成内存泄漏
  • Handler 防止内存泄漏常见方法:
    1. Handler 继承类,用 static 进行修饰,成为静态内部类
    2. Handler 中对 Activity 进行弱引用
    3. Activity 执行 onDestroy() 方法时,对 Handler 做执行清空操作。

从源码角度解析为什么可以跨线程进行 UI 更新

  • 消息机制主要包括: Handler, Looper , MessageQueue, Message
    • Handler: 两个作用,发送消息到 MessageQueue ,同时 Looper 不断从 MessageQueue 拿出消息分发给 Handler ,Handler 把消息通过 dispathMessage()handleMessage() 把消息分发具体的业务
    • Looper: 初始化 MessageQueue, 内部死循环不断从 MessageQueue 中获取数据进行分发给 Handler
    • MessageQueue: 消息队列,内部使用的是单链表的数据结构,
    • Message: 被传递的消息
  • 先来张时序图,让大脑有个大致的了解。
    image

  • 为什么通过 Handler 可以把子线程的结果通知或者携带给 UI 线程
    • 这里的 Handler 指的是主线程的 Handler ,同时与 Handler 配套的 LooperMessageQueue 是在 UI 线程初始化的,所以在子线程中调用 Handler 发送消息可以更新 UI 线程。
    • LooperUI 线程源码, 在 ActivityThread 类:
      public static void main(String[] args) {
          ...
          Looper.prepareMainLooper(); // 初始化在 UI 线程的 Looper 
    
          ActivityThread thread = new ActivityThread();
          thread.attach(false);
    
          if (sMainThreadHandler == null) {
              sMainThreadHandler = thread.getHandler();
          }
    
          if (false) {
              Looper.myLooper().setMessageLogging(new
                      LogPrinter(Log.DEBUG, "ActivityThread"));
          }
    
          // End of event ActivityThreadMain.
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          Looper.loop();  // 开启从当前线程的 MessageQueue 中获取 Message 
    
          throw new RuntimeException("Main thread loop unexpectedly exited");
      }
    • Looper
      public static void prepareMainLooper() {
          prepare(false);// 设置不可退出
          synchronized (Looper.class) {
              if (sMainLooper != null) {
                  throw new IllegalStateException("The main Looper has already been prepared.");
              }
              sMainLooper = myLooper();
          }
      }
      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));// 初始化 Looper
      }
      private Looper(boolean quitAllowed) {
          mQueue = new MessageQueue(quitAllowed);// 在UI 线程,传入的是 fasle ,MessageQueue 不可退出。
          mThread = Thread.currentThread();
      }

源码解析

  • 先从 Handler 的 sendMessage() 这个入口开始。
    发现 sendMessage() 调用了内部方法 sendMessageDelayed() ,而 sendMessageDelayed() 调用了内部方法 sendMessageAtTime(), sendMessageAtTime() 调用了 enqueueMessage()enqueueMessage() 调用了 MessageQueueenqueueMessage() 方法。以上传来传去的这些在 Handler 中的方法,都是做各种状态的处理,然后把 Message 传到 MessageQueueenqueueMessage() 方法。
    源码如下:
    public final boolean sendMessage(Message msg)
    {   // 立即发送的消息,设置延迟时间为 0 
        return sendMessageDelayed(msg, 0);
    }

    ...

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {     
        if (delayMillis < 0) {
            delayMillis = 0;
        }  
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // mQueue 从 Looper 中获取的。判断当前 Handler 持有的 MessageQueue 是否为 null ,为 null 的时候抛出 RuntimeException 。
        MessageQueue queue = mQueue;
        if (queue == null) {   
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 传入 MessageQueue, Message, Time
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    ...

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 设置 Message 的 target 为 当前的 Handler
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 把 Message 和 Time 传入到 MessageQueue 的 enqueueMessage() 方法执行。
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    ...
  • MessageQueue 的 enqueueMessage() 源码
    boolean enqueueMessage(Message msg, long when) {
        // Message 的target 即 Handler ,不能为 null ,为 null 的时候抛异常
        if (msg.target == null) { 
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) { // msg 用过抛出异常
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {// 退出标志位,要是退出的话, 执行 msg 的回收操作。
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(); //Message 回收操作
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // MessageQueue 没有消息,或者是 msg 是第一条消息,唤醒阻塞的 MsssageQueue
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 将消息按时间顺序插入到 MessageQueue, 一般地,不需要唤醒消息队列,除非消息队头存在 barrier ,并且 Message 同时是队列最早的异步消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueue 按照 Message 触发时间的先后顺序排列的,对头的消息是将要最早触发的消息,当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

  • 取消息
    当发送了消息后,由 MessageQueue 维护消息队列,然后在 Looper 中通过 loop() 方法不断的取消息。
    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.");
        }
        final MessageQueue queue = me.mQueue;

        // 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();
        // 死循环,不断的从 MessageQueue 中获取 Message 
        for (;;) {
            Message msg = queue.next(); // MessageQueue 没消息的时候,可能会阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                // 消息分发,Message 的 target 就是 Handler, 这里调用 Handler 的dispatchMessage() 把消息分发出去
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
    Message next() {
        // 返回
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        // 当消息循环已经退出则返回
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // 循环迭代首次为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 阻塞操作,当等待 nextPollTimeoutMillis时长 或者消息队列被唤醒,都会返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 当 msg 的 Handler 为null时,查询 MessageQueue 的下一条异步消息,为Null则退出
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 当异步消息触发时间大于当前时间,则设置下一次循环的超市时长
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取一条消息,设置消息为已使用,并返回
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        // 设置消息的使用状态
                        msg.markInUse();
                        // 获取MessageQueue下一条要执行的消息
                        return msg;
                    }
                } else {
                    // 没有消息
                    nextPollTimeoutMillis = -1;
                }
                // 所有待处理的消息已被处理,消息正在退出,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
            }
        }
    }

nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一个消息到来前,还需要等待的时长;当 nextPollTimeoutMillis = -1 时,表示消息队列中无消息,会一直等待下去。
可以看出 next() 方法根据消息的触发时间,获取下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作。

  • 分发消息
    分发消息,由 Handler 的 dispatchMessage() 方法执行
    public void dispatchMessage(Message msg) {
        // msg 的 callback 不为null,这消息分发给 handleCallback() 执行, 然后调用 message.callback.run() 方法
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // Handler 的内部接口,不为null ,执行该接口的实现方法 handleMessage();
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // Message 的 Runable 和 Callback 接口都为 null 的情况下,执行 Handler 的handlrMessage() 方法。
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }

消息分发流程:
Message 的 msg.callback 不为空的时候,执行 message.callback.run() 方法
Message 成员变量 Callback 不为null 的时候,执行该接口的 handleMessage() 方法
最后调用 Handler 自身的 handleMessage() 方法

  • 最后放一张图来整理理解
    image

https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/message-mechanism.html

转载于:https://www.cnblogs.com/liyiran/p/9150452.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值