乐趣区

关于android:从源码解析Handler机制

Handler 机制是面试中的常客了,明天和大家一起通过源码层面来解析一下。
@[toc]

前提知识点

Handler 机制波及到几个类:MessageQueue, Looper, Message, ActivityThread。

  • ActivityThread: 主线程,开启 loop 循环,治理 application 过程,调度治理 activity, 播送及其他操作。
  • Message: handler 机制中音讯的载体,蕴含相干形容和数据对象,蕴含属性 what, obj, arg1,arg2 等。
  • MessageQueue: 音讯队列。
  • Looper:循环去 MessageQueue 中音讯。

AppCompatActivity.java-> FragmentActivity.java->SupportActivity.java->Activity.java

在 Activity 中默认有一个 ActivityThread 这是一个 main thread, 其中 final Looper mLooper = Looper.myLooper(); 实例化了一个 looper 对象。
在 ActivityThread.java 中的 main 办法中,有如下代码

public static void main(String[] args) {
           ... 省略其余代码
        Looper.prepareMainLooper();
          ... 省略其余代码
        Looper.loop();}
Looper.java

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 构造函数中初始了音讯队列 MessageQueue 对象
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();}

创立 looper 对象之前,会判断 sThreaLocal 中是否曾经绑定过 Looper 对象,如果是则抛出异样,确保一个线程中 Looper.prepare()只调用一次。
如果在 MainActivity 中调用,如下代码,会报错。

通过上述代码能够得悉,Activity 中默认开始了 loop()循环,用于获取 handler 发送的音讯。

最简略的利用

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private final String TAG = "MainActivity";
    public final int MSG_DOWN_FAIL = 1;
    public final int MSG_DOWN_SUCCESS = 2;
    public final int MSG_DOWN_START = 3;

    @BindView(R.id.btn_start_thread)
    Button btnStart;

    @BindView(R.id.tv_status)
    TextView tvShow;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {switch (msg.what) {
                case MSG_DOWN_START:
                    tvShow.setText("down start");
                    break;
                case MSG_DOWN_SUCCESS:
                    tvShow.setText("down success");
                    break;
                case MSG_DOWN_FAIL:
                    tvShow.setText("down fail");
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        btnStart.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {switch (v.getId()) {
            case R.id.btn_start_thread:
                new MyThread().start();
                break;
            default:
                break;
        }
    }

    class MyThread extends Thread {

        @Override
        public void run() {handler.sendEmptyMessage(MSG_DOWN_START);
            try {Thread.sleep(3000);
            } catch (InterruptedException e) {e.printStackTrace();
            }

            Message msg = Message.obtain();
            msg.what = MSG_DOWN_SUCCESS;
            handler.sendMessage(msg);
        }
    }
}

sendMessage 之后产生了什么

public final boolean sendMessage(Message msg)
    {return sendMessageDelayed(msg, 0);
    }

sendMessage 调用了 sendMessageDelayed->sendMessageAtTime->enqueueMessage, 最终调用了 MessageQueue 的 enqueueMessage 的办法, 并将本人设置为 message 的 target

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

接下来就到了音讯队列 MessageQueue 中了,来看一下

  • 如果 message 的 target 为 null,则间接抛出异样
  • 依照 message 的工夫 when 有序插入到 MessageQueue 中,所以 MessageQueue 是一个按工夫排序的有序队列。

怎么取 MessageQueue 中的音讯

其实这里就是 Looper.loop()办法从音讯队列中一直循环取音讯了。

一直调用 MessageQueue 的 next()办法取音讯,如果 message 不为 null,则调用 handler 的 dispatchMessage 散发音讯。


这个 handleMessage 办法就是在创立 Handler 中笼罩的办法。

至此 Handler 的发送音讯和音讯解决流程曾经介绍结束。

面试常见问题

1.Looper.loop() 为什么不会阻塞主线程

Android 的 Ui 线程,开启了一个死循环,然而并没有阻塞主线程是为什么呢?
在 MessageQueue 的 next 办法中有这样一行代码

private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/

nativePollOnce 办法是一个 native 办法,当调用此 native 办法时,主线程会开释 CPU 资源进入休眠状态,直到下条音讯达到或者有事务产生,通过往 pipe 管道写端写入数据来唤醒主线程工作,这里采纳的 epoll 机制。

2.Handler 的 sendMessageDelayed 或者 postDelayed 是如何实现的

messageQueue 的 enqueueMessage 是依照音讯的 when 工夫有序退出到队列的,取的时候也是依照工夫进行取的

能够看到如果以后工夫小于 msg 设置的工夫,会计算一个 timeout,在 timeout 到了之后,才会将 UI 线程唤醒,交由 CPU 执行。

总结

1.APP 启动创立主线程的时候会通过 ActivityThread 执行 Looper.prepare(),创立一个 looper 对象,在公有的构造方法中又创立了 MessageQueue 作为此 Looper 对象的成员变量,Looper 对象通过 ThreadLocal 绑定 MainThread 中。
2. 在创立 Handler 对象的时候,通过构造函数获取 ThreadLocal 绑定的 looper 对象,并通过 looper 获取音讯队列 MessageQueue 作为成员变量
3. 子线程发送音讯时,将 msg 的 target 设置为 handler 本身,之后调用成员 MessageQueue 的 enqueueMessage 将音讯依照 msg.when 工夫排序插入到音讯队列
4. 主线程通过 Looper.loop() 开启不阻塞 UI 线程的死循环,通过绑定的 looper 对象获取 MessageQueue,调用 next()办法一直获取 msg, 并通过 (handler)msg.target.dispatchMessage 发送至咱们创立的 handler 时笼罩的 handleMessage() 办法

退出移动版