お題 2:スレッド処理の実装
2-1. イメージ
下記のように、ボタンを押すと時刻が更新される画面を実装する。
この時、画面が操作不能にならないこと。
2-2. 実装
実装したコードは下記の通り。
package com.example.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Looper;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
private TextView text;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// TextViewを取得
text = (TextView)findViewById(R.id.text);
// Buttonを取得
button = (Button)findViewById(R.id.button);
// Buttonをタップされた時の処理
button.setOnClickListener(buttonClick);
}
private View.OnClickListener buttonClick = new View.OnClickListener() {
@Override
public void onClick(View view) {
// thread poolにthreadを1つ生成
Executor executor = Executors.newFixedThreadPool(1);
// threadを実行
executor.execute(new WThreadTimer());
}
};
public class WThreadTimer implements Runnable {
final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void run() {
// ラムダ式だと↓のように書くらしい
// mainThreadHandler.post(() -> { text.setText("WThreadTimer start."); });
mainThreadHandler.post(postMessage("WThreadTimer start."));
for (int cnt = 0; cnt < 20; ++cnt) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
mainThreadHandler.post(postMessage(LocalDateTime.now().toString()));
}
mainThreadHandler.post(postMessage("WThreadTimer end."));
}
public Runnable postMessage(String message) {
Runnable r = new Runnable() {
@Override
public void run(){
text.setText(message);
}
};
return r;
}
}
}
2-3. Thread生成・起動処理
画面がバインドされないようにするため、threadで処理を行う必要がある。
なので、java.util.concurrent
から必要なクラスをインポートしてthread処理を実装した。
ボタンを押すとthreadを起動するよう処理を実装する。
private View.OnClickListener buttonClick = new View.OnClickListener() {
@Override
public void onClick(View view) {
// thread poolにthreadを1つ生成
Executor executor = Executors.newFixedThreadPool(1);
// threadを実行
executor.execute(new WThreadTimer());
}
};
Executors.newFixedThreadPool(number)
でthread実行用のIFをインスタンス化しているが、
ここで生成したthreadはプールされ、60秒間の間実行待ちしてくれるらしい。
threadを作り放題にするとマシン側が耐えられないので、もししっかりアプリを実装するならthread制御はExecutor
, Executors
, ExecutorService
などを使って上限を定めたい。
2-4. threadで行う処理
下記のように、threadで行う処理はRunnable
を継承したクラス内にrun
関数の実体を作って実装した。Runnable
は、Executor
で実行する処理を実装する場所らしい。Runnable
自体は、void run()
の仮想関数のみを持っている。
public class WThreadTimer implements Runnable {
final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void run() {
// ラムダ式だと↓のように書くらしい
// mainThreadHandler.post(() -> { text.setText("WThreadTimer start."); });
mainThreadHandler.post(postMessage("WThreadTimer start."));
for (int cnt = 0; cnt < 20; ++cnt) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
mainThreadHandler.post(postMessage(LocalDateTime.now().toString()));
}
mainThreadHandler.post(postMessage("WThreadTimer end."));
}
public Runnable postMessage(String message) {
Runnable r = new Runnable() {
@Override
public void run(){
text.setText(message);
}
};
return r;
}
}
下記のmainThreadHandler
は、UI threadが持つUIオブジェクトに対する更新を行う際に使用するハンドラ。Worker threadが、UIオブジェクトに対して更新を行うことはできないそうなので宣言、生成した。
final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
mainThreadHandler.post(Runnable r)
で、UI thread側に画面更新処理を委託するが、
更新処理は、Worker thread側で用意する必要があるようなので、下記で更新処理を実装。
UI thread側で、public void run()
が実行される...?
public Runnable postMessage(String message) {
Runnable r = new Runnable() {
@Override
public void run(){
text.setText(message);
}
};
return r;
}
UI threadへ処理依頼をしている箇所は、下記の通り。
ラムダ式は(私の中では)難しいので、postMessage
関数の中でRunnable
クラスを生成して返すように実装した。
// ラムダ式だと↓のように書くらしい
// mainThreadHandler.post(() -> { text.setText("WThreadTimer start."); });
mainThreadHandler.post(postMessage("WThreadTimer start."));
2-5. 所感など
UI thread、Worker threadについて
どこからでも画面オブジェクトをいじることができるわけでないということが分かった。
アプリが突然落ちるような挙動になるが、android studioのLogcatに原因が記載されていた。
本記事に記載したソースの致命的なバグ
画面が処理でバインドされないため、何回でもthread処理をボタンで実行できる。
必要に応じて排他制御を実装する必要あり。
スレッド生成上限とスレッド多重起動防止制御を実装する必要あり。