Android|Android_Handler机制原理解析和源码分析

什么是Handler机制
在Android开发的过程中,会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),因为UI的更新要通过Main thread来进行(其实特殊子线程也可以更新UI)。那么这里就涉及到了如何将子线程的数据传递给主线程的问题。
所以Android系统提供了一个消息传递的机制——Handler,可用于将子线程的数据传递给主线程,其实,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。
Handler机制运行流程
一、Handler机制四大核心类

  • Handler
    Handler类就像工人一般,往传送带上发送Message。
  • Message
    Message就好比传送带上的货箱,货箱里装着要传送的物品。
  • MessageQueue
    MessageQueue如同一个传送带,里面存放着一个个的Message。在Handler机制中,MessageQueue是一个优先级队列,是按传入的执行时间来进行入队出队操作。
  • Looper
    Looper就像传送带的轮子,一直循环执行。
    Android|Android_Handler机制原理解析和源码分析
    文章图片
二、Handler源码分析
通过阅读Handler类源码可以看到如图以下发送消息的方法最终都是调用了MessageQueue的enqueueMessage方法。
Android|Android_Handler机制原理解析和源码分析
文章图片

三、MessageQueue源码分析
1、MessageQueue队列
1)MessageQueue是一个优先级队列,优先级是以要延迟执行的时间为判断依据,时间越短,执行优先级越长,如果延迟执行时间都为0,则插入到此节点的前面。
2)如果执行时间不为0,传过来的时间是按入参+系统时间为值,系统时间是越来越大,所以不可能出现时间相等的情况(时间为0除外)。
这里可以查看Handler中的sendMessageDelayed源码,如下所示:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } // 延迟时间是调用了系统时间+延迟时间 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }

