- 领 域
- 计算机编程
- 通常用途
- 处理相对耗时比较长的操作
- 应 用
- 异步消息处理
- 外文名
- handler
-
Handler
2018-07-30 19:24:10Handler:作用就是发送与处理信息,如果希望Handle正常工作,在当前线程中要有一个Looper对象 Message:Handle接收与处理的消息对象 MessageQueue:消息队列,先进先出管理Message,在初始化Looper...子线程与UI主线程的通信在android中使用了消息机制来完成
消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息Handler类的主要作用有两个:
1.在新启动的线程中发送消息
2.在主线程中获取,处理消息即Handler的作用是把消息加入特定的Looper所管理的MessageQueue中,并处理该MessageQueue中Looper分发的Message。
一个线程只能有一个Looper,对应一个MessageQueue。
相关名词:
UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue
Handler:作用就是发送与处理信息,如果希望Handle正常工作,在当前线程中要有一个Looper对象
Message:Handle接收与处理的消息对象
MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue
Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理
在Android应用启动时,会自动创建一个线程,即程序的主线程,主线程负责UI的展示、UI事件消息的派发处理等等,因此主线程也叫做UI线程
如果有多个线程并发操作UI组件,就会出现线程安全问题,所以Android中制定了一个规则:在Android中只允许主线程(UI线程)修改Activity中的UI组件(子线程不允许操作主线程内的UI组件)
很多时候我们做界面刷新都需要通过Handler来通知UI组件更新
每个Handler都绑定了一个线程,Handler是Thread的代言人,是多线程之间通信的桥梁,通过Handler,我们可以在一个线程中控制另一个线程去做某事。
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待。由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理
-
Android Handler消息机制原理最全解读(持续补充中)
2018-05-13 19:22:57Handler 在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉 UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来...本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问。
Handler
在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来进行。那么这里就涉及到了如何将
子线程的数据传递给main thread呢?
Android已经为我们提供了一个消息传递的机制——Handler,来帮助我们将子线程的数据传递给主线程,其实,当熟悉了Handler的原理之后我们知道,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。
接下来,我们便详细的了解下Handler的原理及其使用。
首先看一下Handler最常规的使用方式:private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MESSAGE_WHAT: Log.d(TAG, "main thread receiver message: " + ((String) msg.obj)); break; } } }; private void sendMessageToMainThreadByWorkThread() { new Thread(){ @Override public void run() { Message message = mHandler.obtainMessage(MESSAGE_WHAT); message.obj = "I am message from work thread"; mHandler.sendMessage(message); } }.start(); } /* * 通常我们在主线程中创建一个Handler, * 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message, * 该参数就是我们从其他线程传递过来的信息。 * * 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例, * 我们来看看Message的几个属性: * Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源 * Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量 * Message.obj------------------->用来传递任何实例化对象 * 最后通过sendMessage将Message发送出去。 * * Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的来源呢?当然是通过what值啦。 * 怎么样很简单吧 */
文章的开头说过,Handler不仅仅是能过将子线程的数据发送给主线程,它适用于任意两个线程之间的通信。
下面我们来看下两个子线程之间如何进行通信的。
很简单啊,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage发送消息啊!
写程序可不能关说不练啊,我们把代码敲出来看一下!private Handler handler; private void handlerDemoByTwoWorkThread() { Thread hanMeiMeiThread = new Thread() { @Override public void run() { // Looper.prepare(); handler = new Handler() { @Override public void handleMessage(Message msg) { Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj)); Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show(); } }; // Looper.loop(); } }; Thread liLeiThread = new Thread() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Message message = handler.obtainMessage(); message.obj = "Hi MeiMei"; handler.sendMessage(message); } }; hanMeiMeiThread.setName("韩梅梅 Thread"); hanMeiMeiThread.start(); liLeiThread.setName("李雷 Thread"); liLeiThread.start(); /* * 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊! * 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。 * 只不过此处我们只发送一个消息,所以没有使用what来进行标记 * 运行看看,我们的李雷能拨通梅梅吗? * 啊哦,出错了 * 05-13 17:08:17.709 20673-20739/? E/AndroidRuntime: FATAL EXCEPTION: 韩梅梅 Thread Process: design.wang.com.designpatterns, PID: 20673 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) *Can't create handler inside thread that has not called Looper.prepare() * -----------》它说我们创建的handler没有调用Looper.prepare(); * 好的,我们在实例化Handler之前调用下该方法,看一下。加上是不是没有报错了呢。 * 等等,虽然没有报错,但是hanMeiMeiThread也没有接到消息啊,消息呢?别急。 * 我们在Handler实例化之后加上Looper.loop();看一看,运行一下,是不是收到消息了呢。 * 这是为什么呢? * 接下来我们就去看看Handler是怎么实现的发消息呢,弄清楚了原理,这里的原因也就明白了。 */ }
好了,卖了半天的关子,终于要开始真正的主题了。
首先我们来看下,为什么在子线程里实例化的时候不调用Looper.prepare()就会报错呢?//我们先来看看new Handler();时出错的原因。后续讲解源码分析只贴出关键部分。 //如下是Handler构造函数里抛出上文异常的地方,可以看到,由于mLooper对象为空才抛出的该异常。 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } /* 异常的原因看到了,接下来我们看看Looper.prepare()方法都干了些什么? */ public static void prepare() { prepare(true); } 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)); } /* 可以看到,该方法在当前thread创建了一个Looper(), ThreadLocal主要用于维护线程的本地变量, */ private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } //而Looper的构造函数里面又为我们创建了一个MessageQueue()对象。
了解到此,我们已经成功引出了Handler机制几个关键的对象了,Looper、MessageQueue、Message。
那么,肯定也有人又产生新的疑问了——为什么在主线程中创建Handler不需要要用Looper.prepare()和Looper.loop()方法呢?
其实不是这样的,App初始化的时候都会执行ActivityThread的main方法,我们可以看看ActivityThread的main()方法都做了什么?Looper.prepareMainLooper(); 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(); /* 真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper() 和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。 */
我们接着来看Handler发送消息的过程:
//调用Handler不同参数方法发送Message最终都会调用到该方法 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
sendMessage的关键在于enqueueMessage(),其内部调用了messageQueue的enqueueMessage方法
boolean enqueueMessage(Message msg, long when) { ... 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; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { 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; } /*从代码可以看出Message被存入MessageQueue时是将Message存到了上一个Message.next上, 形成了一个链式的列表,同时也保证了Message列表的时序性。 */
Message的发送实际是放入到了Handler对应线程的MessageQueue中,那么,Message又是如何被取出来的呢?
细心的朋友可能早早就发现了,之前抛出异常的地方讲解了半天的Loop.prepare()方法,一直没有说到Loop.loop()方法。同时,在之前的例子中也看到了,如果不调用Looper.loop()方法,Handler是接受不到消息的,所以我们可以大胆的猜测,消息的获取肯定和它脱不了关系!当然关怀疑还不行,我们还必须找出真相来证明我们的猜想?那还等什么,先看看loop()方法吧。public static void loop() { //可以看到,在调用Looper.prepare()之前是不能调用该方法的,不然又得抛出异常了 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(); for (;;) { 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; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } 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(); } } /* 这里我们看到,mLooper()方法里我们取出了,当前线程的looper对象,然后从looper对象开启了一个死循环 不断地从looper内的MessageQueue中取出Message,只要有Message对象,就会通过Message的target调用 dispatchMessage去分发消息,通过代码可以看出target就是我们创建的handler。我们在继续往下分析Message的分发 */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } /*好了,到这里已经能看清晰了 可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法 */ private static void handleCallback(Message message) { message.callback.run(); } //即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法,由于Handler在主线程创建,所以最终得以在主线程执行: runOnUiThread(new Runnable() { @Override public void run() { } }); public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } /* 而如果msg.callback为空的话, 存在一种情况,当创建Handler使用了带Callback的构造方法的话,将会执行Callback的handleMessgae方法,并且会根据其方法的返回值判断是否会被callback拦截, 是否需要继续执行Handle的handlMessgae方法,如果不存在Callback的拦截则Handler本身的handleMessage方法得意执行。(Handler(Callback)的构造方法使用场景暂时未遇到,欢迎补充) */
到这里,想必你应该清楚如何在不同的线程之间来使用Handler了吧。
最后总结一下:
- 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,如需了解ThreadLocal,点击查看详细讲解 ,
Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
但是只能有一个Looper和一个MessageQueue。 - Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。 - Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
然后通过handler将消息分发传回handler所在的线程。
最后附上一张自己理解画出来的流程图:
Handler补充:
1. Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。
为什么会出现内存泄漏问题呢?
首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的,
如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。- 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
- 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决的办法:
(1). 使用静态内部类+弱引用的方式:
静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用是不会造成对象该回收回收不掉的问题,不清楚的可以查阅JAVA的几种引用方式的详细说明。
private Handler sHandler = new TestHandler(this); static class TestHandler extends Handler { private WeakReference<Activity> mActivity; TestHandler(Activity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = mActivity.get(); if (activity != null) { //TODO: } } }
(2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。
@Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }
2. 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?
下面就通过代码来一探究竟
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
其实在在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。
接着看下sPool中缓存的Message是哪里来的?public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); } 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 = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。
recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)
总结:
由此可见,使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池,
在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。
3. Handler sendMessage原理解读。
引入问题!
- sendMessageDelayed是如何实现延时发送消息的?
- sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?
详细分析请移步下篇文章:Handler进阶之sendMessage原理探索
欢迎提供其他有关Handler的问题分享讨论
- 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,如需了解ThreadLocal,点击查看详细讲解 ,
-
从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)
2016-07-29 10:25:53Handler 每个初学Android开发的都绕不开Handler这个“坎”,为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部分人都...今天看到Handler.post这个方法之后决定再去翻翻源代码梳理一下Handler的实现机制转载请注明出处http://blog.csdn.net/ly502541243/article/details/52062179
Handler
每个初学Android开发的都绕不开Handler这个“坎”,为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部分人都是知其然却不知其所以然。今天看到Handler.post这个方法之后决定再去翻翻源代码梳理一下Handler的实现机制。
异步更新UI
先来一个必背口诀“主线程不做耗时操作,子线程不更新UI”,这个规定应该是初学必知的,那要怎么来解决口诀里的问题呢,这时候Handler就出现在我们面前了(AsyncTask也行,不过本质上还是对Handler的封装),来一段经典常用代码(这里忽略内存泄露问题,我们后面再说):
首先在Activity中新建一个handler:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: mTestTV.setText("This is handleMessage");//更新UI break; } } };
然后在子线程里发送消息:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络 mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
至此完成了在子线程的耗时操作完成后在主线程异步更新UI,可是并没有用上标题的post,我们再来看post的版本:
private Handler mHandler;//全局变量 @Override protected void onCreate(Bundle savedInstanceState) { mHandler = new Handler(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络 mHandler.post(new Runnable() { @Override public void run() { mTestTV.setText("This is post");//更新UI } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
从表面上来看,给post方法传了个Runnable,像是开了个子线程,可是在子线程里并不能更新UI啊,那么问题来了,这是怎么个情况呢?带着这个疑惑,来翻翻Handler的源码:
先来看看普通的sendEmptyMessage是什么样子:
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
将我们传入的参数封装成了一个消息,然后调用sendMessageDelayed:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
再调用sendMessageAtTime:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
好了,我们再来看post():
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是两种发送消息的不同之处 }
方法只有一句,内部实现和普通的sendMessage是一样的,但是只有一点不同,那就是 getPostMessage® 这个方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
这个方法我们发现也是将我们传入的参数封装成了一个消息,只是这次是m.callback = r,刚才是msg.what=what,至于Message的这些属性就不看了
Android消息机制
看到这里,我们只是知道了post和sendMessage原理都是封装成Message,但是还是不清楚Handler的整个机制是什么样子,继续探究下去。
刚才看到那两个方法到最终都调用了sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
这个方法又调用了 enqueueMessage,看名字应该是把消息加入队列的意思,点进去看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
mAsynchronous这个异步有关的先不管,继续将参数传给了queue的enqueueMessage方法,至于那个msg的target的赋值我们后面再看,现在继续进入MessageQueue类的enqueueMessage方法,方法较长,我们看看关键的几行:
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;
果然像方法名说的一样,一个无限循环将消息加入到消息队列中(链表的形式),但是有放就有拿,这个消息怎样把它取出来呢?
翻看MessageQueue的方法,我们找到了next(),代码太长,不赘述,我们知道它是用来把消息取出来的就行了。不过这个方法是在什么地方调用的呢,不是在Handler中,我们找到了Looper这个关键人物,我叫他环形使者,专门负责从消息队列中拿消息,关键代码如下:
for (;;) { Message msg = queue.next(); // might block ... msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); }
简单明了,我们看到了我们刚才说的msg.target,刚才在Handler中赋值了msg.target=this,所以我们来看Handler中的dispatchMessage:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
- msg的callback不为空,调用handleCallback方法(message.callback.run())
- mCallback不为空,调用mCallback.handleMessage(msg)
- 最后如果其他都为空,执行Handler自身的 handleMessage(msg) 方法
msg的callback应该已经想到是什么了,就是我们通过Handler.post(Runnable r)传入的Runnable的run方法,这里就要提提java基础了,直接调用线程的run方法相当于是在一个普通的类调用方法,还是在当前线程执行,并不会开启新的线程。
所以到了这里,我们解决了开始的疑惑,为什么在post中传了个Runnable还是在主线程中可以更新UI。
继续看如果msg.callback为空的情况下的mCallback,这个要看看构造方法:
1. public Handler() { this(null, false); } 2. public Handler(Callback callback) { this(callback, false); } 3. public Handler(Looper looper) { this(looper, null, false); } 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 5. public Handler(boolean async) { this(null, async); } 6. public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
具体的实现就只有最后两个,已经知道mCallback是怎么来的了,在构造方法中传入就行。
最后如果这两个回调都为空的话就执行Handler自身的handleMessage(msg)方法,也就是我们熟知的新建Handler重写的那个handleMessage方法。
Looper
看到了这里有一个疑惑,那就是我们在新建Handler的时候并没有传入任何参数,也没有哪里显示调用了Looper有关方法,那Looper的创建以及方法调用在哪里呢?其实这些东西Android本身已经帮我们做了,在程序入口ActivityThread的main方法里面我们可以找到:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ...
总结
已经大概梳理了一下Handler的消息机制,以及post方法和我们常用的sendMessage方法的区别。来总结一下,主要涉及四个类Handler、Message、MessageQueue、Looper:
新建Handler,通过sendMessage或者post发送消息,Handler调用sendMessageAtTime将Message交给MessageQueue
MessageQueue.enqueueMessage方法将Message以链表的形式放入队列中
Looper的loop方法循环调用MessageQueue.next()取出消息,并且调用Handler的dispatchMessage来处理消息
在dispatchMessage中,分别判断msg.callback、mCallback也就是post方法或者构造方法传入的不为空就执行他们的回调,如果都为空就执行我们最常用重写的handleMessage。
最后谈谈handler的内存泄露问题
再来看看我们的新建Handler的代码:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... } };
当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有Activity的引用。
而Handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕后发送消息去更新UI。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束。
另外,如果执行了Handler的postDelayed()方法,那么在设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
解决方法之一,使用弱引用:
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
聊技术 聊电影 聊人生 什么都聊的公众号Handler文章关联:
一个线程可以有几个Looper?几个Handler?从Looper.prepare()来看看关于Looper的一些问题
Thread、Handler和HandlerThread关系何在?
AsyncTask你真的用对了吗?
从Handler.postDelayed来看看Android怎么实现处理延时消息
尊重劳动成果,转载请注明出处http://blog.csdn.net/ly502541243/article/details/52062179
-
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
2014-08-07 09:17:40转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自【张鸿洋的博客】很多人面试肯定都被问到过,请问Android中的Looper , Handler , ...1、 概述Handler 、 Looper 、Mess转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自【张鸿洋的博客】
很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为大家从源码角度介绍3者关系,然后给出一个容易记忆的结论。
1、 概述
Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。说了这一堆,那么和Handler 、 Looper 、Message有啥关系?其实Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。
2、 源码解析
1、Looper
对于Looper主要是prepare()和loop()两个方法。
首先看prepare()方法public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(true)); }
sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例~相信有些哥们一定遇到这个错误。
下面看Looper的构造方法:
在构造方法中,创建了一个MessageQueue(消息队列)。private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread(); }
然后我们看loop()方法:public static void loop() { 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(); for (;;) { 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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); 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.recycle(); } }
第2行:
public static Looper myLooper() {
return sThreadLocal.get();
}
方法直接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。
第6行:拿到该looper实例中的mQueue(消息队列)
13到45行:就进入了我们所说的无限循环。
14行:取出一条消息,如果没有消息则阻塞。
27行:使用调用 msg.target.dispatchMessage(msg);把消息交给msg的target的dispatchMessage方法去处理。Msg的target是什么呢?其实就是handler对象,下面会进行分析。
44行:释放消息占据的资源。
Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。2、Handler
使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
14行:通过Looper.myLooper()获取了当前线程保存的Looper实例,然后在19行又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了。
然后看我们最常用的sendMessage方法
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
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) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
辗转反则最后调用了sendMessageAtTime,在此方法内部有直接获取MessageQueue然后调用了enqueueMessage方法,我们再来看看此方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
enqueueMessage中首先为meg.target赋值为this,【如果大家还记得Looper的loop方法会取出每个msg然后交给msg,target.dispatchMessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueueMessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。
现在已经很清楚了Looper会调用prepare()和loop()方法,在当前执行的线程中保存一个Looper实例,这个实例会保存一个MessageQueue对象,然后当前线程进入一个无限循环中去,不断从MessageQueue中读取Handler发来的消息。然后再回调创建这个消息的handler中的dispathMessage方法,下面我们赶快去看一看这个方法:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
可以看到,第10行,调用了handleMessage方法,下面我们去看这个方法:
可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
例如:
private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case value: break; default: break; } }; };
到此,这个流程已经解释完毕,让我们首先总结一下1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。
3、Handler post
今天有人问我,你说Handler的post方法创建的线程和UI线程有什么关系?
其实这个问题也是出现这篇博客的原因之一;这里需要说明,有时候为了方便,我们会直接写如下代码:
mHandler.post(new Runnable() { @Override public void run() { Log.e("TAG", Thread.currentThread().getName()); mTxt.setText("yoxi"); } });
然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
其实上面已经贴过代码,就是dispatchMessage方法:
第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
好了,关于Looper , Handler , Message 这三者关系上面已经叙述的非常清楚了。
最后来张图解:
希望图片可以更好的帮助大家的记忆~~
4、后话
其实Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。
new Thread() { private Handler handler; public void run() { Looper.prepare(); handler = new Handler() { public void handleMessage(android.os.Message msg) { Log.e("TAG",Thread.currentThread().getName()); }; };
Looper.loop(); }
Android不仅给我们提供了异步消息处理机制让我们更好的完成UI的更新,其实也为我们提供了异步消息处理机制代码的参考~~不仅能够知道原理,最好还可以将此设计用到其他的非Android项目中去~~最新补充:
关于后记,有兄弟联系我说,到底可以在哪使用,见博客:Android Handler 异步消息处理机制的妙用 创建强大的图片加载类
-
Handler 源码解析——Handler的创建
2018-08-18 16:07:15Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信。这是两个完全不同的概念。 Handler先进先出原则,Looper类用来管理特定线程内消息的交换(MessageExchange); 1... -
Handler用法及解析
2018-08-10 17:01:121.handler作用: 1)传递消息Message 2)子线程通知主线程更新ui 2.常用api 3.handler使用避免内存泄露 1)handler怎么使用会产生内存泄露? 2)如何避免handler的内存泄露? 3) 雷区 4.handlerThread... -
Android Handler异步通信:深入详解Handler机制源码
2018-05-21 10:08:17在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将手把手带你深入分析 Handler机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 定义 一套 Android 消息传递机制 作用 在多... -
Android handler 取消延时handler消息
2019-08-31 10:04:55Android handler 取消延时handler消息 myHandler.sendEmptyMessageDelayed(TEST, 30000);// 半分钟后发TEST(即msg.what) 如果想在未到半分钟的时候取消发送TEST,可以 myHandler.removeMessages(TEST); 这样... -
Handler完全解读——Handler的使用
2018-09-18 15:52:57Handler完全解读——Handler的使用 Handler是什么 Handler是Android给我们提供用于更新UI的一套机制,也是一套消息处理机制。我们用它可以发送消息,也可以用它处理消息。在Android开发中有着非常重要的地位。 为... -
Handler: 更新UI的方法
2019-07-23 15:35:02读这篇文章之前,假设你已经明白线程、Handler 的使用。 在文章的最后,附录一张草图,主要用于说明 Handler、Message、MessageQueue、Looper 之间的关系。 1. 在 onCreate() 方法中开启线程更新 UI public ... -
安卓Handler
2018-03-08 15:26:261.什么是Handler? Handler是安卓SDK中处理异步消息的核心类。 Handler的作用是让子线程通过与主线程通信来更新UI界面。 2.Handler的运行机制 创建一个Handler对象,系统就把Handler对象、UI线程和UI线程的消息... -
Handler源码分析
2021-03-01 00:13:41Handler Handler机制是子线程和主线程、子线程和子线程通信的一种方法,其中包括Looper、MessageQueue、Message、Handler。Handler将Message发送到MessageQueue中,Looper不停的轮询从MessageQueue中取出Message交给... -
Handler学习
2018-07-05 14:53:151 什么是Handler 以下内容摘自Android官方原文: A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread's {@link MessageQueue}. Each Handler ... -
springmvc拦截器 handler instanceof HandlerMethod false
2018-11-22 15:14:36新建拦截器,获取调用方法中的注解参数,由于handler的对应类型为Controller实例,所以 (handler instanceof HandlerMethod) == false,导致无法将 handler 转换为HandlerMethod类型,解决办法是在xxx-servlet.... -
Handler dispatch failed; nested exception is java.lang.StackOverflowError
2018-06-25 18:46:20springBoot项目遇到了“Handler dispatch failed; nested exception is java.lang.StackOverflowError”的错误。总结一哈:StackOverflowError通常情况下是死循环或者是循环依赖了。Caused by: java.lang.... -
解决Handler handler = new Handler之后出现publish、close等方法
2017-06-15 22:31:49今天声明Handler时,发现出错,alt+回车后出现三个不需要的方法: Handler handler = new Handler() { @Override public void publish(LogRecord record) { } @Override public void flush() { } -
Handler运行机制
2016-04-24 22:52:35Handler原理分析 -
stm32 各种类型错误:HardFault_Handler、MemManage_Handler、BusFault_Handler、UsageFault_Handler
2020-07-27 15:16:41STM32出现HardFault_Handler故障的原因主要有两个方面: 1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。 2、堆栈溢出。增加堆栈的大小。 MemManage_Handler:访问了内存... -
Android-Handler机制详解并自定义Handler
2018-06-26 15:12:06之前研究过Android的Handler机制,但是一直没有机会自己实现一次。最近又看到这个Handler机制,于是决定自己实现以下这个Handler机制。 首先,简单介绍以下Handler机制。 Handler机制在Android中通常用来更新UI:... -
如何使用Handler
2017-05-18 20:24:17什么是Handler?Handler可以发送和处理消息对象或Runnable对象,这些消息对象和Runnable对象与一个线程相关联。每个Handler的实例都关联了一个线程和线程的消息队列。当创建了一个Handler对象时,一个线程或消息队列... -
Handler基本使用(一) new Handler
2016-12-13 14:48:08通常我们使用如下方式获取一个Handler对象 private Handler mHandler1 = new Handler(){ public void handleMessage(Message msg) { }; }; private Handler mHandler2 = new Handler(new Handler.... -
Handler机制
2016-03-30 14:29:00Handler原理、使用 -
Handler/Handler.post引发内存泄漏
2019-02-18 20:54:01GC ROOT static HandlerCenter.mHandlerList-- Android ... handler.removeCallbacks(null)不起作用; 调用handler.removeCallbacksAndMessages(null);可以清空当前handler的所有回调和所有消息。想当然的... -
【Android】java Handler导致内存泄露分析
2019-12-17 21:51:42Handler导致内存泄露分析 有关内存泄露请猛戳内存泄露 Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // do something. } } 当我们这样创建Handler的时候Android ... -
Handler总结
2013-08-24 21:33:46全面介绍了Handler,post(Runnable runable). -
Handler详解
2016-03-11 11:03:13Handler、Message、异步消息处理 -
Kotlin 使用Handler
2020-06-21 12:13:24方式 A 网上普遍用法 ... val handler : Handler = object : Handler(){ override fun handleMessage(msg: Message?) { super.handleMessage(msg) when(msg?.what){ 9999 ->{ try { var t = msg.data.get(... -
Handler翻译
2016-05-22 00:09:08A Handler allows you to send and process Message and Runnable objects associated with a thread'sMessageQueue. Handler允许你去发送和处理与thread的MessageQueue有关的Message和Runnable对象 E