安卓编程 多线程与Handler消息传递(附案例 计时器)

安卓编程 多线程与Handler消息传递

介绍

在安卓编程中,许多时候都要使用到多线程,一般超过几毫秒的功能都应该放在后台线程去运行.
在此之前要先了解清楚线程进程的概念.
进程: 每个APP都是一个进程
在这里插入图片描述
线程: 如微信APP中聊天界面,包含了许多线程
在这里插入图片描述
下图为微信聊天界面中的主线程与子线程
在这里插入图片描述

入门

多线程的创建有如下两种方式
在这里插入图片描述
在这里插入图片描述
两种方式都可以创建一个子线程,并且都要重写run方法,这个线程要做的事情都写在run方法里面,一但执行完run方法,线程就消亡了,有可能被Java内存回收,所以调用线程的时候最好使用start方法而不是run方法
这里要注意 run方法和start方法的区别:

  • start: 另外开辟一个子线程执行任务
  • run 在当前线程直接执行任务

为什么要使用Handler:

案例1: 显示下一句

在很多APP中,我们需要通过子线程来修改主UI,比如点击下一句按钮的时候修改TextView的内容
在这里插入图片描述

子线程直接操作主线程的错误案例

在安卓编程的线程安全性,子线程不允许直接修改主线程的内容
在这里插入图片描述
下面就通过代码来观察一下如果直接通过子线程修改主线程会发生什么

布局文件显示如下
在这里插入图片描述
为了实现点击按钮的时候使用新的线程来修改TextView的内容,在Button的点击事件中,我增加了如下代码

new Thread(new Runnable() {
    @Override
    public void run() {
        tv.setText("我中过最惊喜的彩券");
    }
}).start();

下面就运行运行程序查看一下效果
在这里插入图片描述
可以看到一点击按钮程序就炸了
在这里插入图片描述
其中有报错如下
Only the original thread that created a view hierarchy can touch its views.
只有创建这个view的线程才能操作这个view

这就验证了之前的结论,子线程不能直接修改主线程中的内容,而想要达成目的,我们只能借助android提供的Handler消息机制

Handler消息机制实现功能

Handler相当于一个中转站,子线程点击按钮后先告诉Handler, 然后由Handler传递消息给主线程,修改其中的UI组件
在这里插入图片描述

  • 每个Handler实例都与单个线程和该线程的消息队列相关联。当您创建一个新的Handler时,它被绑定到创建它的线程的线程/Messagequeue–从那时起,它将把消息和可运行的消息传递到消息队列中,并在消息队列中出现时执行它们
  • Handler通过Message对象来传递信息
    • 在这里插入图片描述
    • 可以通过obtain方法获取到Handler中的Message
    • what参数可以用来设置用户自定义的标识(下文第一个案例就使用了what参数)
    • setData(Bundle data) 更加灵活,通过Bundle对象可以设置更多类型的数据(下文第二个案例将使用Bundle)

下面会结合案例更加易懂的解释一下Handler的使用,上面的参数先了解一下即可

注意: 创建的时候一定要使用android自带的Handler包
在这里插入图片描述
修改一下我的代码,增加Handler来传递信息,观察一下结果

        final Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                if (msg.what == 1) {
                    tv.setText("我中过最惊喜的彩券");
                    button.setText("");
                }
                return false;
            }
        });
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        handler.sendEmptyMessage(1);
                    }
                }).start();
            }
        });

成功完成了子线程修改主UI的目的,没有任何报错
在这里插入图片描述

案例一解析

这里再解释一下Handler消息机制是如何传递消息的

  • 首先创建一个Handler对象,由于这个对象创建在MainActicity中创建,因此它被绑定到了MainActivity所在的线程(主线程上)
  • Handler的Callback 回调函数相当于一个循环,当它监听到了有子线程执行了sendMessage的操作时就会被触发,如上文的小案例
    在这里插入图片描述
    因为可以有很多个子线程会发送消息,我不知道TextView要更新文字属于哪个子线程的消息,所以要设置一个判断

本案例中我使用的是发送一个空消息,设置what参数的方式来发送消息
这里的what参数相当于一个标识符,使得接收方能正确的接收到消息
如下图,发送消息的线程中设置的 what=1与接收消息时候的what==1相对于,起到了一个用户自定义标识符的作用

在这里插入图片描述

案例一代码

