参考资料

  • 官方介绍文档
  • Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
  • Android异步消息处理机制完全解析,带你从源码的角度彻底理解
  • 慕课网课程-Android面试常客Handler详解

如果在非UI线程中更新UI会出现问题吗?

实践:

public class MainActivity extends AppCompatActivity {    @BindView(R.id.id_tv)    TextView idTv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        new Thread() {            @Override            public void run() {                try {                    Thread.sleep(1000*5);                    idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }.start();    }}

我们运行项目就会以下异常

非UI线程中更新UI出现

为了更直观的看到报错原因,我们找到源码ViewRootImpl的checkThread方法,看它做了些什么。

ViewRootImpl.checkThread()

但是Android为什么要这样搞呢?

我们仔细看这句话,只有创建了View的线程才能对这个View进行操作。而我们一般View都是为了显式在UI上的。Android正是为了防止我们在非UI线程去操作这些UI上的控件,才加了限制的。因为UI体验对用户来说是最直观的,如果谁都有权限去操作一下,那UI要么很乱,要么控制很复杂。

竟然Android是不允许我们在非UI线程中去执行更新UI,那我们要怎么解决这个问题呢?那我们就要使用Android 提供的Hander机制去更新UI了

一、什么是Handler

Handler是Android提供的用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息。

二、为什么要使用Handler

Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息,就会抛出异常。

三、Handler怎么用呢?

  • 在非UI线程借助Handler.post(Runnable)更新UI

首先在Activity中实例化一个Hander

Handler handler = new Handler();

然后在子线程中调用Handler.post(Runnable)更新UI
详细的代码如下:

public class MainActivity extends AppCompatActivity {    @BindView(R.id.id_tv)    TextView idTv;    Handler handler = new Handler();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        new Thread() {            @Override            public void run() {                try {                    Thread.sleep(1000*5);//                    idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");                    handler.post(new Runnable() {                        @Override                        public void run() {                            idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");                        }                    });                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }.start();    }}
  • 在非UI线程借助Handler.postDelayed(Runnable, DelayTime)定时执行相关动作

栗子:实现三张图片自动切换

实现三张图片自动切换
 private  int images[]= {R.drawable.image4,R.drawable.image6,R.drawable.image7};     private int index=0;Handler handler = new Handler();Thread myThread = new Thread(){        @Override        public void run() {            index++;            index = index%3;            System.out.println(index);            idImg.setImageResource(images[index]);            handler.postDelayed(myThread,1000);        }    };handler.postDelayed(myThread,1000);

要是我们想停止图片的切换,那要如何操作呢?

