FC2ブログ
HOME   »  2012年03月
Archive | 2012年03月

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[Android]マルチスレッドでBluetoothAdapterクラスを扱う場合の注意点

BluetoothAdapterクラスのインスタンスは、コンストラクタではなくBluetoothAdapterクラスのgetDefaultAdapterメソッドを使用して取得する。
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
下記では、BluetoothAdapterのインスタンスを問題なく取得でき、Toastによる"Completed"メッセージが表示される。
public class MainActivity extends Activity {
    private BluetoothAdapter bluetoothAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter != null) {
            Toast.makeText(this, "Completed", Toast.LENGTH_SHORT).show();
        }
    }
}
しかし下記では、getDefaultAdapterメソッド実行時にRuntimeException("Can't create handler inside thread that has not called Looper.prepare()")がスローされてしまう。
public class MainActivity extends Activity {
    private BluetoothAdapter bluetoothAdapter;
    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handler = new Handler();
        new Thread() {
            @Override
            public void run() {
                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                if (bluetoothAdapter != null) {
                    handler.post(new Runnable() {
                        public void run() {
                            Toast.makeText(MainActivity.this, "Completed", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }.start();
    }
}
これは、getDefaultAdapterメソッドあるいはそこから呼ばれる別のメソッドの中でHandlerクラスあるいはそのサブクラスのコンストラクタを実行しているからである。

Handlerクラス及びそのサブクラスはメッセージの投げ先として、コンストラクタの引数あるいは内部処理にてLooperクラスのインスタンスを取得して保持しており、このLooperクラスのインスタンスはスレッドと1対1で関連付けられている(1つのスレッドには1つのLooperクラスのインスタンスのみが存在し得る)。
上記の失敗する場合では、getDefaultAdapterメソッドがメインスレッドとは別のスレッドで実行されており、このスレッドには関連付けられたLooperクラスのインスタンスが存在しない為、結果としてHandlerクラスあるいはそのサブクラスのコンストラクタにてRuntimeExceptionがスローされることになる(メインスレッド(UIスレッド)には裏で自動的にLooperクラスのインスタンスが関連付けられている為、特に意識する必要はないが、自前のスレッドではLooperクラスのprepareメソッドを実行して明示的に関連付ける必要がある)。

マルチスレッドでBluetoothAdapterクラスを扱う場合は注意が必要である。


スポンサーサイト

[Java]スレッドの終了

マルチスレッド動作時に自スレッド以外のスレッドを終了させたい場合は、ThreadクラスのInterruptメソッドが使用できる。
public class Main {
    public static boolean interrupted;
    public static long startTime;
    public static long interruptedTime;

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                long referenceTime = startTime;
                long currentTime;
                while (!isInterrupted()) {
                    currentTime = System.currentTimeMillis();
                    if ((currentTime - referenceTime) >= 1000) {
                        referenceTime = currentTime;
                        System.out.println("別スレッド動作中:" + currentTime);
                    }
                }
                interruptedTime = System.currentTimeMillis();
                System.out.println("割り込み:" + interruptedTime);
                interrupted = true;
            }
        };
        interrupted = false;
        startTime = System.currentTimeMillis();
        System.out.println("開始:" + startTime);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("メインスレッド動作中:" + System.currentTimeMillis());
        }
        thread.interrupt();
        while (!interrupted) {
        }
        System.out.println("経過時間:" + (interruptedTime - startTime));
    }
}
上記では、メインスレッド、別スレッド双方で開始から1秒周期で「動作中」メッセージを表示し、メインスレッド側で5秒経過(5回目の「動作中」メッセージを表示)したタイミングで別スレッド側のInterruptメソッドを実行している。
別スレッド側では、isInterruptedメソッドで割り込み発生状態(Interruptメソッド実行状態)をチェックし、割り込みが発生していたら(Interruptメソッドが実行され、その結果isInterruptedメソッドの戻り値がtrueとなったら)「割り込み」メッセージ表示後runメソッドを終了する。

実行結果は以下の通りで、開始から約5秒後(約5000ミリ秒後)に割り込みが発生し、そこで別スレッドの動作が終了している。


開始:1330828609421
別スレッド動作中:1330828610421
メインスレッド動作中:1330828610453
別スレッド動作中:1330828611421
メインスレッド動作中:1330828611484
別スレッド動作中:1330828612421
メインスレッド動作中:1330828612484
別スレッド動作中:1330828613421
メインスレッド動作中:1330828613484
別スレッド動作中:1330828614421
メインスレッド動作中:1330828614484
割り込み:1330828614500
経過時間:5079


但し、Interruptメソッドはそれ自体にスレッドを終了させる機能はなく、あくまでもトリガとして機能するだけである(上記でも、Interruptメソッド実行を契機としてwhileループから抜ける様にしている)。

スレッドがsleepメソッド、joinメソッド、waitメソッドを実行中にInterruptメソッドが実行された場合はInterruptedExceptionがスローされる為、これをキャッチすることにより割り込み発生を認識することができる。
public class Main {
    public static boolean interrupted;
    public static long startTime;
    public static long interruptedTime;

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (!isInterrupted()) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        interruptedTime = System.currentTimeMillis();
                        System.out.println("割り込み(InterruptedException):" + interruptedTime);
                        interrupted = true;
                        return;
                    }
                    System.out.println("別スレッド動作中:" + System.currentTimeMillis());
                }
                interruptedTime = System.currentTimeMillis();
                System.out.println("割り込み(isInterrupted):" + interruptedTime);
                interrupted = true;
            }
        };
        interrupted = false;
        startTime = System.currentTimeMillis();
        System.out.println("開始:" + startTime);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("メインスレッド動作中:" + System.currentTimeMillis());
        }
        thread.interrupt();
        while (!interrupted) {
        }
        System.out.println("経過時間:" + (interruptedTime - startTime));
    }
}
上記の実行結果は以下の通り(上記もそうだが、runメソッド内にsleepメソッド、joinメソッド、waitメソッド以外の処理内容がある場合は、割り込み発生タイミングによってはInterruptedExceptionがスローされない可能性があることを考慮して、InterruptedExceptionのキャッチと併せてisInterruptedメソッドによる割り込み発生確認も行うべき)。


