提到异步, 你们首先想到的必定是多线程, 多线程当中的并发, 指的是经过CPU调度计算, 让用户看上去是同时执行的, 实际上从CPU操做层面上并非真正的同时。CPU经过调度计算, 在同一时间, 只会有一条线程在执行任务, 只不过调度计算速度很快, 随机选择执行的线程, 因此咱们看起来像是同时在执行, 下面咱们来举个例子: android
private static class ThreadOne extends Thread {
private static final String ThreadName = "ThreadOne";
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
Log.i(TAG, "run: " + ThreadName + "执行了任务" + i);
}
}
}复制代码
private static class ThreadTwo extends Thread {
private static final String ThreadName = "ThreadTwo";
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
Log.i(TAG, "run: " + ThreadName + "执行了任务" + i);
}
}
}复制代码
private void threadTest() {
final ThreadOne threadOne = new ThreadOne();
final ThreadTwo threadTwo = new ThreadTwo();
threadOne.start();
threadTwo.start();
}复制代码
例子很简单, 建立两个线程, 调用 start()方法启动线程bash
上面的例子运行结果以下: (部分结果截图)多线程
不一样的手机运行结果可能不一样, 可是咱们仍是可以得出结论: 并发
多线程的异步, 是多条路径都在执行, 互不干扰, 因此他们执行的顺序是未知的, 这得看CPU的调度计算,正常状况下, 你没法控制哪条线程先执行哪条线程后执行, 或者哪条线程先结束哪条线程后结束。app
平时咱们提到 Handler, 咱们有的时候也会说是异步分发, 可是这里的"异步"跟多线程的异步是同样的吗?? 咱们看一下下面的小例子: 异步
private void test() {
Log.i(TAG, "onCreate: 事件分发前");
sHandler.sendEmptyMessage(0);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "test: 事件分发后");
}复制代码
private static Handler sHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: 事件处理回调");
}
};复制代码
咱们在onCreate()方法中执行了 test() 方法, 经过 hander 分发事件之后, 让主线程 slee 2秒, ide
运行上面的程序之后, 打印结果以下: post
发现问题了吗?? 多线程的状况下, 每一条线程中的代码是交替执行的, 可是上面的这个Handler 例子, 并无出现交替执行的状况, 而是等到了咱们 test()方法执行完成了之后, 才去执行了 Handler 的处理回调, 这看上去并非一个异步操做, 而是一个同步操做, 只不过Handler 回调的代码放到了最后执行, 这是为何呢? 下面咱们来分析一下: 学习
首先, 咱们得了解 Handler 的运行原理, 咱们的主线程会对应配备一个消息队列MessageQueue, 而且只有一个消息队列, Handler 是消息的发送接收者和处理这, Message是消息的载体, 大体的一个运行状况以下: ui
这里面, 消息的发送和处理并非连续发生的, 并非我发送了消息之后当即就去执行处理消息, 而是现将消息发送到线程中对应的消息队列的尾部, 当消息队列中前面的消息执行完了之后, 才会轮到他来执行.
再者, 在android中, 全部的事件的运行都是基于事件驱动的, activity 中的声明周期也不例外, 在 app 程序的入口类 ThreadActivity 中定义了一个内部类 H, 这个H就是一个Handler, activity 的声明周期也是经过 handler 进行分发的, 感兴趣的能够去看看源码, 这里不是咱们的重点,
这里, 我经过几张图来分析一下咱们上面的Handler 例子:
如今假设咱们的 test() 方法是消息A, 在 test()方法中经过Handler 发送的消息是消息B, 为了更直观一点, 咱们假设消息A是带血槽的(原谅我中了王者的毒):
前面咱们说过, onCreate()方法也是经过Handler 分发出来的, 也就是程序经过Handler 将咱们的消息发送到主线程的消息队列 MessageQueue 中, test()方法在 onCreate 中执行的, 咱们只看test()方法, 也就是消息A在将要被执行是的状态是这样的:
这时消息A开始执行, 当第一个血槽执完之后, 变成下面这样:
这时候, 血槽二开始执行, 在这是经过 handler 分发事件B, 也就是 test()方法中的sHandler.sendEmptyMessage(0);
如今MessageQueue 中的状态有变成了下面这样:
由于咱们的Handler 是在主线程中建立的,也就是说Handler 跟主线程绑定的, 因此经过Handler 发送出来的消息, 会先存到消息队列MessageQueue 中, 这是消息A没有执行完, 等到消息A所有执行完了, 咱们的消息B才能获得执行, 这也是为何咱们上面的那个例子打印顺序的缘由.
经过上面的分析, 咱们能够知道, Handler 中所谓的"异步", 其实并非真正的异步, 只不过是消息的发送和处理不止前后执行的, 并且咱们的程序也不用阻塞着等到 handle 回调执行完成之后再继续执行, 而是继续向下执行, 等当前消息执行完成了, 再从消息队列中取出下一个消息执行.最终的执行也并非异步的,而是同步的, 只不过消息B放到了事件A的后面, 当时间A执行完了才能轮到事件B执行.
并且, 在这里咱们须要注意一个问题, Handler 最终的回调也是在主线程的, 咱们不能再他的回调方法中执行耗时操做, 不仅仅是 handler.sendMessage()方法, 咱们调用 handler.post()方法的时候他也是这样的.
说到这里, 让我想起来以前在学习 view 的绘制过程当中遇到的一个问题.
咱们知道, view 的测量绘制操做是在 ViewRootImpl 这个类里面的, 这个类是在Activity 的onResume()方法后才建立的, 因此咱们在 onCreat()方法中去获取 view 的宽高, 获得的都是0. 若是想在 onCreate()方法中获取宽高, 其中的一个方法就是经过 view.post()方法去获取,咱们能够看下这个方法的源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}复制代码
AttachInfo 是View 的一个内部类, 在 onCreate()方法中 view 尚未被建立, 因此这里是AttachInfo 是 null 的, 这是他会调用下面这个方法:
getRunQueue().post(action);
复制代码
在调用这个方法以后, 他会把传入的Runnable 对象放入到运行队列 RunQueue 中, 这里只是放入运行队列, 而并无去执行, 真正的执行是在哪里呢?在ViewRootImpl的 performTraversals()这个方法里面, 这里为了便于分析, 我简化一下这个方法:
performTraversals() {
// 省略.....
getRunQueue().executeActions(mAttachInfo.mHandler);
performMeasure();
performLayout();
performDraw();
}复制代码
这里面会真正的去执行运行队列中的消息, 可是问题来了, 咱们在这个方法里面, 是先调用的运行队列, 再调用的 performMeasure()方法, 按道理来讲, 咱们的 view.post() 里面也拿不到 view 的宽高, 可是咱们是能够拿到的, 其实这个缘由就是咱们上面分析的那样, 当执行performTraversals()这个方法时,它是阻塞在消息队列头部的, 而后调用 getRunnQueue().executeAction()方法会把运行队列里面的消息发送到消息队列里面, 可是它是排在 performTraversals()这个方法以后的, 只有当performTraversals()方法执行完了, 才能轮到运行队列里面的消息执行.