Android 消息传递 - Handler
Android 为了线程安全,不允许我们在 UI 线程外操作 UI
但是,经常,我们需要 UI 线程之外刷新界面,比如异步下载完成要更新 UI,比如数据下载成功后要通知 UI 数据已经刷新,需要重绘
这样的情况下,要怎么做呢?
不用着急
我们有很多方式可以达到异步更新 UI 的目的,比如 runOnUiThread()
,或者更高级的 事务总线
当然了,如标题所言,我们这章节只讲 Android 提供了 Handler
Handler
Android 提供了 android.os.Handler
类用于通知 UI 更新,当然了,它能做的事不止这些
我们先来看看 Handler
的执行流程
名词 | 说明 |
---|---|
UI 线程 | 就是主线程,系统在创建 UI 线程的时候会初始化一个 Looper 对象,同时也会创建一个与其关联的 MessageQueue; |
Handler | 作用就是发送与处理信息,如果希望 Handler 正常工作,在当前线程中要有一个 Looper 对象 |
Message | Handler接收与处理的消息对象 |
MessageQueue | 消息队列,先进先出管理 Message,在初始化 Looper 对象时会创建一个与之关联的 MessageQueue |
Looper | 每个线程只能够有一个 Looper,管理 MessageQueue,不断地从中取出 Message 分发给对应的 Handler 处理 |
因此,如果子线程想修改 Activity 中的 UI 组件,就需要新建一个 Handler 对象
然后通过这个对象向主线程发送信息,而发送的信息会先到主线程的 MessageQueue
进行等待,由 Looper
按先入先出顺序取出,再根据 message
对象的 what
属性分发给对应的 Handler
进行处理
Handler 方法
方法 | 说明 |
---|---|
void handleMessage(Message msg) | 处理消息的方法,一般都会被重写 |
sendEmptyMessage(int what) | 发送空消息 |
sendEmptyMessageDelayed(int what,long delayMillis) | 指定延时多少毫秒后发送空信息 |
sendMessage(Message msg) | 立即发送信息 |
sendMessageDelayed(Message msg) | 指定延时多少毫秒后发送信息 |
boolean hasMessage (int what) | 检查消息队列中是否包含 what 属性为指定值的消息如果是参数为 (int what,Object object),除了判断 what 属性,还需要判断 Object 属性是否为指定对象的消息 |
Handler 的使用
Handler 的应用场景有两种
- Handler 写在主线程中
- Handler 写在子线程中
下面我们就分别对不同的场景写一个范例来演示一下如何使用
Handler 写在主线程中
主线程中,系统已经初始化了一个 Looper 对象
我们可以直接创建 Handler 对象,然后就可以进行信息的发送与处理
范例
我们通过 Timer定时器,定时修改 ImageView 显示的内容,从而形成帧动画
-
创建一个 空的 Android 项目
cn.twle.android.HandlerMessage
-
下载 /static/i/android/tuzi.zip 并把所有的图片拖到
res/drawable
目录下 -
修改
activity_main.xml
添加一个ImageView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <ImageView android:id="@+id/tuzi" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/tuzi_1" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> </RelativeLayout>
-
修改 MainActivity.java
package cn.twle.android.handlermessage; import android.support.v7.app.AppCompatActivity; import android.widget.ImageView; import android.os.Handler; import android.os.Message; import android.os.Bundle; import java.util.TimerTask; import java.util.Timer; public class MainActivity extends AppCompatActivity { private ImageView tuzi; //定义切换的图片的数组id int imgids[] = new int[]{ R.drawable.tuzi_1, R.drawable.tuzi_2, R.drawable.tuzi_3, R.drawable.tuzi_4, R.drawable.tuzi_5, R.drawable.tuzi_6, R.drawable.tuzi_7, R.drawable.tuzi_8, R.drawable.tuzi_9, R.drawable.tuzi_10, R.drawable.tuzi_11, R.drawable.tuzi_12 }; int imgstart = 0; final Handler myHandler = new Handler() { @Override //重写 handleMessage 方法,根据 msg 中 what的 值判断是否执行后续操作 public void handleMessage(Message msg) { if(msg.what == 0x123) { tuzi.setImageResource(imgids[imgstart++ % 12]); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tuzi = (ImageView) findViewById(R.id.tuzi); //使用定时器,每隔 100 毫秒让 handler 发送一个空信息 new Timer().schedule(new TimerTask() { @Override public void run() { myHandler.sendEmptyMessage(0x123); } }, 0,200); } }
运行效果如下
Handler 写在子线程中
如果将 Handler 写在子线程中的,那就要我们自己创建一个 Looper
对象
-
调用
Looper.prepare()
方法为当前线程创建Looper
对象,而它的构造器会创建配套的MessageQueue
-
创建
Handler
对象,重写handleMessage()
方法处理来自于其它线程的信息 -
调用
Looper.loop()
方法启动 Looper
范例
输入一个数,计算后通过 Toast 输出在这个范围内的所有质数
-
创建一个 空的 Android 项目
cn.twle.android.HandlerMessage
-
修改
activity_main.xml
添加一个输入框和一个按钮<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/etNum" android:inputType="number" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入上限"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="cal" android:text="计算"/> </LinearLayout>
-
修改
MainActivity.java
package cn.twle.android.handlermessage; import android.os.Looper; import android.support.v7.app.AppCompatActivity; import android.os.Handler; import android.os.Message; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { static final String UPPER_NUM = "upper"; EditText etNum; CalThread calThread; // 定义一个线程类 class CalThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { // 定义处理消息的方法 @Override public void handleMessage(Message msg) { if(msg.what == 0x123) { int upper = msg.getData().getInt(UPPER_NUM); List<Integer> nums = new ArrayList<Integer>(); // 计算从2开始、到upper的所有质数 outer: for (int i = 2 ; i <= upper ; i++) { // 用i处于从2开始、到i的平方根的所有数 for (int j = 2 ; j <= Math.sqrt(i) ; j++) { // 如果可以整除,表明这个数不是质数 if(i != 2 && i % j == 0) { continue outer; } } nums.add(i); } // 使用Toast显示统计出来的所有质数 Toast.makeText(MainActivity.this , nums.toString() , Toast.LENGTH_LONG).show(); } } }; Looper.loop(); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etNum = (EditText)findViewById(R.id.etNum); calThread = new CalThread(); // 启动新线程 calThread.start(); } // 为按钮的点击事件提供事件处理函数 public void cal(View source) { // 创建消息 Message msg = new Message(); msg.what = 0x123; Bundle bundle = new Bundle(); bundle.putInt(UPPER_NUM , Integer.parseInt(etNum.getText().toString())); msg.setData(bundle); // 向新线程中的Handler发送消息 calThread.mHandler.sendMessage(msg); } }
运行效果如下