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

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

Intentの作り方

Activity/Service起動用のIntentを作るとき、
皆どうやってるのだろう、とふと疑問。


例えば Activity#onCreate 内で
「Service(test.intent.TestService)起動用のIntent」 を作るとする。
基本的な作り方はこんなかんじになるのかな?

@Override
public void onCreate(Bundle savedInstanceState) {
    〜

    // コンストラクタのみ で作る場合
    Intent intent1 = new Intent(this, TestService.class);
    Intent intent2 = new Intent(getApplicationContext(), TestService.class);

    // コンストラクタ(引数なし)+setClassName で作る場合
    String packageName = getPackageName();
    String className = TestService.class.getCanonicalName();
    Intent intent3 = new Intent();
    intent3.setClassName(packageName, className);
}


自分の場合は intent1、2 みたいにコンストラクタ1行で済ませることが多い。
web上のサンプル見てもこのパターンが多い気がする。
通常はこれでいいはず。楽だし。




ここで出た疑問。
ApplicationContext を渡す必要があるのだろうか。




Intent のソースを確認してみたところ、
本来は ApplicationContext を引数に渡す必要などなく、
最低限必要なのはこれ↓↓だけだった。


Intent作成に必要な情報

  1. アプリケーションのパッケージ名
  2. 起動するクラス名


1は AndroidManifest.xmlタグのpackage属性指定値。
起動するクラス(TestService)のパッケージ名ではない。
2は 起動するActivity/Service等のクラス名(パッケージ名まで含む)。


引数に渡された ApplicationContext は、内部で
「アプリケーションのパッケージ名」 を取得するために使ってるだけ。


まーつまり intent1、2、3 はどれも同じなので、
作りたい作り方でおk、ということでしたw


Intent.java(抜粋)

public Intent() {
}

public Intent(Context packageContext, Class<?> cls) {
    mComponent = new ComponentName(packageContext, cls);
}

public Intent setClassName(Context packageContext, String className) {
    mComponent = new ComponentName(packageContext, className);
    return this;
}


ComponentName.java(抜粋)

public ComponentName(String pkg, String cls) {
    if (pkg == null) throw new NullPointerException("package name is null");
    if (cls == null) throw new NullPointerException("class name is null");
    mPackage = pkg;
    mClass = cls;
}

public ComponentName(Context pkg, String cls) {
    if (cls == null) throw new NullPointerException("class name is null");
    mPackage = pkg.getPackageName();
    mClass = cls;
}

public ComponentName(Context pkg, Class<?> cls) {
    mPackage = pkg.getPackageName();
    mClass = cls.getName();
}


話変わって。


intentって文字列定義みたいなもんだから、
できれば 「static final」 定義で、static初期化子 内で作っておきたいなー、
なんて自分は思ってたりする。後から編集しないようなintentは特に。


でも 「アプリケーションのパッケージ名」 は
static初期化子 内で動的に取得できない。
ということは、こんなかんじで固定値定義するしかないのか??

private static final Intent intent;
static {
    String packageName = "test.intent";
    String className = TestService.class.getCanonicalName();
    intent = new Intent();
    intent.setClassName(packageName, className);
}


うーん。微妙。

別スレッドでキュー管理(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)