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は AndroidManifest.xml の
起動するクラス(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 は 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。
手順をまとめるとこんなかんじ。
- 別スレッド(HandlerThread)を生成
- 別スレ開始
- HandlerThreadインスタンス から Looperインスタンス 取得
- Handlerインスタンスを生成(3で取得した Looperインスタンス を 引数指定)
- 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 作曲の被災地復興応援ソングプロジェクトの
バックコーラスに参加させていただきました。
元気が出るとてもいい曲なので、
たくさんの方に聴いていただけたら、と思ってます。
震災で亡くなった方々のご冥福を心よりお祈りいたします。
Error generating final archive(Debug Certificate expired on~) が発生する
androidプロジェクト をビルド時に突然こんなエラーが出るようになった。
Error generating final archive: Debug Certificate expired on 〜
調べてみると「デバッグ用証明書の期限切れ」で発生するエラー、とのこと。
対処方法は2種類あるみたい。
- debug.keystoreファイルを再生成する
- 有効期限を長くする
というわけで具体的な手順。
1.debug.keystoreファイルを 削除->再生成 する
手順
debug.keystoreファイルパスはココを見ればおk。
eclipseの「Preferences - Android - Build」の「Default debug keystore」
2.有効期限を長くする
こちらを参考にさせていただきました。
Androidの「Debug certificate expired」エラーの対処方法
毎年やることになるけど、今回は 1 を採用。
debug.keystoreのデフォルト有効期限は1年。
ということは、この1年、特にOS再インストールとかやってないので、
Android開発初めてちょうど1年が経過した、ということになるのか??
これはなんだか感慨深い!!
というわけでちょっと振り返ってみた。
この1年で公開したアプリはーー
- 定点カメラアプリ(無音で定期的にタイマ撮影)
- カメラプレビュー画面で顔認識して顔の上にう○こ載せるアプリ
うーむ。。
ろくなアプリがない。。
AndroidMarketにも似たようなアプリ多数。
個性がないw
例外catch漏れ対策(UncaughtExceptionHandler)
全ての例外を自前でcatchしていれば何の問題もないんだけれども、
もしアプリ内で例外catch漏れしてしまった場合、
Dalvik VM まで例外が通知されて
「〜が予期せず停止しました。やり直してください」
のダイアログが出てアプリが強制終了してしまう。
これはとにかくダサい!!
原因が ぬるぽ とかだともう顔まっか!!
できればこんなプログラムミスはこっそり隠蔽しておきたい!!
そんな都合のいい欲求を満たす方法として、
Thread.UncaughtExceptionHandler という仕組みがあるようだ。
(android固有ではなく、Javaでもともとあるみたい)
アプリ内で予期しない例外発生時の
「開発者にバグ報告する/しない」機能は
この仕組みで実装できるみたい。
※こちらの記事を参考にさせていただきました。
androidアプリのバグ報告システムを考える
発生しうる例外をすべて把握/制御できればベストなのだが、
とりあえずこの処理を保険として実装しておくのはアリかも。
と安直に考えて、テストコード書いてみました。
テストコードの流れ
- ボタン押下で UIスレ or 別スレ で例外発生させる
- catch漏れ例外を uncaughtExceptionHander で catch(ややこしい)
- 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 で Activity#finish を呼ばなかった場合は
アプリが終了せず、これまた ANR発生 してしまいます。
うーん。
本来どうするのが正しいのだろうか。。
実装してる方、教えていただけると助かります。
これならcatchせずにダイアログ出したほうがまだマシ、なのか。
そもそも例外catch漏れすることがダメなので、
「例外はすべてcatchしましょう」という原点に戻ってくる気もするけどもw
※1 動作環境追記(20110629)