11 February 2015

(Android) スレッドを利用する方法

Androidでスレッドを利用する簡単なサンプルコード

■ Runnableインターフェースのrunメソッドを作成

Eclipseのパッケージ・エクスプローラまたはナビゲーターで、MainActivityクラスを選択した状態で、「ソース」ー「メソッドのオーバーライド/実装」を選択する。

20150211-eclipse-run.jpg

キーボードから“run”と入力すると、インクリメンタルサーチが掛かる。“run”のチェックボックスをONにしてOKボタンを押す。

作成されたrunメソッドに手を加えて完成させる

MainActivity.java の run 部分抜粋
    @SuppressLint("SimpleDateFormat")
    @Override
    public void run() {
        final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
 
        while (!flagThreadStop) {
            // 500 ミリ秒待機
            try { Thread.sleep(500); }
            catch (InterruptedException e) {
                // interrupt() がコールされた場合の処理
                flagThreadStop = true;
                // LogCatにデバッグ用メッセージを表示
                Log.d("(thread) interrupt receive", "1");
            }
            // UI widgetへのアクセスは Runnable() 内で行う
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 現在時刻の表示
                    text.setText(dateFormat.format(new Date()));
                }
            });
        }
        // (デバッグ用)動作の明確な確認のため 3秒待つ
        try { Thread.sleep(3000); }
        catch (InterruptedException e) { }
        // LogCatにデバッグ用メッセージを表示
        Log.d("(thread) thread run() exit", "1");
    }

緑と赤で着色した部分が、手入力した部分。それ以外は、Eclipseの自動作成部分。なお、赤の部分がスレッドのループ処理部分で、500ミリ秒のsleepを入れて定期的に停止条件flagThreadStopを監視しつつ、画面への時刻表示text.setText(dateFormat.format(new Date()));を行っている。

■ スレッド内から画面へのアクセスはHandlerを経由させる

スレッド内から画面へ直接アクセスすると、例外が発生する。そのため、Handlerを用いて回避する。

MainActivity.java の 該当部分抜粋
public class MainActivity extends ActionBarActivity implements Runnable, OnClickListener {
 
    final int ID_TEXTVIEW = 0x1001;
    final int ID_STOPBUTTON = 0x1002;
    private Thread thread = null;
    private final Handler handler = new Handler();
    // スレッド内から書き込むUI Widget
    TextView text;
    // スレッド内で使う、スレッド終了命令のフラグ
    private volatile boolean flagThreadStop = false;
 
 〜 略 〜
 
    @SuppressLint("SimpleDateFormat")
    @Override
    public void run() {
  
 〜 略 〜
 
            // UI widgetへのアクセスは Runnable() 内で行う
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 現在時刻の表示
                    text.setText(dateFormat.format(new Date()));
                }
            });
 
 〜 略 〜
 
   }
■ スレッドの終了

スレッドの終了は、UIからスレッドに終了を知らせて、内部のループを抜けるようにするコーディングが必要。

今回は、割込処理を用いて通知を行っている

MainActivity.java の 該当部分抜粋
    @SuppressLint("SimpleDateFormat")
    @Override
    public void run() {
        final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
 
        while (!flagThreadStop) {
            // 500 ミリ秒待機
            try { Thread.sleep(500); }
            catch (InterruptedException e) {
                // interrupt() がコールされた場合の処理
                flagThreadStop = true;
            }
 
            〜 略 (スレッドの実際の処理部分など) 〜
 
        }
    }
 
    @Override
    protected void onDestroy() {
        // スレッドが“生存”している場合は、スレッド終了処理を行う
        if(thread.isAlive()) 
        {
            // スレッドに interrupt 割り込みを送る
            thread.interrupt();
            // スレッドの終了を待つ
            try{ thread.join(); }
            catch(InterruptedException e) { }
        }
        // 上位クラスのDestroyをコール
        super.onDestroy();
    }
■ サンプル画面とMainActivityのコード

20150211-thread-disp.jpg

MainActivity.java
package com.example.android_thread_1;
 
import android.support.v7.app.ActionBarActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.os.Handler;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Button;
import android.view.KeyEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.util.Log;
 
public class MainActivity extends ActionBarActivity implements Runnable, OnClickListener {
 
    final int ID_TEXTVIEW = 0x1001;
    final int ID_STOPBUTTON = 0x1002;
    private Thread thread = null;
    private final Handler handler = new Handler();
    // スレッド内から書き込むUI Widget
    TextView text;
    // スレッド内で使う、スレッド終了命令のフラグ
    private volatile boolean flagThreadStop = false;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        setContentView(layout);
 
        // 終了ボタン
        Button button = new Button(this);
        button.setText("スレッド終了");
        button.setId(ID_STOPBUTTON);
        layout.addView(button);
        button.setOnClickListener(this);
        
        // 時間を表示するテキスト (スレッドより書き換える部分)
        text = new TextView(this);
        text.setId(ID_TEXTVIEW);
        text.setTextSize((int)(text.getTextSize()*1.5));
        layout.addView(text);
 
        thread = new Thread(this);
        thread.start();
    }
 
    @SuppressLint("SimpleDateFormat")
    @Override
    public void run() {
        final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
 
        while (!flagThreadStop) {
            // 500 ミリ秒待機
            try { Thread.sleep(500); }
            catch (InterruptedException e) {
                // interrupt() がコールされた場合の処理
                flagThreadStop = true;
                // LogCatにデバッグ用メッセージを表示
                Log.d("(thread) interrupt receive", "1");
            }
            // UI widgetへのアクセスは Runnable() 内で行う
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 現在時刻の表示
                    text.setText(dateFormat.format(new Date()));
                }
            });
        }
        // (デバッグ用)動作の明確な確認のため 3秒待つ
        try { Thread.sleep(3000); }
        catch (InterruptedException e) { }
        // LogCatにデバッグ用メッセージを表示
        Log.d("(thread) thread run() exit", "1");
    }
 
    @Override
    public void onClick(View v) {
        switch(v.getId()){
        case ID_STOPBUTTON:
            // プログラムを終了する
            this.finish();
            break;
        }
    }
 
    // スレッド終了を行うため、onDestroyをフック
    @Override
    protected void onDestroy() {
        // スレッドが“生存”している場合は、スレッド終了処理を行う
        if(thread.isAlive()) 
        {
            // LogCatにデバッグ用メッセージを表示
            Log.d("(from UI) interrupt sent", "1");
            // スレッドに interrupt 割り込みを送る
            thread.interrupt();
            // LogCatにデバッグ用メッセージを表示
            Log.d("(UI) waiting thread terminate", "1");
            // スレッドの終了を待つ
            try{ thread.join(); }
            catch(InterruptedException e) { }
        }
        // 上位クラスのDestroyをコール
        super.onDestroy();
    }
 
    // キーイベントをフックする (任意!)
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(keyCode != KeyEvent.KEYCODE_BACK){
            // 「戻る」キー以外
            return super.onKeyDown(keyCode, event);
        }
        else {
            // 「戻る」キーを無視する(プログラムを終了させない)
            Log.d("(UI) KEYCODE_BACK disabled", "1");
            return false;
        }
    }
}

赤で着色した implements を忘れると、正常動作しない。implements と、そのリスナー・メソッドの追加は、まず、Activityのところで
… extends ActionBarActivity implements Ru…
と入力を始めると、コード補完が働き、挿入できる候補が表示される。implementsの行を完成させると、クラス部分(class MainActivity)に「エラー表示」が出てくるので、そこにカーソルを合わせると「実装されていないメソッドの実装」と表示されるので、それを選択すればメソッドが自動生成される。