【UI更新机制】handler

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,否则会抛出异常的。

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更新等。

2Message 消息,包含消息id,消息处理对象以及处理的数据。
        //由MessageQueue同意列队,终由Handle处理

3、MessageQueue 消息队列,存放Handler发送过来的Message
        //当然,存放Message并非实际意义的保存
        //而是将Message以链表的方法串联起来的,等待Looper的抽取

4、looper 消息泵,不间断的从MessageQueue消息队列中抽取消息。

5Thread:线程,负责调整整个消息循环,及消息循环的执行场所

简单的比喻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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值