開始:1330828060578
別スレッド動作中:1330828061609
メインスレッド動作中:1330828061609
別スレッド動作中:1330828062609
メインスレッド動作中:1330828062609
別スレッド動作中:1330828063609
メインスレッド動作中:1330828063609
別スレッド動作中:1330828064609
メインスレッド動作中:1330828064609
別スレッド動作中:1330828065609
メインスレッド動作中:1330828065609
割り込み(InterruptedException):1330828065609
経過時間:5031



[Java]InputStreamの読み出しデータを格納した配列型変数の状態 その2

以前の記事「InputStreamの読み出しデータを格納した配列型変数の状態」で書いたreadメソッドは、read(byte[] b)というシグネチャについてだったが、readメソッドにはもう1つread(byte[] b, int off, int len)というシグネチャが存在する(更にもう1つread()というシグネチャも存在するが、これは配列型引数がないので割愛する)。

read(byte[] b, int off, int len)は、入力ストリームから最大lenバイトのデータを読み出し、b[off]からb[off+k-1](kはメソッドの戻り値で実際に読み出したバイト数)に格納する、という動作になる。
従って、read(byte[] b)シグネチャはread(byte[] b, int off, int len)シグネチャにてoff=0、len=b.lengthとした場合と等価である。

英大文字の"A"、"B"、"C"、"D"、"E"、"F"、"G"、"H"、"I"の9文字を記したテキストファイル(test.txt)を作成し、以下の様にFileInputStreamクラスのreadメソッド(read(byte[] b, int off, int len)シグネチャ)を使用してこのファイルからデータを読み出す。
public class Main {
    public static void main(String[] args) {
        File file = new File("/*****/test.txt");
        FileInputStream fileInputStream;
        int readDataLength;
        byte[] buffer = new byte[9];
        int index = 0;
        try {
            fileInputStream = new FileInputStream(file);
            for (int i = 0; i < 3; i++) {
                readDataLength = fileInputStream.read(buffer, index, 3);
                index = index + readDataLength;
                System.out.println(i + 1 + "回目");
                System.out.println("読み出したデータ数:" + readDataLength);
                for (int j = 0; j < buffer.length; j++) {
                    System.out.println("buffer[" + j + "]:" + buffer[j]);
                }
                System.out.println("");
            }
            fileInputStream.close();
        } catch (Exception e) {
        }
    }
}
上記の実行結果は以下の通り。


1回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:0
buffer[4]:0
buffer[5]:0
buffer[6]:0
buffer[7]:0
buffer[8]:0

2回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:68
buffer[4]:69
buffer[5]:70
buffer[6]:0
buffer[7]:0
buffer[8]:0

3回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:68
buffer[4]:69
buffer[5]:70
buffer[6]:71
buffer[7]:72
buffer[8]:73


次回読み出し時の格納先先頭インデックス(変数index)を、12行目で直前に読み出したバイト数分ずらしている為、読み出す度にデータが上書きではなく追加される。


プロフィール

まさお

Author:まさお
プログラミングは趣味レベルなので、お手柔らかに。

ブログランキング
ブログランキング参加中。是非クリックお願いします。


にほんブログ村 IT技術ブログ Androidアプリ開発へ

人気ブログランキングへ

ブログランキング



ブログ王

ブログランキング【ブログの惑星】

プログラム人気ブログランキング
最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。