対象読者
- AndroidStudioの環境設定が終わり、Hello world! を出力できる人
-
MainActivity.java
とは何かを知っている人 - Javaを触ったことのある人
- AndroidStudioでラムダ式が使えること
進捗表示(作りたいやつ)
- 重たい処理をしている時に進捗をリアルタイムで確認したい
サンプルコード
- 簡単な例として、何らかの適当な処理を100回繰り返し、1回処理が終わるごとに進捗を1%増やすコードを作る
- ※この例での処理
doSomeThing()
は0.01秒間スリープするだけ
MainActivity.java
// ボタンクリック時(関数: busyLoop()を呼び出す)
execButton.setOnClickListener(view -> busyLoop());
// ダメ関数
private void busyLoop(){
for(int i = 0; i < 100; i++){
doSomeThing(); // <--- 処理
progressText.setText(String.valueOf(i+1)); // 進捗表示を更新
}
}
進捗表示(うまく行かないやつ)
- コンパイルしてみると。。。
- あれ、0→100%になっちゃった!
原因解説
- 原因
- mainループで処理と画面表示を同時に制御しようとするから
- mainループとは、イベントの検出、画面の更新などを行う無限ループのようなもの
forループ1回ごとに画面が更新されないの?
- サンプルコードでの、mainループの流れは下図の通り
解決策
Thread処理実装の流れ
-
- 行いたい処理の部分を
new Thread(()→ { }).start()
句で囲む
- 行いたい処理の部分を
-
- 画面更新を行うために、
Handler
を使用する
- 画面更新を行うために、
1. new Thread 句で囲む
- 今回は、自作関数 loopOnOtherThread()を作成し、その中にThread処理を定義した
MainActivity.java
// ボタンクリック時(関数: loopOnOtherThread()を呼び出す)
execButton.setOnClickListener(view -> loopOnOtherThread());
// Threadを利用する関数
void loopOnOtherThread() {
new Thread(()-> {
for(int i = 0; i < 100; i++) {
doSomeThing(); // ← 処理
updateUIText(i); // ← 画面更新( 後ほど解説 )
}
}).start();
}
- 先ほどのダメ関数と構成はほぼ同じであるが、
new Thread(()→ { }).start()
で処理を囲んでいる。 - 囲まれた部分は別スレッドとして動作するため、画面更新は停止しない。
2. Handler の作成
-
Handlerとは、別スレッドからmainスレッドの処理を呼ぶための仕組みである。
-
別スレッドでは画面を更新する権限がないため、Handlerを介して、mainスレッドに画面更新を依頼する
-
実装部分は以下の通りである
MainActivity.java
// mainActivityクラスのメンバ変数として、Handlerを宣言
final Handler handler = new Handler();
// UI更新用の関数を作成
void updateUIText(int num){
handler.post(() -> {
progressText.setText(String.valueOf(num + 1)); // 画面更新の処理を書く
});
}
- 上記コードのポイントは次の2点である
- HandlerをmainActivityクラスのメンバ変数として宣言する
- 別スレッドからUIを更新するための関数
updateUIText()
を定義し、行いたい画面処理をhandler.post(() -> {});
句で囲む - ※ 関数 updateUIText() は前述した関数 loopOnOtherThread() 内で呼ばれる
サンプルコード
- 上述したコードの実装例を以下にまとめた
- AndroidStudioで
java8
以上の設定であること(lambda式を使用するため)
- AndroidStudioで
mainActivity.java
package com.example.testapplication; //← あなたのプロジェクト名が入る
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
// 定数
final int LOOP_COUNT = 100;
final int SLEEP_MSEC = 10;
// メンバ変数
TextView progressText;
Button execButton;
final Handler handler = new Handler(); // UI制御用のハンドラ
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
assignLayoutToVariable();
execButton.setOnClickListener(view -> loopOnOtherThread()); // ボタンにクリックイベント追加
}
private void assignLayoutToVariable(){
// UI上のコンポーネントを変数に割り当てる
// R.id.○○ には、リソースファイルで定義したボタンのid名を入れる
setContentView(R.layout.activity_main);
progressText = findViewById(R.id.progressText);
execButton = findViewById(R.id.execButton);
}
private void doSomeThing(){
// あなたの行いたい処理を書く
//(※ 例としてスリープ関数を実装)
try{
Thread.sleep(SLEEP_MSEC);
}catch(InterruptedException e){
Log.d("MySleep", "Interrupted");
}
}
void loopOnOtherThread() {
// 別スレッドを立てる関数
new Thread(()-> {
for(int i = 0; i < LOOP_COUNT; i++) {
doSomeThing();
updateUIText(i);
}
}).start();
}
void updateUIText(int num){
// UI更新用の関数
handler.post(() -> {
progressText.setText(String.valueOf(num + 1));
});
}
}