プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

別スレッドでキュー管理(Handler, Looper, HandlerThread)

非同期処理を組んでると、
「別スレッド上でも Handler みたいなキュー管理がしたい」
って時があったりしますよねー。(きっと)


そんなとき、HandlerThread を使うとわりと簡単に実装できたりする。


HandlerThread(公式)


HandlerThread は java.lang.Thread を継承したクラスで、
android標準の Handler と組み合わせて使うための仕組みが入ってたり。


具体的な使い方。


まずは Handler(defaultコンストラクタ) を使う場合。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Handler handler = new Handler();
    handler.post(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });
}

実行すると当然 「main」 と出力される。
当然、mainスレ上でキューが実行される。
これは問題ないですね。


次に Handler+HandlerThread の場合。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // 別スレ生成 -> 開始
    HandlerThread handlerThread = new HandlerThread("other");
    handlerThread.start();

    Handler handler = new Handler(handlerThread.getLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });
}

実行すると 「other」 と出力される。
キューが実行されてるスレッドが mainスレ から 別スレ に変更されます。
もちろんこの Handlerインスタンス は別スレ上で使用してOK。


手順をまとめるとこんなかんじ。

  1. 別スレッド(HandlerThread)を生成
  2. 別スレ開始
  3. HandlerThreadインスタンス から Looperインスタンス 取得
  4. Handlerインスタンスを生成(3で取得した Looperインスタンス を 引数指定)
  5. 4 で作成した Handlerインスタンス を使用(Handler#post 等)


今まで PriorityBlockingQueue とか使って、
自前のキュー管理クラス作ってた自分涙目。
キューをFIFOで実行するだけのキュー管理であれば、
このやり方でまったく問題なさそうですね。


framework ではどうやってるのかなーと思い、
Looper、Handler、HandlerThread のソースを読んでみた。


ポイントはこのあたり。

  • コンストラクタ Handler(default) は「mainスレッド上で作成された」 Looperインスタンス を使用
  • コンストラクタ Handler(Looper) は「引数で渡された」 Looperインスタンス を使用
  • Looperインスタンスは Looper#prepare を呼び出すと生成される(ThreadLocal保持)
  • HandlerThread は Thread を継承したクラス
  • HandlerThread#run の中で Looper#prepare を呼び出している


Looperインスタンスの生成場所 の違い。
なるほど。

東日本大震災から半年経って思うこと

実は母親と祖父が福島に住んでいたりする。
原発から半径数十キロとかそのレベル。
直接的な津波の被害にはあっていないのだが、
震災が起きた際にはもちろん電話も通じず、物流も止まってた。
ただ、わりと早い時間で電気/ガスも復旧し、
今は普段どおりの生活はおくれているみたい。


震災直後。
電車も不通、道路もどうなっているかわからない。
祖父の家までたどりつけるかもわからない。
けど、このままだとどうなるかわからないから直接迎えに行く、
と言ったが、親からは絶対に来るな、と言われた。


私らは寿命まであと20数年。あんたは今からまだ長い。
今から結婚して子供生んだり育てたりするだろう。
原発の状況がわからない、どうなるかわからない場所には
絶対に来るな、来るのは親不孝な行為だ、と。


まーそんなこと言われても子の立場としては心配で仕方ない。
でもどうにもできず、ヤキモキしながら今を過ごしている。


ついこの間。
何かを見てて考えさせられることがあった。
両親とはあとどれくらい会えるのだろう、話せるのだろう、と。


両親は二人とも去年還暦を迎えて。
考えたくないけど、平均寿命とかイロイロ考えるとあと25年くらいなのか?
自分の場合、実際のところ年間何日会っているだろう。
月1回会うとしても年間12回。
12日×25=300日。


この数字を見て考えてしまった。
1年もないのか、と。
会える時間をもっと大事にしないといかんなー、と。
いろんなことを後悔しないように、と。


東日本大震災から半年。
メディアからは震災関連情報がだいぶ減ってきた。
震災直後ほど頭の中がそのことでいっぱいではない自分もいる。
若干麻痺してきているというか慣れつつあるというか。
でもやっぱり、自分の身内が被災しているので余計に他人ごととは思えない。
復興に関してもし自分にできることがあれば、
これからも少しでも協力できたらいいなと考えている。


ありがたいことに、@kuneen 作曲の被災地復興応援ソングプロジェクトの
バックコーラスに参加させていただきました。
元気が出るとてもいい曲なので、
たくさんの方に聴いていただけたら、と思ってます。


Pray〜明日を生きる


震災で亡くなった方々のご冥福を心よりお祈りいたします。

Error generating final archive(Debug Certificate expired on~) が発生する

androidプロジェクト をビルド時に突然こんなエラーが出るようになった。

Error generating final archive: Debug Certificate expired on 〜


調べてみるとデバッグ用証明書の期限切れ」で発生するエラー、とのこと。
対処方法は2種類あるみたい。


  1. debug.keystoreファイルを再生成する
  2. 有効期限を長くする


というわけで具体的な手順。


1.debug.keystoreファイルを 削除->再生成 する

手順

  • debug.keystoreファイルの削除
  • eclipseandroidプロジェクトをcleanビルド


debug.keystoreファイルパスはココを見ればおk。

eclipseの「Preferences - Android - Build」の「Default debug keystore」

f:id:dai4649:20110713015811p:image



2.有効期限を長くする

こちらを参考にさせていただきました。
Androidの「Debug certificate expired」エラーの対処方法




毎年やることになるけど、今回は 1 を採用。


debug.keystoreのデフォルト有効期限は1年
ということは、この1年、特にOS再インストールとかやってないので、
Android開発初めてちょうど1年が経過した、ということになるのか??
これはなんだか感慨深い!!


というわけでちょっと振り返ってみた。
この1年で公開したアプリはーー

  • 定点カメラアプリ(無音で定期的にタイマ撮影)
  • カメラプレビュー画面で顔認識して顔の上にう○こ載せるアプリ


うーむ。。
ろくなアプリがない。。
AndroidMarketにも似たようなアプリ多数。
個性がないw

例外catch漏れ対策(UncaughtExceptionHandler)

全ての例外を自前でcatchしていれば何の問題もないんだけれども、
もしアプリ内で例外catch漏れしてしまった場合、
Dalvik VM まで例外が通知されて
「〜が予期せず停止しました。やり直してください」
のダイアログが出てアプリが強制終了してしまう。


f:id:dai4649:20110629004538p:image


これはとにかくダサい!!
原因が ぬるぽ とかだともう顔まっか!!
できればこんなプログラムミスはこっそり隠蔽しておきたい!!


そんな都合のいい欲求を満たす方法として、
Thread.UncaughtExceptionHandler という仕組みがあるようだ。
(android固有ではなく、Javaでもともとあるみたい)

アプリ内で予期しない例外発生時の
「開発者にバグ報告する/しない」機能
この仕組みで実装できるみたい。


※こちらの記事を参考にさせていただきました。
androidアプリのバグ報告システムを考える


発生しうる例外をすべて把握/制御できればベストなのだが、
とりあえずこの処理を保険として実装しておくのはアリかも。


と安直に考えて、テストコード書いてみました。


テストコードの流れ

  1. ボタン押下で UIスレ or 別スレ で例外発生させる
  2. catch漏れ例外を uncaughtExceptionHander で catch(ややこしい)
  3. uncaughtExceptionHander で 例外catch したらアプリ終了
package test.uncaughtexception;

import java.lang.Thread.UncaughtExceptionHandler;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(createLayout());

        Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable t) {
                Log.e(getClass().getName(), "### uncaughtException ###", t);

                // Activity終了
                finish();
            }
        });
    }

    private LinearLayout createLayout() {
        Button buttonUI = new Button(this);
        buttonUI.setText("UIスレで例外発生");
        buttonUI.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                occurErrorUIThread();
            }
        });

        Button buttonOther = new Button(this);
        buttonOther.setText("別スレで例外発生");
        buttonOther.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                occurErrorOtherThread();
            }
        });

        LinearLayout layout = new LinearLayout(this);
        layout.addView(buttonUI);
        layout.addView(buttonOther);
        return layout;
    }

    private void occurErrorUIThread() {
        throw new RuntimeException("UI");
    }

    private void occurErrorOtherThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException("OTHER");
            }
        }, "OTHER").start();
    }
}


