1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Androidで進捗表示ができるまで

Last updated at Posted at 2020-01-02

対象読者

  • AndroidStudioの環境設定が終わり、Hello world! を出力できる人
  • MainActivity.javaとは何かを知っている人
  • Javaを触ったことのある人
    • AndroidStudioでラムダ式が使えること

進捗表示(作りたいやつ)

  • 重たい処理をしている時に進捗をリアルタイムで確認したい

loopOnOtherThread.gif

サンプルコード

  • 簡単な例として、何らかの適当な処理を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));  // 進捗表示を更新
        }
    }

進捗表示(うまく行かないやつ)

busyLoop.gif

  • コンパイルしてみると。。。
    • あれ、0→100%になっちゃった!

原因解説

  • 原因
    • mainループで処理と画面表示を同時に制御しようとするから
  • mainループとは、イベントの検出、画面の更新などを行う無限ループのようなもの
    • 画面の更新は、mainループの最後に行われる
    • つまり、他の処理が終わらない限り画面は固まったままである
    • image.png

forループ1回ごとに画面が更新されないの?

  • サンプルコードでの、mainループの流れは下図の通り
    • 関数busyLoop()内のforループで、進捗%の値は逐次書き換えられている
    • しかし、値の更新が画面に反映されるのは、関数busyLoop()が終了した後 となる
    • 従って、0%でしばらく固まったのち、100%が表示される という残念な画面表示になってしまった。
    • image.png

解決策

  • 非同期処理を使う
    • mainループとは別のループを生成し、処理と画面更新を分離する
    • 今回は、実装が簡単なThread処理を紹介する
    • image.png

Thread処理実装の流れ

    1. 行いたい処理の部分を new Thread(()→ { }).start() 句で囲む
    1. 画面更新を行うために、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スレッドに画面更新を依頼する

    • image.png
  • 実装部分は以下の通りである

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式を使用するため)
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));
        });
    }
}

参考文献

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?