3)MessageQueue是在Looper类的 构造方法中 创建的,所以 一个Looper 对应一个 MessageQueue;
2、enqueueMessage方法
enqueueMessage方法主要作用是往消息队列中存消息,即入队方法。
boolean enqueueMessage(Message msg, long when) { 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 e = 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(); msg.when = when; Message p = mMessages; boolean needWake; // 注释1:当不存在消息或者当前要执行的时间为0,也就是立即执行或者当前延迟执行时间小于队列中的消息延迟时间的时候 // 将当前Message插入到队列最前面的位置(延迟执行时间为0的都插入到队列的最前面),如果线程阻塞则唤醒 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. // 根据时间的顺序调用 msg.next,给每一个消息指定它的下一个消息是什么 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(); // 注释2:如果不满足上述条件,则需要遍历整个消息队列,依次判断每个Message要延迟执行的时间,对比之后插入到其中,如果比某个几点要延迟执行的时间小,则插入到这个节点的前面进行执行 Message prev; for (; ; ) { prev = p; p = p.next; // 如果判断时间比某个节点小则跳出for循环,之后执行插入到此节点的前面的逻辑 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; }

这段代码有几个点要分析一下:
  • 注释1:当不存在消息或者当前要执行的时间为0,也就是立即执行或者当前延迟执行时间小于队列中的消息延迟时间的时候
    将当前Message插入到队列最前面的位置(延迟执行时间为0的都插入到队列的最前面),如果线程阻塞则唤醒。
  • 注释2:如果不满足上述条件,则需要遍历整个消息队列,依次判断每个Message要延迟执行的时间,对比之后插入到其中,如果比某个几点要延迟执行的时间小,则插入到这个节点的前面进行执行。
3、next方法
next方法负责从消息队列中取出Message,方法返回Message 对象。
  • 注释1:执行循环,不断从队列中获取Message,因为可能有Message插入到队头部,所以不断循环获取;
  • 注释2:获取当前系统时间;
  • 注释3:获取队列头消息;
  • 注释4:队头消息时间大于当前时间,即队头消息还没到执行时间;
  • 注释5: 当开始执行时,就从队列中取出Message进行返回。
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 only during first iteration int nextPollTimeoutMillis = 0; // 注释1:执行循环,不断从队列中获取Message,因为可能有Message插入到队头部,所以不断循环获取 for (; ; ) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 调用native层的nativePollOnce方法, nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message.Return if found. // 注释2:获取当前系统时间 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; // 注释3:获取队列头消息 Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier.Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { // 注释4:队头消息时间大于当前时间,即队头消息还没到执行时间 if (now < msg.when) { // Next message is not ready.Set a timeout to wake up when it is ready. // 消息还没准备好,就设置一个唤醒时间,此时是等待状态 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 注释5: 当开始执行时,就从队列中取出Message进行返回 // Got a message. 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(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ...... } }

【Android|Android_Handler机制原理解析和源码分析】四、Looper源码分析
1、loop方法
  • 注释1:如果当前线程没有创建looper则抛出异常,所以要在子线程先执行Looper.prepare()方法;
  • 注释2:执行for循环,开始获取消息;
  • 注释3:不断执行queue.next()方法,从MessageQueue获取消息,如果消息还没到执行时间,则处于阻塞等待状态;
  • 注释4到注释5: Message类的target属性就是handler,这里调用handler的dispatchMessage方法,dispatchMessage又会调用handleMessage方法,这里就回到了创建handler的回调方法中,就可以在主线程中更新UI。
public static void loop() { final Looper me = myLooper(); if (me == null) { // 注释1:如果当前线程没有创建looper则抛出异常,所以要在子线程先执行Looper.prepare()方法 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 获取MessageQueue 实例 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(); // 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); boolean slowDeliveryDetected = false; // 注释2:执行for循环,开始获取消息 for (; ; ) { // 注释3:不断执行queue.next()方法,从MessageQueue获取消息,如果消息还没到执行时间,则处于阻塞等待状态 Message msg = queue.next(); // might block 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; // 注释4: 打印日志,开始分发Message if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); }final long traceTag = me.mTraceTag; long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; if (thresholdOverride > 0) { slowDispatchThresholdMs = thresholdOverride; slowDeliveryThresholdMs = thresholdOverride; } final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); final boolean needStartTime = logSlowDelivery || logSlowDispatch; final boolean needEndTime = logSlowDispatch; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); }final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; try { // 注释5: Message类的target属性就是handler,这里调用handler的dispatchMessage方法,dispatchMessage又会调用handleMessage方法,这里就回到了创建handler的回调方法中,就可以在主线程中更新UI msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logSlowDelivery) { if (slowDeliveryDetected) { if ((dispatchStart - msg.when) <= 10) { Slog.w(TAG, "Drained"); slowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. slowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); } // 打印日志,结束分发Message 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(); } }

面试问题
一、为什么谷歌要提出:“UI更新一定要在UI线程里实现”这一规则呢?
目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:
Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。
二、 为什么用异步消息处理方式可以更新UI?
因为 handler 依赖于创建 handler 对象所在的线程,比如在主线程中创建handler对象,在子线程中不能直接更新UI,需要通过一系列的 发送消息、入队、出队等操作,最后调用到 handler.handleMessage(msg) 方法,此时 handleMessage()方法已经在主线程中,所以就可以在 handleMessage() 方法中更新UI。
Android|Android_Handler机制原理解析和源码分析
文章图片

三、一个线程可以创建几个Handler?
一个线程可以创建多个handler,比如每个Activity都可以创建Handler,说明UI线程可以创建多个handler。
四、一个线程有几个Looper,如何保证?
一个线程只创建一个Looper。在使用Handler机制之前,需要先调用prepare方法,源码如下,这段代码做了两件事:
第一件事:从sThreadLocal中获取Looper,如果能获取到,则抛出异常,从而保证一个线程只有一个Looper;
第二件事:在prepare方法内new一个Looper,然后放到ThreadLocal中。
private static void prepare(boolean quitAllowed) { // 从`sThreadLocal`中获取Looper,如果能获取到,则抛出异常,从而保证一个线程只有一个Looper if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // new一个Looper,然后放到ThreadLocal中 sThreadLocal.set(new Looper(quitAllowed)); }

查看ThreadLocal类的set方法,可以发现,ThreadLocal会把当前ThreadLocal的实例作为key,传入的Looper作为value,存入到数组中(ThreadLocal是以一个数组来维护,方便每个线程处理自己的状态而引入的一个机制。)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }

五、Handler内存泄露原因?
Handler会造成内存泄露是因为内部类持有了外部类的引用造成的,至于是如何持有外部类的,如下所示:
@SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); // jvm的可达性 MainActivity.this.click(); } }; private void click() { }

handleMessage方法中,调用了外部类MainActivity的click方法,这就是内部类持有外部类的引用。当MainActivity销毁的时候,handler还没有执行完延迟消息,就一直持有外部类的引用。
那接下来分析是如何造成内存泄露的,查看Handler的enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 把当前的handler对象赋值给Message msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }

会发现msg.target = this; 会把当前的handler对象赋值给Message,而Message在未处理完之前会一直存在内存当中,所有Message会一直持有Handler的引用,Handler又会持有外部类的引用,所以如果长时间未回收Message,就可能造成内存泄露。
为何其他内部类没有造成内存泄露,比如ViewHolder?
因为MessageQueue中的Message的处理,是有延迟时间的,如果延迟时间太长,就会一直持有外部类引用,从而造成内存泄露。
那对于这个问题要如何解决?
通过调用MessageQueue的remove方法。
六、为何主线程可以new Handler?如果想在子线程中new Handler需要做什么准备?
之所以可以在主线程中创建Handler是因为在Framwork层的ActivityThread类中的main方法中已经创建了主线程的Looper和执行loop循环;
主线程中的所有代码逻辑都是执行在主线程的Looper中。可以查看ActivityThread类中的H类,H类继承于Handler类,处理所有的消息逻辑。
public static void main(String[] args) { // 省略代码。。。。。。 // 注释1:创建主线程的Looper Looper.prepareMainLooper(); // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. // It will be in the format "seq=114" long startSeq = 0; if (args != null) { for (int i = args.length - 1; i >= 0; --i) { if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { startSeq = Long.parseLong( args[i].substring(PROC_START_SEQ_IDENT.length())); } } } ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); 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); // 注释2:执行loop循环 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }

那么如果想在子线程中new Handler需要做什么准备?
就如同主线程一样,需要先调用prepare()方法来创建一个Looper,然后再执行loop()方法开启Looper的循环。
new Thread(new Runnable() { @SuppressLint("HandlerLeak") @Override public void run() { Looper.prepare(); threadHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 会收到消息 Log.e( "run: ", "接收到消息"); } }; // 会打印 Log.e( "run: ", "loop"+Thread.currentThread().getId()); Looper.loop(); // 不会打印,因为loop一直处于阻塞状态,所以不会向下执行,整个子线程都卡到这里 Log.e( "run: ", "循环外Log不打印"); } }).start();

七、子线程的Looper,当消息队列中没有消息的时候的处理方案是什么?有什么用?
需要调用Looper的quitSafely()函数,quitSafely()函数会调用MessageQueue的quit方法,源码如下:
void quit(boolean safe) { // 注释1:如果是主线程,不允许退出 if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); }synchronized (this) { if (mQuitting) { return; } mQuitting = true; // 注释2:移除队列中所有的Message if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); }// We can assume mPtr != 0 because mQuitting was previously false. // 注释3:当移除掉所有的Message之后,唤醒Looper循环 nativeWake(mPtr); } }

  • 注释1:如果是主线程,不允许退出;
  • 注释2:移除队列中所有的Message,释放内存;
  • 注释3:当移除掉所有的Message之后,会唤醒Looper循环,当Looper循环被唤醒后,会触发next()方法的nativePollOnce(ptr, nextPollTimeoutMillis); 方法,将阻塞的loop循环唤醒,而取出的Message就为null,next方法就会返回null,代码如下:
if (mQuitting) { dispose(); return null; }

next方法返回null,在loop方法中就会直接return;
Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }

这时loop中的无线for循环就会停止,代码就会往下执行下去。整个子线程的代码才能执行完成,从而释放线程。
new Thread(new Runnable() { @SuppressLint("HandlerLeak") @Override public void run() { Looper.prepare(); threadHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 会收到消息 Log.e( "run: ", "接收到消息"); } }; // 会打印 Log.e( "run: ", "loop"+Thread.currentThread().getId()); Looper.loop(); // 调用threadHandler.getLooper().quitSafely(); 方法后,message为null则会打印下面日志,执行完子线程代码 Log.e( "run: ", "循环外Log不打印,需要释放Message,调用threadHandler.getLooper().quitSafely(); "); } }).start();

八、既然可以存在多个Handler往MessageQueue中添加消息(发消息时各个Handler可能处于不同的线程),那它内部是如何保证线程安全的呢?
MessageQueue的enqueueMessage方法和next方法都是通过synchronized来保证线程安全的。
九、Message该如何创建?
Message可以通过new Message()进行创建,但这种创建Message会造成内存抖动,所以正确的创建Message应该调用Hand而的
obtainMessage方法,handler的obtainMessage方法会调用Message的obtain方法,源码如下:
public static Message obtain(Handler h, int what) { Message m = obtain(); m.target = h; m.what = what; return m; }

然后再找到obtain()方法,源码如下:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { // 链表结构的Message池子 Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }

这段代码表示如果Message池中有创建的Message就可以复用,不用频繁的new Message(),如果没有,再new一个Message。
这样就可以避免频繁创建Message从而造成内存抖动。
当移除Message的时候,也不是真正的删除掉一个Message,而是重置这个Message的状态参数,当size大于50个的时候才销毁。源码如下:
private void removeAllMessagesLocked() { Message p = mMessages; while (p != null) { Message n = p.next; // 回收 p.recycleUnchecked(); p = n; } mMessages = null; }

找到Message的recycleUnchecked方法,重置Message状态,这里用的是享元设计模式(如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象。目的是提高系统性能)。
void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = https://www.it610.com/article/null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }

十、使用Handler的postDelay后消息队列会有什么变化?
使用Handler的postDelay后,enqueueMessage方法中会执行nativeWake(mPtr); 唤醒方法,队列被唤醒,loop继续执行next方法,在next方法中会对比执行时间和当前时间,计算出一个消息等待时间,
if (now < msg.when) { // Next message is not ready.Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); }

然后程序下一次for循环,执行nativePollOnce(ptr, nextPollTimeoutMillis); 方法,让消息队列进入等待状态,等时间到后再唤醒。
十一、Looper的死循环为什么不会导致应用卡死?
在点击Launch桌面的图标第一次启动Activity时,会最终走到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法。
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程肯定不能运行一段时间后就自动结束了,那么如何保证一直存活呢?
简单的做法就是可执行代码能一直执行下去,死循环便能保证不会被退出,所以主线程是一直运行在Looper的loop死循环中,所以Looper只有一直循环下去,主线程才能一直执行下去。
除了主线程还有binder线程也是采用死循环方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单的死循环,无消息时会休眠,但是死循环又如何处理其他事物呢??通过创建新的线程。真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。
主线程的死循环一直运行会不会特别消耗CPU资源呢?
其实不然这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。另外主线程的唤醒可以通过输入事件或者发送Message来触发。
学习参考
Handler(二) - 如何发送消息:
https://www.jianshu.com/p/ca108516007a
Android Handler消息机制原理最全解读(持续补充中):
https://blog.csdn.net/wsq_tomato/article/details/80301851?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
android Handler机制原理解析(一篇就够,包你形象而深刻):
https://blog.csdn.net/luoyingxing/article/details/86500542
https://www.jianshu.com/p/f03a3dc55941
为什么不能在子线程中更新UI:
https://blog.csdn.net/haoyuegongzi/article/details/79414081
Handler机制Looper死循环为什么不会导致应用卡死:
https://www.jianshu.com/p/e52947e4028d

    推荐阅读