  • Handler移除一个消息
handler.removeCallbacks(myThread);
  • 自定义Handler和Message

自定义Hander(customHander)

Handler customHander = new Handler() {       @Override       public void handleMessage(Message msg) {           idTv.setText("msg.arg1>"+msg.arg1+ "\nmsg.arg2>" +msg.arg2 +"\nmsg.obj>"+((Dog)msg.obj).toString());       }   };

实体类

package com.javen205.entity;import java.io.Serializable;public class Dog implements Serializable{    private String name;    private int  age;    public Dog() {    }    public Dog(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "Dog{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

使用只定义Hander(customHander)发送消息

Dog dog=new Dog("萨摩耶",1);Message message = new Message();                message.arg1 = 1;                message.arg2 = 2;                message.obj = dog;                customHander.sendMessage(message);               

复用Message

 Dog dog=new Dog("萨摩耶",1);//                Message message = new Message();                Message message= customHander.obtainMessage();                message.arg1 = 1;                message.arg2 = 2;                message.obj = dog;                customHander.sendMessage(message);

原理:obtainMessage()方法做了哪些操作呢?

 /**     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).     *  If you don't want that facility, just call Message.obtain() instead.     */    public final Message obtainMessage()    {        return Message.obtain(this);//this 就是Hander本身    }

我们看看Message.obtain(Handler h) 又做了哪些操作呢?

 /**     * Same as {@link #obtain()}, but sets the value for the target member on the Message returned.     * @param h  Handler to assign to the returned Message object's target member.     * @return A Message object from the global pool.     */    public static Message obtain(Handler h) {        Message m = obtain();        m.target = h;         return m;    }

target就是Hander自己,指消息要发送给谁

再来看看obtain()方法

/**     * Return a new Message instance from the global pool. Allows us to     * avoid allocating new objects in many cases.     */    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();    }

这个方法就是跟我们平常new Message 对象一样 ,只不过是在之前添加了一判断,判断系统中是否存在空的Message,如果存在就直接返回否则就创建一个Message对象。

其实发送消息也可以这样玩,不使用直接使用Hander而是使用Message的sendToTarget()方法,代码如下。

 Dog dog=new Dog("萨摩耶",1);//                Message message = new Message();                Message message= customHander.obtainMessage();                message.arg1 = 1;                message.arg2 = 2;                message.obj = dog;//                customHander.sendMessage(message);                message.sendToTarget();

我们来看看Message.sendToTarget()方法源码

 /**     * Sends this Message to the Handler specified by {@link #getTarget}.     * Throws a null pointer exception if this field has not been set.     */    public void sendToTarget() {        target.sendMessage(this);    }

上面提到过target就是Hander自己,其实就是调用Hander自己的一个sendMessage,跟我们普通发送Message没有什么区别只是里面封装了一个target,本质还是调用了Hander.sendMessage(...)

  • Handler 消息拦截使其接收者接收不到消息

自定义Handler(interceptHander)拦截消息

Handler interceptHander = new Handler(new Handler.Callback() {        @Override        public boolean handleMessage(Message msg) {            System.out.println("is intercept Handler>"+msg.what);            // 设置true拦截消息            return true;        }    }){        @Override        public void handleMessage(Message msg) {            System.out.println("is intercept Handler");        }    };

interceptHander发送一个消息Message

 interceptHander.sendEmptyMessage(1);

四、Handler几种发送消息方式之间区别

可以参考:Handler发送sendMessage和postRunnable的区别

五、Handler的原理是什么?

handler原理图
  • Handler封装了消息的发送:内部会跟Looper关联

  • Looper(消息封装的载体):内部包含一个消息队列(MessageQueue),所有Handler发送的消息都会走向这个消息队列;
    Looper.Looper方法是一个死循环,不断的从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。

  • MessageQueue(消息队列):可以添加消息,并处理消息

总结:Handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给Handler自己(handleMessage),MessageQueue就是一个存储消息的容器。

六、自定义一个与线程相关的Handler

1、线程中创建一个Looper 可以使用 Looper.prepare();方法
2、实例化一个Handler
3、调用Looper.loop();方法循环处理消息

public class HandlerActivity extends AppCompatActivity {    private MyThread myThread;    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            System.out.println("UI :"+Thread.currentThread());        }    };    class MyThread extends Thread{        public Handler handler;        @Override        public void run() {            Looper.prepare();            handler= new Handler(){                @Override                public void handleMessage(Message msg) {                    System.out.println("currentThread:"+Thread.currentThread());                }            };            Looper.loop();        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_handler);        myThread = new MyThread();        myThread.start();        try {            Thread.sleep(1000*5);        } catch (InterruptedException e) {            e.printStackTrace();        }        myThread.handler.sendEmptyMessage(1);        handler.sendEmptyMessage(1);    }}

输出结果:

System.out: currentThread:Thread[Thread-151,5,main]System.out: UI :Thread[main,5,main]

七、非UI线程真的不能更新UI吗?

我们运行下面的代码测试:

 @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        new Thread() {            @Override            public void run() {                idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");            }        }.start();    }

最开始的案例中我们去掉Thread.sleep(...)会正常运行吗?答案:可正常运行。是不是感觉有点奇怪呢?那为什么直接在Activity的onCreate中添加子线程可以直接更新UI呢?

详细解答: 为什么我们可以在非UI线程中更新UI

八、Handler异步消息处理(HandlerThread)

Android HandlerThread 完全解析

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

一个简单的例子

public class HandlerThreadActivity extends AppCompatActivity {    private Handler handler;    private HandlerThread thread;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_handler_thread);        thread = new HandlerThread("Handler Thread");        thread.start();        handler = new Handler(thread.getLooper()){            @Override            public void handleMessage(Message msg) {                // 处理耗时操作               System.out.println("current thread>"+Thread.currentThread());            }        };        handler.sendEmptyMessage(1);    }}

测试输出结果
I/System.out: current thread>Thread[Handler Thread,5,main]

九、如何在主线程给子线程发送消息

public class HandlerThreadActivity extends AppCompatActivity {    @BindView(R.id.id_btn1)    Button idBtn1;    @BindView(R.id.id_btn2)    Button idBtn2;    private Handler threadhandler;    private HandlerThread thread;    Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            System.out.println("UI thread>" + Thread.currentThread());            // 给主线程发送消息            Message message = new Message();            message.what =1;            threadhandler.sendMessageDelayed(message, 1000);        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_handler_thread);        ButterKnife.bind(this);        thread = new HandlerThread("Handler Thread");        thread.start();        threadhandler = new Handler(thread.getLooper()) {            @Override            public void handleMessage(Message msg) {                // 处理耗时操作                System.out.println("current thread>" + Thread.currentThread());                // 给主线程发送消息                Message message = new Message();                message.what =1;                handler.sendMessageDelayed(message, 1000);            }        };//        threadhandler.sendEmptyMessage(1);    }    @OnClick({R.id.id_btn1, R.id.id_btn2})    public void onClick(View view) {        switch (view.getId()) {            case R.id.id_btn1:                handler.sendEmptyMessage(1);                break;            case R.id.id_btn2:                handler.removeMessages(1);                threadhandler.removeMessages(1);                break;        }    }}

十、Android中更新UI的几种方式

  • handler sendMessage
  • runOnUIThread
  • handler post
  • view post

项目源码地址:https://github.com/Javen205/Hander

推荐阅读
AndroidStudio多渠道打包
Android依赖管理与私服搭建
Android Studio 上传aar(Library)到JCenter
Android版-支付宝APP支付
Android版-微信APP支付
支付宝Wap支付你了解多少?
一张二维码集成微信、支付宝支付

安利时间:
JPay是对微信App支付、支付宝App支付的二次封装,对外提供一个相对简单的接口以及支付结果的回调

极速开发微信公众号是对微信公众平台接口的二次封装。包括开发者模式、事件回调监听、微信模板消息、微信客服消息、自定义菜单、微信支付、素材管理等

如遇到问题欢迎留言交流

更多相关文章

  1. Android(安卓)AsyncTask源码简单分析
  2. Android开发之SurfaceView
  3. Android(安卓)studio爬取网页
  4. Android的线程使用来更新UI----Thread、Handler、Looper、TimerT
  5. Android(安卓)LocationManager 使用
  6. android 发送邮件
  7. AOSP和Chromium的Android(安卓)WebViewTest
  8. Android(安卓)更新UI的两种方法——handler和runOnUiThread()
  9. Android消息处理机制

随机推荐

  1. Android消息推送简介
  2. [android|ViewPager]关于ViewPager如何阻
  3. android中make命令
  4. Android布局管理器 - 详细解析布局实现
  5. 标题:[资讯]注意!Android惊爆两安全漏洞
  6. Android保存List>数据到SD卡及读取
  7. 3G之Android学习第一章节>
  8. Android(安卓)学习笔记之 Activity 简介
  9. Android(安卓)Bitmap 缓存策略
  10. Android——UI篇:Android监听back按键动作