handler之复习

1.什么是handler
handler的涉及的设计模式?
Android中更新UI的几种方式? 非UI线程真的不能更新UI吗?
handler的注意事项?
Handler与Looper、MessageQueue 的关系,
handler的原理和过程?
handler的基本使用
Handler与子线程:如何实现一个线程相关的Handler?HandlerThread是什么? 如何在主线程给子线程发送消息呢?
什么是Handler,如何传递Message,传递Runnable 对象,传递Callback对象?
android为什么要设计只能用过Handler机制更新UI呢?
使用handler时候遇到的问题?

1.什么是handler
其实Google参考了Windows的消息处理机制,在Android系统实现一套类似的消息处理机制。
Handler 是Android提供来更新UI的一套机制,也是一套消息处理的机制,可以发送消息和处理消息。
Android 在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样机制就没有办法更新UI信息,就会抛出异常信息。

2.handle相关类概念
Message:消息,理解为线程间通讯的数据单元。如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程
Message Queue 消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
Looper循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Queue里面的Message,并交付给相应的Handler进行处理。
线程UI thread通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
到此,这个流程已经解释完毕,首先总结一下
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的设计模式?
1)享元设计模式?
产生一个Message对象,可以new Message(),也可使用Message.obtain()方法;

复用系统的message对象 即Message msg =handler.obtainMessage();
更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new重新分配内存。

1)Handler引起的内存泄露原因以及最佳解决方案?
Handler允许发送延时消息,如果在延时期间用户关闭了Activity,那么该Activity会泄露。
这个泄露是因为Message会持有Handler,而又因为Java的特性,内部类会持有外部类,使得Activity会被Handler持有,
这样最终就导致 Activity 泄露。
解决:将Handler定义成静态的内部类,在内部持有Activity 的弱引用,
并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。

2)为什么我们能在主线程直接使用Handler,而不需要创建Looper ?
通常认为ActivityThread就是主线程。事实上它并不是一个线程,而是主线程操作的管理者。
在ActivityThread.main()调用Looper.prepareMainLooper()创建主线程的Looper,并且调用loop(),所以就可以直接使用 Handler 了。
因此我们可以利用Callback这个拦截机制来拦截Handler的消息。如大部分插件化框架中Hook ActivityThread.mH的处理。

3)handler主线程的死循环一直运行是不是特别消耗CPU资源呢?
并不是,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。
这里采用epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,
本质是同步I/O,即读写是阻塞的。
所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

4.Looper:
1)handler中Looper里的ThreadLocal?
定义:线程内部的数据存储类
作用:负责存储和获取本线程的Looper

ThreadLocal的原理
ThreadLocal是一个关于创建线程局部变量的类。
在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。
使用场景如下:
1)实现单个线程单例以及单个线程上下文信息存储,比如交易id等。
2)实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例。
承载一些线程相关的数据,避免在方法中来回传递参数。
当需要使用多线程时,有个变量恰巧不需要共享,此时就不必使用synchronized麻烦的关键字来锁住,
每个线程都相当于在堆内存中开辟一个空间,线程中带有对共享变量的缓冲区,通过缓冲区将堆内存中的共享变量进行读取和操作,
ThreadLocal相当于线程内的内存,一个局部变量,每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与主内存中的变量进行交互。
并不会像synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。
ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。

2)Looper主要作用:

a、与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
b、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

3)Looper中最为重要的两个方法:
Looper.prepareMainLooper():该方法是Looper对象的初始化
Looper.loop(): 该方法会循环取出Message,Queue的Message,将取出的Message交付给相应的Handler(Looper的作用就体现在这里)
loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
每一个线程里只能有一个Looper对象以及一个MessageQueue数据结构,可以有多个handler。
在Handler构造函数创建一个Looper对象,Looper对象是通过Looper.myLooper()创建,同时还会创建一个MessageQueue,而这个MessageQueue是从Looper中获取的。
意味着Handler和Looper共用一个消息队列,此时Handler,Looper以及MessageQueue已经捆绑到一起了。
class Looper {
//存放线程的容器类,为确保获取的线程和原来的一样
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
...
}
通过Looper类得知
1)在Looper类的构造函数,创建Looper同时也会创建一个消息队列
2)Looper内部是通过ThreadLocal存放和获取线程中的Looper的,确保每个线程获取到的looper都是唯一的。
在创建Looper对象前先会去判断ThreadLocal中是否已经存在Looper对象,
如果不存在就新创建一个Looper对象并且存放ThreadLocal中。
还注意是在Looper创建的同时MessageQueue消息队列也被创建完成,这样的话Looper中就持有了MessageQueue对象。
3)//这个方法是给系统调用的,UI线程通过调用这个线程,从而保证UI线程里有一个Looper
//注意:如果一个线程是UI线程,那么myLooper和getMainLooper是同一个Looper
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}

