【UI更新机制】handler
2016-09-08 17:57
316 查看
handler 是 android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息,如果不遵循这样的机制就无法更新UI信息的,就会抛出异常信息。
Handler主要作用
1.更新UI
2.进行消息处理
更新UI:指Activity中的View被设置了属性(通常被设置了属性就会有应相的效果展示在手机屏幕上,手机展示的效果通常突然改变又称做UI被更新)
例:手机界面mTextView中本来显示的文字是“你好”,但被点击(设置点击事件后)之后就变成你了“测试”我们就可以说UI被更新了。
onClick(){
mTextView.setText(“测试”);
}
注:更新UI通常用来和线程做比较,比如创建了一个线程,在里面写的代码有涉及更改了某View的属性时,就叫做在子线程(新开的线程)更新UI,会导致程序崩溃。
handler怎么用?(调用方法) (1)sendMessage (2)sendMessageDelayed (3)post(Runnable) (4)postDelayed(Runnable, long)
一个Handler实例其实绑定了一个关联的thread(线程)和那个线程的message queue(队列)
handler会把message和runnable对象发送到它所关联的message queue中去,同时它也会执行从message queue中出来的message和runnable
Handler的两大用途:
1. 在未来某个时间点执行message和runnable对象
2. 将要执行的action放到message queue中去,然后使得另外的不同的线程可以执行这个action。
Handler handler = new Handler( ); // 创建Handler对象
通过Handler在自定义的子线程中更新UI,否则会抛出异常的。
End 千万别忘了 .start()
第二章
基本概念
第三章
一、handler 封装了消息的发送,(主要包括消息发送给谁)
loper
1.内部包涵一个消息队列也就是MessageQueue,所有的handler发送的消息都走向这个消息队列
2.Loper.Loper 方法,就是一个死循环,不断地从 MessageQueue 获取消息,如果有消息就处理消息,没有消息就阻塞
二、MessageQueue,就是一个消息队列,可以添加消息,并处理消息
三、Handler 也很简单,内部会跟Looper进行关联 也就是说在Handler的内部可以找到Looper,找到了Looper也就找到了MessageQueue,在handler发送信息,其实就是向MessageQueue队列中发送消息
总结:handler负责发送消息、Looper负责接收handler发送的消息,并直接把消息回传给handler自己
MessageQueue就是一个存储消息的容器
为什么只设计了handler来解决更新ui的问题?
最根本的目的就是解决多线程并发问题,假设如果在一个Activity当中,有多个线程去更新ui,并且都没有加锁机制,那么就会出现更新界面错乱,但是如果都进行加锁处理的话,又会导致性能下降
因此,产生了这种handler的更新机制,根本不用去关心多线程的问题,所有的更新ui的操作,都是在主线程的消息队列当中去轮询处理的
创建一个与线程相关的Handler:
1. 在线程中通过Looper.prepare()方法创建一个与线程相关的Looper对象;
2. 在线程中通过Handler的new关键字,创建一个Handler对象,这个对象在创建的时候会关联上1中创建的Looper对象
3. 调用Looper对象的loop()方法去轮询它的MessageQueue
4. 通过其他的线程拿到这个线程的Handler对象之后调用sendMessage()之后,在这个线程中就可以进行Message的处理了。
我们一般是在主线程中创建Handler对象,在主线程中处理Message,在子线程中调用这个Handler对象的sendMessage()来发送message。所以Handler是在哪个线程创建就有哪个线程处理Message和轮询,而由别的线程负责给这个Handler发送Message。
每个线程都对应一个looper,在线程里面创建的handler默认都会与这个looper对应
一个线程可以有多个handler与唯一的一个looper对应,有且只有一个looper
这里主要是让我们熟悉在oncreat创建的handler与子线程创建handler
oncreate创建的handler他默认里面会有一个looper与之对应
所以我们自己在子线程中创建handler对象的时候我们应该要自主创建一个looper
为什么Handler需要和Looper关联,是因为Handler需要往Looper中的mQeueu里插入Message。所以,如果主线程需要和子线程之间通信,那有两个方法:
1. 主线程拥有子线程的Handler(注意:子线程的Handler需要关联自己的Looper),通过该Handler发送消息即可。
2. 主线程创建一个Handler,但是将子线程的Looper传递给Hander,这样Handler也是往子线程Looper对象的mQueue/队列里插入msg/消息,子线程Looper.loop自然可以拿到主线程消息了。
但是,这两种情况都有弊端。就是线程并发的时候,不能保证子线程的Handler或者Looper对象已经被初始化了。所以这个时候才需要用到HandlerThread
Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。
(1) Looper类用来为一个线程开启一个消息循环。
默认情况下android中新诞生的线程是没有开启消息循环的。
PS:主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。
Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
(2) 通常是通过Handler对象来与Looper进行交互的。
Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。
默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。
(3) 在非主线程中直接new Handler() 会报如下的错误: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare() 原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。
(4) Looper.loop(); // 让Looper开始工作,从消息队列里取消息,处理消息。
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
(5) 基于以上知识,可实现主线程给子线程(非主线程)发送消息。
把下面例子中的mHandler声明成类成员,在主线程通过mHandler发送消息
主线程和子线程之间如何相互通信:
1. 子线程向主线程发送消息,是通过调用主线程的Handler,发送信息给主线程的Looper,该Hnadler绑定了主线程的Looper。
2. 主线程向子线程发送消息,是通过调用子线程的Handler,发送信息给子线程的Looper,因此必须有子线程的Looper,为了防止Looper没有初始化,所以通过HandlerThread类,来保证子线程的Looper再被主线程调用时已经初始化。
子线程与主线程之间的相互通信
1.子线程通过 HandlerThread的thread.getLooper()绑定,
2.在主线程的handler的handlerMessage中调用threadHandler.sendMessageDelay(msg,1000); 向子线程发送消息。
3.在子线程中通过handler.sendMessageDelay(msg,1000);向主线程发送消息
4.在一个启动点btn调用主线程的handler.sendEmptyMessage(int x);
5.在一个结束点btn调用handler.removeMessages(x);
更新UI的4种方式(其实内部都是handler机制:
1.通过Handler的post方法();
2.调用Handler.sendMessage()或Handler.sendEmptyMessage()方法,传统的方法
3.重写Activity中的runOnUIThread方法更新;
4.调用View自身的post(Runnable run)方法更新;
非UI线程能否更新UI
—>刚启动的时候,立即在非UI线程更新->不报错。
—>休眠2s钟以后,更新—————–>报错
更新UI–>会调用checkForRelayout()方法
–>invalidate()方法–>invalidate(true)方法,
关注viewParent–>ViewRootImpl是ViewParent的实现类
—>p.invalidateChild()–>查看ViewRootImpl.invalidateChild()–>checkThread()方法–>判断UI线程是否是当前线程,不想等抛出异常。
ViewRootImpl是onResume()方法才会创建。所以onCreate()方法中要延迟才可以。
handleResumeActivity()方法—> viewManager.addView()–>ViewRootImpl初始化。,关注viewParent–>ViewRootImpl是ViewParent的实现类
总结:
1. 不能直接在非UI线程直接更新UI(大多数时候)
2. 每次创建 Handler 时需要给它绑定一个 looper,如果是主线程不给定具体的looper则会绑定默认的looper。
3. 子线程运行时一定要调用start()方法。
4. 在某些特殊情况下在非UI线程是可以更新UI的//但是!!不推荐使用(当刚启动Activity(即 onCreate 里面此时onResume方法还没有执行的时候)可以,因为在线程中更新UI时会调用 ViewParent.invalidateChild() 方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。
ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。
Handler主要作用
1.更新UI
2.进行消息处理
更新UI:指Activity中的View被设置了属性(通常被设置了属性就会有应相的效果展示在手机屏幕上,手机展示的效果通常突然改变又称做UI被更新)
例:手机界面mTextView中本来显示的文字是“你好”,但被点击(设置点击事件后)之后就变成你了“测试”我们就可以说UI被更新了。
onClick(){
mTextView.setText(“测试”);
}
注:更新UI通常用来和线程做比较,比如创建了一个线程,在里面写的代码有涉及更改了某View的属性时,就叫做在子线程(新开的线程)更新UI,会导致程序崩溃。
handler怎么用?(调用方法) (1)sendMessage (2)sendMessageDelayed (3)post(Runnable) (4)postDelayed(Runnable, long)
一个Handler实例其实绑定了一个关联的thread(线程)和那个线程的message queue(队列)
handler会把message和runnable对象发送到它所关联的message queue中去,同时它也会执行从message queue中出来的message和runnable
Handler的两大用途:
1. 在未来某个时间点执行message和runnable对象
2. 将要执行的action放到message queue中去,然后使得另外的不同的线程可以执行这个action。
Handler handler = new Handler( ); // 创建Handler对象
通过Handler在自定义的子线程中更新UI,否则会抛出异常的。
new Thread() { public void run() { try { Thread.sleep(1000); handler.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // 更新文本信息 // 此时直接在这里更新ui的时候,程序将会直接崩溃掉 textView.setText("更新了"); } }); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); //从上面的方法中我们可以知道,如果想直接更新ui,我们需要在Thread的run方法中通过 //handler.post()的方法来实现,而post方法中的参数是一个 new Runnable(){ public void run(){ // ...... } } // 然后我们在这个run方法中去更新ui,这才是正确的方法
End 千万别忘了 .start()
第二章
//1.使用Handler的sendMessage()和handleMessage() handleMessage(); private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { mTextView.setText("" + msg.arg1); }; }; //主线程中新建一个Thread发送message给主线程。 new Thread() { public void run() { // 不用new Message,而是复用下系统的handler.obtainMessage Message message = handler.obtainMessage(); message.arg1 = 88; handler.sendMessage(message); }; }.start(); /* ↑↑↑↑↑Message对象可以通过handler.obtainMessage()方法获得 ,这个方法是Handler类的一个用于优化Message对象复用的方法 ,它的底层代码是判断程序中是否有一个Message对象 ,即if(message == null),如果为null,则会创建一个Message对象 ,否则直接将程序中有的Message对象复用给message。 */ /* 最终导致同一个handler对象的handleMessage()方法被创建Handler对象的线程 (一般在主线程中创建)回调,然会这个传入的msg会被当成参数传handleMessage()方法的。 这个时候我们就可以对Message对象做一些处理了,而且这个时候我们所处的线程一般就在UI线程中了 ,所以这个时候我们更新UI就没有问题了。 */ //复用系统的message对象 Message msg = handler.obtainMessage(); Message.obtain(); msg.sendtoTarget();// 也可以发送到Handler对Message进行处理 msg.obj=xxx;// 可以传递一个对象; //2. 移除runnable对象 //对应于 handler.postDelayed(myRunnable, 2000); //直接 handler.removeCallbacks(myRunnable); //3. 截获message /*在创建handler对象时,调用new Handler(Callback callback)方法 可以在handler(Callback的接口中进行截获) 在handlerMessage方法中返回值为true时,消息会被拦截, 不执行handler中的handlerMessage方法。*/ private Handler handler = new Handler(new Callback(){ @Override public boolean handleMessage(Message msg){ Toast.makeText(getApplicationContext(),"" + 1, 1).show(); return true;// return true就截获,return false不截获 } }){ public void handleMessage(Message msg){ Toast.makeText(getApplicationContext(),"" + 2, 1).show(); } }; //在主线程中执行 handler.sendEmptyMessage(1); /*原理 当Handler收到message时,新创建的Callback将首先执行,其handleMessage()将会处理消息; 如果Callback的handleMessage()不截获消息,然后Handler才可能会收到消息。*/
基本概念
1、handler 消息处理器,负责处理消息。 //使用Handle时,需要实现handleMessage(Message msg)方法 //对特定的Message进行处理,例如对UI更新等。 2、Message 消息,包含消息id,消息处理对象以及处理的数据。 //由MessageQueue同意列队,终由Handle处理 3、MessageQueue 消息队列,存放Handler发送过来的Message //当然,存放Message并非实际意义的保存 //而是将Message以链表的方法串联起来的,等待Looper的抽取 4、looper 消息泵,不间断的从MessageQueue消息队列中抽取消息。 5、Thread:线程,负责调整整个消息循环,及消息循环的执行场所 简单的比喻looper就是水泵,MessageQueue储水的池塘,Message就是水,Handler就是操作的人。
第三章
一、handler 封装了消息的发送,(主要包括消息发送给谁)
loper
1.内部包涵一个消息队列也就是MessageQueue,所有的handler发送的消息都走向这个消息队列
2.Loper.Loper 方法,就是一个死循环,不断地从 MessageQueue 获取消息,如果有消息就处理消息,没有消息就阻塞
二、MessageQueue,就是一个消息队列,可以添加消息,并处理消息
三、Handler 也很简单,内部会跟Looper进行关联 也就是说在Handler的内部可以找到Looper,找到了Looper也就找到了MessageQueue,在handler发送信息,其实就是向MessageQueue队列中发送消息
总结:handler负责发送消息、Looper负责接收handler发送的消息,并直接把消息回传给handler自己
MessageQueue就是一个存储消息的容器
为什么只设计了handler来解决更新ui的问题?
最根本的目的就是解决多线程并发问题,假设如果在一个Activity当中,有多个线程去更新ui,并且都没有加锁机制,那么就会出现更新界面错乱,但是如果都进行加锁处理的话,又会导致性能下降
因此,产生了这种handler的更新机制,根本不用去关心多线程的问题,所有的更新ui的操作,都是在主线程的消息队列当中去轮询处理的
创建一个与线程相关的Handler:
1. 在线程中通过Looper.prepare()方法创建一个与线程相关的Looper对象;
2. 在线程中通过Handler的new关键字,创建一个Handler对象,这个对象在创建的时候会关联上1中创建的Looper对象
3. 调用Looper对象的loop()方法去轮询它的MessageQueue
4. 通过其他的线程拿到这个线程的Handler对象之后调用sendMessage()之后,在这个线程中就可以进行Message的处理了。
我们一般是在主线程中创建Handler对象,在主线程中处理Message,在子线程中调用这个Handler对象的sendMessage()来发送message。所以Handler是在哪个线程创建就有哪个线程处理Message和轮询,而由别的线程负责给这个Handler发送Message。
每个线程都对应一个looper,在线程里面创建的handler默认都会与这个looper对应
一个线程可以有多个handler与唯一的一个looper对应,有且只有一个looper
这里主要是让我们熟悉在oncreat创建的handler与子线程创建handler
oncreate创建的handler他默认里面会有一个looper与之对应
所以我们自己在子线程中创建handler对象的时候我们应该要自主创建一个looper
1. Handler在创建的时候可以指定Looper,这样通过Handler的sendMessage()方法 发送出去的消息就会添加到指定Looper里面的MessageQueue里面去。在不指定Looper 的情况下Handler绑定的是创建它的线程的Looper。如果这个线程的Looper不存在,程 序将抛出"Can't create handler inside thread that has not called Looper.prepare()" // 抛出异常、 2. HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别 就在于,它自带一个封装好了的Looper成员变量。在其run()方法中,调用 Looper.myLooper()获得一个looper对象。 3. HandlerThread的用处 创建Handler时指定的looper,也可以是别的线程创建的。 所以Handler中MessageQueue的轮询不一定非要在创建Handler的线程进行 ,还可以在别的线程中进行。 这个时候我们就需要使用HandlerThread这个类的Looper,这样消息的处理就在新创建 的HandlerThread中进行。模拟异步处理,主线程给子线程发送消息,在子线程中处理 比较耗时的操作。 //为新创建的线程指定一个名字,HandlerThread线程独有 mThread = new HandlerThread("Handler Thread"); mHandler = new Handler(mThread.getLooper()){ public void handleMessage(android.os.Message msg) {... }; };
为什么Handler需要和Looper关联,是因为Handler需要往Looper中的mQeueu里插入Message。所以,如果主线程需要和子线程之间通信,那有两个方法:
1. 主线程拥有子线程的Handler(注意:子线程的Handler需要关联自己的Looper),通过该Handler发送消息即可。
2. 主线程创建一个Handler,但是将子线程的Looper传递给Hander,这样Handler也是往子线程Looper对象的mQueue/队列里插入msg/消息,子线程Looper.loop自然可以拿到主线程消息了。
但是,这两种情况都有弊端。就是线程并发的时候,不能保证子线程的Handler或者Looper对象已经被初始化了。所以这个时候才需要用到HandlerThread
Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。
(1) Looper类用来为一个线程开启一个消息循环。
默认情况下android中新诞生的线程是没有开启消息循环的。
PS:主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。
Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
(2) 通常是通过Handler对象来与Looper进行交互的。
Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。
默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。
mainHandler = new Handler() //等价于↓↓↓↓↓↓↓ new Handler(Looper.myLooper()). Looper.myLooper() //获取当前进程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。
(3) 在非主线程中直接new Handler() 会报如下的错误: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare() 原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。
(4) Looper.loop(); // 让Looper开始工作,从消息队列里取消息,处理消息。
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
(5) 基于以上知识,可实现主线程给子线程(非主线程)发送消息。
把下面例子中的mHandler声明成类成员,在主线程通过mHandler发送消息
主线程和子线程之间如何相互通信:
1. 子线程向主线程发送消息,是通过调用主线程的Handler,发送信息给主线程的Looper,该Hnadler绑定了主线程的Looper。
2. 主线程向子线程发送消息,是通过调用子线程的Handler,发送信息给子线程的Looper,因此必须有子线程的Looper,为了防止Looper没有初始化,所以通过HandlerThread类,来保证子线程的Looper再被主线程调用时已经初始化。
1.创建主线程的handler,并向子线程发送消息: //主线程的handler Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { Message message = new Message(); //向子线程发送消息 threadHandler.sendMessageDelayed(message, 1000); }; }; 2.创建子线程的handler,向主线程发送消息,要关联一个threadHandler, threadHandler.getLooper()得到一个looper对象 HandlerThread handlerThread = new HandlerThread("handler thread"); handlerThread.start();//不要忘记调用start方法! //子线程的handler threadHandler = new Handler(handlerThread.getLooper()){ @Override public void handleMessage(Message msg) { //处理消息 Message message = new Message(); handler.sendMessageDelayed(message, 1000); } };
子线程与主线程之间的相互通信
1.子线程通过 HandlerThread的thread.getLooper()绑定,
2.在主线程的handler的handlerMessage中调用threadHandler.sendMessageDelay(msg,1000); 向子线程发送消息。
3.在子线程中通过handler.sendMessageDelay(msg,1000);向主线程发送消息
4.在一个启动点btn调用主线程的handler.sendEmptyMessage(int x);
5.在一个结束点btn调用handler.removeMessages(x);
更新UI的4种方式(其实内部都是handler机制:
1.通过Handler的post方法();
2.调用Handler.sendMessage()或Handler.sendEmptyMessage()方法,传统的方法
3.重写Activity中的runOnUIThread方法更新;
4.调用View自身的post(Runnable run)方法更新;
非UI线程能否更新UI
—>刚启动的时候,立即在非UI线程更新->不报错。
—>休眠2s钟以后,更新—————–>报错
更新UI–>会调用checkForRelayout()方法
–>invalidate()方法–>invalidate(true)方法,
关注viewParent–>ViewRootImpl是ViewParent的实现类
—>p.invalidateChild()–>查看ViewRootImpl.invalidateChild()–>checkThread()方法–>判断UI线程是否是当前线程,不想等抛出异常。
ViewRootImpl是onResume()方法才会创建。所以onCreate()方法中要延迟才可以。
handleResumeActivity()方法—> viewManager.addView()–>ViewRootImpl初始化。,关注viewParent–>ViewRootImpl是ViewParent的实现类
总结:
1. 不能直接在非UI线程直接更新UI(大多数时候)
2. 每次创建 Handler 时需要给它绑定一个 looper,如果是主线程不给定具体的looper则会绑定默认的looper。
3. 子线程运行时一定要调用start()方法。
4. 在某些特殊情况下在非UI线程是可以更新UI的//但是!!不推荐使用(当刚启动Activity(即 onCreate 里面此时onResume方法还没有执行的时候)可以,因为在线程中更新UI时会调用 ViewParent.invalidateChild() 方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。
ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。
相关文章推荐
- DDMS测试工具之UI检查
- 后台管理UI的选择
- 设置UITextView的时候文字居中显示了?
- Swagger-UI 基于WebAPI文档描述插件
- truecrpyt加密头信息被破坏
- 让UITableView自动滑动(定位)到某一行cell
- Android环信EaseUI的快速集成
- android原始sqlite中query的复杂用法
- vue-cli构建项目教程
- 并行编译 Xoreax IncrediBuild
- 关于Request
- webpack+vue + express (hot) 热启动调试简单配置
- RequireJS和AMD规范
- IOS TableView的Cell高度自适应,UILabel自动换行适应(转载)
- Vue.js初步入门
- STM32+uCOS-II+uc/GUI移植 (uC/GUI API函数学习一)
- iOS UILabel设置行间距和字间距并计算高度
- Strategy_Requirement2
- JS模块化工具requirejs教程(二):基本知识
- JS模块化工具requirejs教程(一):初识requirejs