Task 4-1 Thread
题目:
Make an app with 2 TextViews and 2 Buttons.
The 1st TextView shows your name and student number.
When users click the start Button, a thread runs in background and count the timer with the period of 10ms. The thread send a Message to the main UI thread by handler when the timer count updated, and the 2nd TextView shows the timer count. The timer count can be sent by the Message’s arg1 variable.
When users click the stop Button, the thread stops, and the TextView shows the last counter of the timer.
Hint:
- the running status in the Thread can be taken by AtomicBoolean;
- the timer count can be realized by Thread.sleep() function;
- the Handler defined in main UI can be the field of the user defined Thread class and passed by constructor.
演示
分析与步骤
1、Thread多线程,主要有两种是实现方法,一种是继承多线程,一种是实现Runnable接口,两种都要重写run方法,启动线程可以用run()也可以用start(),主要区别是:使用run方法是直接在主线程里面进行的,而使用start()方法则是另外开辟个线程调用
2、Handler类:主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。简而言之就是实现获取obtainMessage
和发送sendMessage
消息,值得注意的是,message一旦发送就会在定义的地方产生回调对消息进行处理
3、Bundle类:用于携带数据,类似于Map,使用键值对的方式来存放数据,其内部实际上是使用Hash Map类型来存放,可以存放各种类型的数据setString
,setFloat
等,通过key来获取,
注意:在回调的函数获取消息时,是先创建一个bundle来存放数据,然后通过bundle.getxxx(key)来获取具体的数据
4、AtomicBoolean原子变量的使用:由于start和stop两个按钮共用一个线程,因此使用AtomocBoolean会使得线程更加的安全,原子变量控制状态变化,类似于加锁的操作,只能使用set和get方法来获取和设置,原子变量在构造器中被初始化为false,当为true的时候可以运行
5、两个按钮的设置:由于两个按钮应该是互斥的存在,因此在一个按钮可以点击时(Enabled:“true”),另一个应该无法被点击(Enabled:“false”),避免线程发生崩溃
6、线程每0.1s休眠一次,此时将计数器数字增加0.1
7、注意:handler的包应该是:import android.os.Handler;
代码
MainActivity
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
Button bt_start,bt_stop;
TextView tv;
Handler handler;
TimeCounterThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_main);
bt_stop=findViewById(R.id.bt_stop);
bt_start=findViewById(R.id.bt_start);
tv=findViewById(R.id.tv);
final Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
Bundle data = msg.getData();
float timecount = data.getFloat(TimeCounterThread.KEY_TIME_COUNT);
tv.setText(String.format("%.2f",timecount));
return false;
}
});
bt_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread=new TimeCounterThread(handler);
thread.start();
bt_start.setEnabled(false);
bt_stop.setEnabled(true);
}
});
bt_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread.stopTimer();
bt_start.setEnabled(true);
bt_stop.setEnabled(false);
}
});
}
}
TimeCounterThread
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.util.concurrent.atomic.AtomicBoolean;
public class TimeCounterThread extends Thread {
private Handler handler;
private AtomicBoolean isRunning;
public static final String KEY_TIME_COUNT="key_time_count";
public float timecounter =0.0f;
public TimeCounterThread(Handler handler) {
this.handler = handler;
this.isRunning=new AtomicBoolean(false);
}
@Override
public void run() {
super.run();
isRunning.set(true);
while(isRunning.get()){
try {
Thread.sleep(100);
timecounter+=0.10f;
sendMessage();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void sendMessage() {
Bundle bundle = new Bundle();
bundle.putFloat(KEY_TIME_COUNT,timecounter);
Message msg = handler.obtainMessage();
msg.setData(bundle);
handler.sendMessage(msg);
}
public void stopTimer(){
isRunning.set(false);
}
}