布局文件的代码

<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        android:gravity="center"
        android:textColor="#000"
        android:text="错误案例: 子线程直接修改主线程" />
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:text="在我遇见你以前\n也拥有过完整的睡眠"
        android:layout_margin="50dp"
        android:padding="20dp"
        android:textSize="30sp"
        android:textColor="#3F51B5">
    </TextView>

    <Button
        android:id="@+id/button"
        android:layout_width="200dp"
        android:layout_gravity="center"
        android:layout_height="wrap_content"
        android:background="#2196F3"
        android:textColor="#fff"
        android:textSize="18sp"
        android:text="下一句" />

</LinearLayout>

MainActivity的代码

package com.example.review41;

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.TextView;

public class MainActivity extends AppCompatActivity {
    Button button;
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        tv = findViewById(R.id.tv);
        final Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                if (msg.what == 1) {
                    tv.setText("我中过最惊喜的彩券");
                    button.setText("");
                }
                return false;
            }
        });
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        handler.sendEmptyMessage(1);
                    }
                }).start();
            }
        });
    }
}

案例2 计时器

在这里插入图片描述

Bundle对象

Bundle主要用于传递数据;它保存的数据,是以key-value(键值对)的形式存在的。

因为Handler自身不能传递多样类型的数据,所以要借助Bundle来传递数据

整套的流程如下

  • 创建Bundle
  • 设置Bundle的数据
  • 将Bundle 使用 setData的方法添加到Message对象中
  • 使用handler的sendMessage方法发送数据

完成计时器同样需要多线程,这里我创建了一个类来继承Thread,注意初始化类的时候需要传入handler对象,才可以在正确的线程之间达到数据传输的效果


计时器的思路很简单,比之前的点击显示下一句多了个资源的互斥访问,当点击开始按钮的时候,启用循环不停的发送消息,完成秒数的增加和显示,而点击暂停按钮的时候停止秒数增加
这里就使用了一个AtomicBoolean对象来起到互斥访问的作用
计时器的核心代码如下,这里的isRunning是一个AtomicBoolean类型的对象,相当于一个资源信号量,通过它可以完成互斥访问的效果,在初始化类的时候被创建,在run方法中始终为true,只有当其他线程,如stop按钮启用的线程将它设置为fasle的时候计时才会停止

public void run() {
        isRunning.set(true);
        while (isRunning.get()) {
            try {
                Thread.sleep(10);
                clock += 0.01f;
                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putFloat("time", clock);
                message.setData(bundle);
                handler.sendMessage(message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        super.run();
    }
代码
package com.example.review41;

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.TextView;

public class MainActivity extends AppCompatActivity {
    Handler handler;
    TextView textView;
    Button btn_start, btn_stop;
    ClockThred thred;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv);
        btn_start = findViewById(R.id.btn_start);
        btn_stop = findViewById(R.id.btn_stop);
        handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                Bundle data = msg.getData();
                Float clock = data.getFloat("time");
                textView.setText(String.format("%.2f", clock));
                return false;
            }
        });
        btn_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thred.stopClock();
                btn_stop.setEnabled(false);
                btn_start.setEnabled(true);
            }
        });
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thred = new ClockThred(handler);
                thred.start();
                btn_stop.setEnabled(true);
                btn_start.setEnabled(false);
            }
        });
    }
}

package com.example.review41;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

import java.util.concurrent.atomic.AtomicBoolean;

public class ClockThred extends Thread {
    private Handler handler;
    private Float clock = 0.00f;
    private AtomicBoolean isRunning;

    public ClockThred(Handler handler) {
        this.handler = handler;
        this.isRunning= new AtomicBoolean(false);
    }
    public void stopClock(){
        this.isRunning.set(false);
    }
    @Override
    public void run() {
        isRunning.set(true);
        while (isRunning.get()) {
            try {
                Thread.sleep(10);
                clock += 0.01f;
                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putFloat("time", clock);
                message.setData(bundle);
                handler.sendMessage(message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        super.run();
    }
}

<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        android:gravity="center"
        android:textColor="#000"
        android:text="计时器" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Your Name And Id" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="20sp"
        android:text="0.00" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp">

        <Button
            android:id="@+id/start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始" />

        <Button
            android:id="@+id/stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="暂停" />
    </LinearLayout>
</LinearLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Joker-Tong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值