4) looper相关的问题:Handler工作就必须在当前线程中有一个Looper对象, 因为在Handler构造函数需要一个Looper对象。
if (mLooper == null) {
Handler是必须在有Looper的线程上执行,这个也就是为什么我在HandlerThread中初始化Handler
//而没有在Thread里面初始化,如果在Thread里面初始化需要先调用Looper.prepare方法
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()"); }
Handler的作用有两个—发送消息和处理消息,我们在使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue;
否则就无法进行消息循环。而MessageQueue是由Looper负责管理的,
创建一个Handler时候,handler = new Handler()时,handler需要用到Looper,不然就会出错,没有拿到Looper对象,
原因:当创建Handler handler = new Handler()时没有关联Looper。
1.2)在一个子线程中去创建一个Handler,那么如何保障当前线程中一定有Looper对象呢?
分两种情况:
(1)在主UI线程中,系统已经初始化好了一个Looper对象,因此直接创建Handler并使用即可。
为什么Handler可以在主线程中直接可以使用呢?
因为主线程(UI线程)的Looper在应用程序开启时创建好了,即在ActivityThread.main方法中创建的,该函数为Android应用程序的入口
public static void main(String[] args) {
...
Process.setArgV0("<pre-initialized>");
//1. 创建消息循环Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
//2. 执行消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
(2)在子线程中创建一个Handler,必须手动去创建一个Looper对象,并且去启动它,才可用Handler进行消息发送与处理。
class childThread extends Thread{
public Handler mHandler;
@Override
public void run() {
//子线程中必须先创建Looper
Looper.prepare();
mHandler =new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
}
}
一.常见的问题?
1.Handler一定要在主线程实例化吗? new Handler()和new Handler(Looper.getMainLooper())的区别?
如果你不带参数的实例化:Handler handler = new Handler(),那么这个会默认用当前线程的looper。
一般而言,如果你的Handler是要来刷新操作UI的,那么就需要在主线程下跑。
情况:
1.要刷新UI,handler要用到主线程的looper。
那么在主线程 Handler handler = new Handler();,
如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());
2.不用刷新ui,只是处理消息。
当前线程如果是主线程的话,Handler handler = new Handler();
不是主线程的话,Looper.prepare(); Handler handler = new Handler();Looper.loop();
或者Handler handler = new Handler(Looper.getMainLooper());
若是实例化的时候用Looper.getMainLooper()就表示放到主UI线程去处理。
如果不是的话,因为只有UI线程默认Loop.prepare();Loop.loop();其他线程需要手动调用这两个,否则会报错。

2.message.what,message.arg1,message.arg2,message.obj,他们在之间有什么区别呢?
what就是一般用来区别消息的,比如你传进去的时候msg.what = 3;
然后处理的时候判断msg.what == 3是不是成立的,是的话,表示这个消息是干嘛干嘛的(自己能区别开)
至于arg1,arg2,其实也就是两个传递数据用的,两个int值,看你自己想要用它干嘛咯。如果你的数据只是简单的int值,那么用这两个,比较方便。
其实这里你还少说了个,setData(Bundle),上面两个arg是传递简单int的,这个是传递复杂数据的。
msg.obj呢,这个就是传递数据了,msg中能够携带对象,在handleMessage的时候,可以把这个数据取出来做处理了。不过呢,如果是同一个进程,最好用上面的setData就行了,这个一般是Messenger类来用来跨进程传递可序列化的对象的,这个比起上面的来,更消耗性能一些。
http://www.cnblogs.com/xpxpxp2046/archive/2012/04/13.html
两篇不错的文章:
http://www.cnblogs.com/xpxpxp2046/archive/2012/04/13/2445395.html
http://www.cnblogs.com/xpxpxp2046/archive/2012/04/13/2445355.html

3. Handler用法
Handler一些常用方法:
void handleMessage(Message msg):处理消息的方法,该方法通常会被重写。
final boolean hasMessages(int what):检测消息队列中是否包含what属性为指定值的消息。
Message obtainMessage():获取消息的方法,此函数有多个重载方法。
sendEmptyMessage(int what):发送空消息。
final boolean sendEmptyMessageDelayed(int what , long delayMillis):指定多少毫秒后发送空消息。
final boolean sendMessage(Message msg):立即发送消息。
final boolean sendMessageDelayed(Message msg ,long delayMillis):指定多少毫秒后发送消息。
final boolean post(Runnable r):执行runnable操作。
final boolean postAtTime(Runnable r, long upTimeMillis):在指定时间执行runnable操作。
final boolean postDelayed(Runnable r, long delayMillis):指定多少毫秒后执行runnable操作。
3.1 传递message用于接受子线程发送的数据,并用此数据配合主线程更新UI。
有以下方法:post类方法允许你排列一个Runnable对象到主线程队列中。
post(Ruannable);
postAtTime(Runnable, long);
postDelayed(Runnable long);
3.2 传递Runnable对象 用于通过Handler绑定的消息队列,安排不同操作的执行顺序,主要有以下方法:
sendEmptyMessage(int);
sendMessage(Message);
sendMessageAtTime(Message,long);
sendMessageDelayed(Message, long);
sendMessage类方法,允许你安排一个带数据的Message对象到队列中,等待更新。
使用 Handler 在子线程中向UI线程发送一个消息进行UI的更新创建一个Message,
Message msg = new Message();
msg.arg1 = 88;
msg.obj = xxx;
handler.sendMessage(msg);
可以传递一个对象 当然不一定要用new一个 Message,也可以复用系统的message对象Message msg =handler.obtainMessage();
3.3 传递Callback对象Callback 用于截获 handler发送的消息,如果返回true就截获成功不会向下传递。
public Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "HandleMessage 1").show();
return true;
}})
{
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "handleMessage 1",
Toast.LENGTH_SHORT).show();
};
}}
上面的示例中,第一个有返回值的handlerMessage方法是 Callback 的回调,
如果返回true,则不执行下面的handlerMessage方法,从而达到拦截handler发送的消息的目的,
如果返回 false,则会继续执行handlerMessage 方法。

posted on 2023-02-15 12:51  左手指月  阅读(26)  评论(0编辑  收藏  举报