android--------Handler 消息传递机制
请看链接:https://zhuanlan.zhihu.com/p/29612923
小议Handler
本文的思维导图:
众所周知,Handler是Android中用来处理异步的类,为什么有时候可以直接使用子线程,而有时候要使用Handler呢?网上有很多教程讲解Handler,个人认为,很多教程都将Handler复杂化,学会Handler的使用是一件非常简单的事。
1、为什么需要Handler?
我们有这样一个需求,在界面中有一个TextView,当系统启动时,每隔一秒就更改里面的文字为:“当前值: ” + i,其中i是一个变量,每次自增1。要实现这个需求,我们很容易想到如下思路:创建一个子线程,在子线程中定义变量i,进行循环,每次循环中首先sleep(1000),然后再设置TextView对象的文本,示例代码如下:
1 new Thread() { 2 public void run() { 3 try { 4 for (int i = 0; i < 20; i++) { 5 Thread.sleep(1000); 6 tv.setText("当前的值为: " + i); 7 } 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 }; 12 }.start();
运行代码,则会在LogCat中出现如下错误:
1 android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这句话的意思是:只有创建View的线程才可以操作这个View对象。这是什么意思呢?试想一下:当一个Activity中创建了很多个子线程,每个子线程都要修改同一个控件的内容,这时候将会变得混乱。
2、Android的消息机制
为了解决这种子线程之间同步互斥的问题,Android的设计人员就规定:创建当前Activity的线程就是主线程,也称为UI线程;只有这个主线程可以更新界面中的内容。OK!如果子线程也要更新界面中的内容,此时应该怎么处理呢?Android中使用了消息机制。当子线程要对界面UI进行更新时,要发送一个消息给主线程,主线程中有一个消息队列,消息到达主线程之后便进入消息队列中。主线程通过一个轮循器,检查消息队列中是否有消息,如果有消息,主线程便会执行这个消息。而这种发送消息、处理消息便封装为一个对象,那就是Handler。
3、使用Handler
换句话说,子线程负责通过Handler对象的sendMessage()方法发送消息给主线程,而UI更新操作,则是由主线程调用Handler对象的handleMessage()方法来完成。
上面的需求就可以如下实现:
1 public class DemoActivity extends Activity { 2 private TextView tv = null; 3 // 定义Handler对象来发送和处理消息 4 private Handler handler = new Handler() { 5 // 主线程通过这个方法处理消息 6 @Override 7 public void handleMessage(Message msg) { 8 tv.setText("当前的值为:" + msg.obj); 9 } 10 }; 11 12 public void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 setContentView(R.layout.main); 15 tv = (TextView) this.findViewById(R.id.tv); 16 // 创建一个子线程 17 new Thread() { 18 public void run() { 19 try { 20 //子线程中只发送更新UI的消息,不进行更新操作 21 for (int i = 0; i < 20; i++) { 22 Thread.sleep(1000); 23 Message msg = new Message(); 24 msg.obj = i; 25 handler.sendMessage(msg); 26 } 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 }; 31 }.start(); 32 }
注意Handler中sendMessage()和handleMessage()两个方法的执行顺序:首先是创建了子线程,然后子线程调用handler的sendMesssage()方法向主线程发送消息,之后主线程收到消息,调用handler的handleMessage()方法来处理消息。
4、例子
模拟下载的进度条:
public class DemoActivity extends Activity { // 定义进度条的最大值和当前值 private int max = 100; private int num = 0; private ProgressBar pb = null; private TextView tv_progress = null; private Handler pbHandler = new Handler() { public void handleMessage(Message msg) { int num = (Integer) msg.obj; pb.setProgress(num); double rate = ((double)num) / max * 100; String text = "已下载:" + rate + "%"; if (num >= max) { text = "恭喜你,下载成功!"; } tv_progress.setText(text); }; }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); pb = (ProgressBar) this.findViewById(R.id.pb); pb.setMax(max); tv_progress = (TextView) this.findViewById(R.id.tv_progress); new MyThread().start(); } class MyThread extends Thread { public void run() { try { while (num < max) { Thread.sleep(1000); num += 5; Message msg = new Message(); msg.obj = num; pbHandler.sendMessage(msg); } } catch (InterruptedException e) { e.printStackTrace(); } }; } }
布局文件如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 <ProgressBar 7 android:id="@+id/pb" 8 style="?android:attr/progressBarStyleHorizontal" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" /> 11 <TextView 12 android:id="@+id/tv_progress" 13 android:textSize="20sp" 14 android:layout_width="fill_parent" 15 android:layout_height="wrap_content" 16 android:text="@string/hello" /> 17 18 </LinearLayout>
总结
1、 只有主线程才能更新UI;
2、 子线程如果需要更新UI,可以发送消息给主线程,让主线程更新;
3、 Handler可通过sendMessage()发送消息,通过handleMessage()处理消息。