とーこーろーがー!
実装してみたけどもうまく動作しなかった。
動作環境は 2.3.3(API Level10) です。(※1)


ココからは有識者の方に助けを求めますw


まず、例外が発生したスレッド が「UIスレッドかどうか」
例外発生->catch後のアプリ挙動が異なってきます。


例えばこんなかんじになる。

case1
例外発生(=UIスレッド)
↓
UncaughtExceptionHandler#uncaughtException で例外検知
↓
アプリ終了(Activity#finish)
↓
アプリ再起動すると 起動時にANR 発生
case2
例外発生(≠UIスレッド)
↓
UncaughtExceptionHandler#uncaughtException で例外検知
↓
アプリ終了(Activity#finish)
↓
アプリ再起動しても問題なし


case1 はなんだかアプリが正常に終了していないように見えますね。。


case1 で再起動後にANR発生の図
f:id:dai4649:20110629023102p:image


ちなみに、case1 で Activity#finish を呼ばなかった場合は
アプリが終了せず、これまた ANR発生 してしまいます。


うーん。
本来どうするのが正しいのだろうか。。
実装してる方、教えていただけると助かります。
これならcatchせずにダイアログ出したほうがまだマシ、なのか。


そもそも例外catch漏れすることがダメなので、
「例外はすべてcatchしましょう」という原点に戻ってくる気もするけどもw


※1 動作環境追記(20110629)

処理時間測定方法(android.util.TimingLogger)

例えば、アプリ内のボトルネック調査するとき、
重そうな処理にアタリつけて処理時間測定しますよね。
ベタにやるとこんな感じ。

// 測定開始
long start = System.currentTimeMillis();

// 測定したい処理

// 測定終了
long end = System.currentTimeMillis();
Log.d(getClass().getName(), "measure: " + (end - start));


何かないかなーと思って公式javadoc見てたら
便利そうなクラスを発見。


android.util.TimingLogger


使い方はざっくりこんな感じ。

  • コンストラクタ(測定開始)
    • // 処理1
    • addSplit(処理1 の処理時間測定)
    • // 処理2
    • addSplit(処理2 の処理時間測定)
    • ・・・
  • dumpToLog(測定終了+ログ出力)


テストコード書いてみた。

private void testTimingLogger() {
    // 測定開始
    TimingLogger logger = new TimingLogger("TAG_TEST", "testTimingLogger");

    // 処理1

    logger.addSplit("abeshi");

    // 処理2

    logger.addSplit("hidebu");

    // 測定終了+ログ出力
    logger.dumpToLog();
}


さっそく実行してみる。
が、ログが出ない。


javadoc見ると、TimingLogger のコンストラクタにこう書いてある。

Create and initialize a TimingLogger object that will log using the specific tag.
If the Log.isLoggable is not enabled to at least the Log.VERBOSE level 
for that tag at creation time then the addSplit and dumpToLog call will do nothing.

参考までにこんな記事も。
stackoverflow - Time code execution in Android


ログ出力レベルを「VERBOSE」にしないと何も出力しないだそうです。


というわけで、ログレベルを「VERBOSE」に変更してみる。
上記ソースの場合、ログレベル変更はこんな感じになりますね。

$ adb shell
# setprop log.tag.TAG_TEST VERBOSE


アプリ実行。

DEBUG/TAG_TEST(940): testTimingLogger: begin
DEBUG/TAG_TEST(940): testTimingLogger:      6 ms, abeshi
DEBUG/TAG_TEST(940): testTimingLogger:      17 ms, hidebu
DEBUG/TAG_TEST(940): testTimingLogger: end, 23 ms


ログ出た。これは便利。