Android学习@Handler的应用
笔者:unirithe
日期:11/09/2021
参考资料:
多线程
两种方式创建并启动线程
- 方式一: 继承
java.lang.Thread并重写run()
class myThread extends Thread{
// 成员变量
private int num;
myThread(int num){
this.num = num;
}
@Override
public void run(){
//...
}
}
// 启动线程
new myThread(10).start();
- 方式二:实现
Runnable接口
class myThread implements Runnable{
private int num;
myThread(int num){
this.num = num;
}
@Override
public void run(){
//...
}
}
// 启动线程
new Thread(new myThread(10)).start();
Android的消息传递机制
Handler
Handler : 线程之间传递消息的即时接口,生产线程和消费线程调用以下操作使用Handler:
- 在消息队列创建、插入或移除消息
- 在消费线程中处理消息
每个Handler有一个与之相关的 Looper 和消息队列MessageQueue。
同一线程的多个Handler分享一个同样的消息队列,因为他们分享的Looper对象是同一个。
关于Handler的构造函数官方文档
| 函数名 | 描述 |
|---|---|
| Handler() | 已弃用 |
| Handler(Handler.Callback callback) | 已弃用 |
| Handler(Looper looper) | 使用提供的looper对象 |
| Handler(Looper looper, Handler.Callback callback) | 使用提供的looper对象和回调接口来处理消息 |
在构造函数中,looper对象可以通过Looper.myLooper() 来创建一个默认的
Message
Message 是容纳任意数据的容器,是Handler接收与处理的消息对象。
生产消息发送消息给Handler,Hadnler将消息加入到消息队列中。
该对象提供三种信息,以便Handler和消息队列处理时使用:
- what 一种标识符,Hander能使用它区分不同消息,从而采取不同的处理方法
- time 告知消息队列何时处理消息
- target 表示哪一个Handler 应当处理消息
该对象的创建方式有:
Handler.obtainMessage();
Handler.obtainMessage(int what);
Handler.obtainMessage(int what, Object obj);
Handler.obtainMessage(int what, int arg1, int arg2);
Handler.obtainMessage(int what, int arg1, int arg2, Object obj);
通过Bundle对象设置消息的数据
public void setData (Bundle data)
MessageQueue
消息队列(为方便阅读,简写为MQ),以FIFO方式管理所有的Message对象。
是一个LinkedList集合
- 在初始化
Looper对象时会创建一个与之关联的MQ - 每个线程最多只有一个MQ
- MQ通常是由
Looper管理 - 主线程创建时,会创建一个默认的
Looper对象,从而自动创建一个MQ
Looper
负责从消息队列MessageQueue中读取消息,然后分发给对应的Handler处理。
每个线程只能管理一个Looper。
使用范例:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper()) {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
AtomicBoolean
作为 java.util.concurrent.atomic 包下的一个类,指原子性的Boolean类型
该类实现的是在多线程环境下,具有排他性,不会被打断,常用方法:
| 方法名 | 描述 |
|---|---|
| get() | 获取当前的值返回true或false |
| set(Boolean nueValue) | 设置新的布尔值 |
构造方法:initialValue(Boolean b)
案例
倒计时
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
class TimeCountThread extends Thread{
private Handler handler;
private int timecount;
public TimeCountThread(Handler handler, int timecount){
this.handler = handler;
this.timecount = timecount;
}
@Override
public void run() {
super.run();
for(int i = timecount; i >= 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("Now", i);
message.setData(bundle);
handler.sendMessage(message);
}
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.TextView);
Handler handler = new Handler(Looper.myLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
tv.setText(message.getData().getInt("Now") + "");
return false;
}
});
new TimeCountThread(handler, 100).start();
}
}
Handler使用总结
首先在AppCompatActivity.java类中定义一个handler,传入两个参数,第一个是looper,第二个是Callback()接口的实现类,
该接口方法实现的是当线程接收到消息message时做出的响应,在倒计时的案例里就是更新TextView,即显示当前的数字。
其次需要实现继承于Thread的线程类,其成员变量有
Handler在多线程进行时,每次需要发送消息,消息需要通过Handler的sendMessage()方法timecount表示目前的倒计时数
该类通过实现run()方法,方法内容主要是发送当前的倒计时数到Handler的消息中,依此进行消息传递。
综上,Handler作为线程之间传递消息的接口,实现步骤为:
- App主类负责编写handler对象接收到消息后的处理逻辑
- 线程类负责存储handler对象,以及在run方法里编写向Handler传递消息的具体实现
计时器

layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0.00" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<Button
android:id="@+id/b_start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginHorizontal="10px"
android:text="Start"
/>
<Button
android:id="@+id/b_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:layout_marginHorizontal="10px"
android:text="Stop" />
</LinearLayout>
</LinearLayout>
TimerCounterThread.java
package com.example.ycc_task4_1;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.util.concurrent.atomic.AtomicBoolean;
public class TimerCounterThread extends Thread{
public static final String KEY_TIME_COUNTER = "key_time_counter";
private Handler handler;
private AtomicBoolean isRunning;
private float timeCount=0.0f;
public TimerCounterThread(Handler handler) {
this.handler = handler;
isRunning = new AtomicBoolean(false);
}
@Override
public void run() {
super.run();
isRunning.set(true);
while(isRunning.get()){
try{
Thread.sleep(100);
timeCount += 0.10f;
sendCounter();
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
private void sendCounter(){
Message mes = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putFloat(KEY_TIME_COUNTER, timeCount);
mes.setData(bundle);
handler.sendMessage(mes);
}
public void stopTimer(){
isRunning.set(false);
}
}
MainActivity.java
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.concurrent.atomic.AtomicBoolean;
public class MainActivity extends AppCompatActivity {
Button b_start, b_stop;
TextView tv;
TimerCounterThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
b_start = findViewById(R.id.b_start);
b_stop = findViewById(R.id.b_stop);
tv = findViewById(R.id.tv_result);
// 创建 Handler
final Handler handler = new Handler(Looper.myLooper(), new Handler.Callback(){
@Override
public boolean handleMessage(@NonNull Message message) {
Bundle data = message.getData();
float timeCounter = data.getFloat(TimerCounterThread.KEY_TIME_COUNTER);
tv.setText(String.format("%.2f", timeCounter));
return false;
}
});
b_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
thread = new TimerCounterThread(handler);
thread.start();
b_start.setEnabled(false);
b_stop.setEnabled(true);
}
});
b_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
thread.stopTimer();
b_start.setEnabled(true);
b_stop.setEnabled(false);
}
});
}
}
获取网页内容
首先需要在项目的 mainifests/AndroidMainifest.xml 里的 <application>标签添加
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
允许项目进行网络访问

layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="https://www.baidu.com" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Get Web" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_ack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
</LinearLayout>
</ScrollView>
</LinearLayout>
JsoupGetThread.java
package com.example.ycc_task4_2;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
public class JsoupGetThread extends Thread{
private static final String HTML_ACK = "html_ack";
private String url;
private Handler handler;
public JsoupGetThread(String url, Handler handler) {
this.url = url;
this.handler = handler;
}
public static String getMessage(Message mes){
Bundle data = mes.getData();
return data.getString(HTML_ACK);
}
@Override
public void run() {
String ack;
try{
Document doc = Jsoup.connect(url).timeout(10000).get();
ack = doc.toString();
} catch (IOException e){
e.printStackTrace();
ack = e.toString();
}
Message mes = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString(HTML_ACK, ack);
mes.setData(bundle);
handler.sendMessage(mes);
}
}
MainActivity.java
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
TextView tv = findViewById(R.id.tv_ack);
Handler handler = new Handler(new Handler.Callback(){
@Override
public boolean handleMessage(@NonNull Message message) {
String s = JsoupGetThread.getMessage(message);
tv.setText(s);
return false;
}
});
EditText et = findViewById(R.id.editText);
Button bt = findViewById(R.id.button);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String url = et.getText().toString();
new JsoupGetThread(url, handler).start();
}
});
}
}

浙公网安备 33010602011771号