2013年11月30日

[Android] IntentServiceを継承したServiceをBroadcastReceiverの中に書く

IntentServiceという便利なクラスがあります。
バックグラウンドで動作してくれて、処理が終わったら勝手に終了してくれるService(という認識)です。
BroadcastReceiver契機で時間のかかる処理を行いたいときに使うと便利ですね。

今回は特定のBroadcastReceiverからしか呼ばれない処理だっため、以下のようにIntentServiceを書いてみました。
public class HogeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// ... なんらかの処理

context.startService(new Intent(context, FugaService.class));
}

public static class FugaService extends IntentService {
public FugaService() {
super("FugaService");
}

@Override
protected void onHandleIntent(Intent intent) {
// ... バックグラウンドで行いたい処理
}
}
}


AndroidManifest.xmlはこんな感じ。
<receiver android:name=".HogeReceiver" />
<service android:name=".HogeReceiver$FugaService" />


ここでのBroadcastReceiverとServiceは2つで1つな感じなので、こんな感じで書いた方がつながりがわかって良いかな、と思った次第。
IntentServiceのクラスを外側にしても良いかもしれない。
BroadcastReceiver内に非同期処理が書ければこんな書き方しなくて済むのですけどね。

posted by t2low at 21:00| Android

2013年11月29日

[Android] android.intent.action.PACKAGE_〜

BroadcastReceiverで受信できる「PACKAGE_〜」のアクションについて調べてみました。
おこなったのは以下のIntentFilterを設定したBroadcastReceiverで受信したActionをログに表示するというもの。
Androidのソースを読んで詳細に調べたわけでなく、動作を確認しただけです。

<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<action android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<action android:name="android.intent.action.PACKAGE_INSTALL" />
<action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="android.intent.action.PACKAGE_VERIFIED" />

<data android:scheme="package" />
</intent-filter>


確認した端末は以下の3つ。括弧内はAndroidのバージョンです。
・HTC Desire (2.3.3)
・HTC Evo 3D (4.0.3)
・HTC J One (4.2.2)

以下が「操作」とその時に受信した「Broadcast」です。

--------------------------------
■インストール
android.intent.action.PACKAGE_ADDED

■アップデート
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_ADDED
android.intent.action.PACKAGE_REPLACED

■アンインストール
android.intent.action.PACKAGE_REMOVED
android.intent.action.PACKAGE_FULLY_REMOVED (4.0以降のみ)

■強制停止
android.intent.action.PACKAGE_RESTARTED

■データ削除
android.intent.action.PACKAGE_RESTARTED
android.intent.action.PACKAGE_DATA_CLEARED

■無効化/有効化 (4.0以降のみ)
android.intent.action.PACKAGE_CHANGED (4.0以降のみ)

■SDカードへ移動/携帯端末へ移動
(なし)
--------------------------------

アップデートはADDEDもREMOVEDも受信するのがめんどくさいですね。
インストール/アップデート/アンインストールで何らかの処理を行いたい場合、単純にADDEDやREMOVED毎に処理を行ってしまうと、アップデート時には2回処理されてしまいます。
アップデート時は受信したIntentのExtraに「android.intent.extra.REPLACING」というキーで「true」が詰められているようなので、それを使って判断するのが良さそうです。
(Intentに EXTRA_REPLACING として定義されています)
あと「SDカードへの移動/携帯端末へ移動」も何かしらBroadcastされるかと思ってましたが何も来ませんでした。


また、以下はそれぞれのアクションがどのAPIレベルで追加されたかの一覧です。

android.intent.action.PACKAGE_ADDED (Lv1)
android.intent.action.PACKAGE_CHANGED (Lv1)
android.intent.action.PACKAGE_INSTALL (Lv1) (deprecated in Lv14)
android.intent.action.PACKAGE_REMOVED (Lv1)
android.intent.action.PACKAGE_RESTARTED (Lv1)
android.intent.action.PACKAGE_DATA_CLEARED (Lv3)
android.intent.action.PACKAGE_REPLACED (Lv3)
android.intent.action.PACKAGE_FIRST_LAUNCH (Lv12)
android.intent.action.PACKAGE_FULLY_REMOVED (Lv14)
android.intent.action.PACKAGE_NEEDS_VERIFICATION (Lv14)
android.intent.action.PACKAGE_VERIFIED (Lv17)


いくつか確認できてないActionがあります。
FIRST_LAUNCHなんて受信できたら何かおもしろそうなことが出来そうな気がしますが、今回確認した範囲では受信しなかったです。
何かpermissionでも必要なんでしょうか。

どの操作で、どのBroadcastが届くのか、また調べるのがめんどくさいのでメモとして残しました。
今回はここまで。

タグ:android
posted by t2low at 21:00| Android

2013年11月28日

[Android] SQLのUPDATEで更新された行数を取得する

AndroidのSQLiteDatabaseクラスで用意されているupdate()では無理そうなSQLを発行する必要がありました。
ContentProviderのupdate()内で呼ぶために、UPDATEで変更された行数を知らなければなりません。
しかし、SQLiteDatabaseクラスのexecSQL()は戻り値を返しません。
UPDATEを行う前に同条件で該当行数を調べておくべきでしょうか。

少し調べてみたところ、SQLiteStatement#executeUpdateDelete()を使えば変更された行数を返してくれるようです。
SQLiteStatementはSQLiteDatabase#compileStatement()で取得することが出来そうです。
しかし、このメソッドが追加されたのはAPI Level 11でした。
残念ながら現在開発中のアプリはAPI Level 9から対応させる予定のため、このメソッドは使えません。

もう少し調べてみたところ、SQLにchanges()というメソッドがあるようです。
UPDATEを実行した後、続けてchanges()のSQLを発行すれば良さそうです。
public static int changes(SQLiteDatabase db) {
Cursor cursor = null;
try {
cursor = db.rawQuery("select changes();", null);
if (cursor.moveToFirst()) {
return cursor.getInt(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return -1;
}

上記のようなメソッドを作成し試してみたところ、期待した結果が返ってきました。
(僕が知らないだけで標準のクラスやメソッドで出来たりするんだろうか…?)
とりあえずはこのメソッドを使っていこうと思います。


タグ:android SQLite
posted by t2low at 21:00| Android

2013年11月27日

[Android] IME以外からパスワードを入力させるときでも、文字がわかるようにする

パスワードを入力する画面を作っています。
この画面ではアプリが表示するキーボードを使うため、IMEは使いません。
キーボードが押される度に以下のようなコードを実行していました。
textView.setText(textView.getText() + moji);

この方法でも文字の追加は出来ますが、入力した文字がわかりません。
マスクされた状態で表示されてしまうのです。
通常であればパスワード入力欄は*や●でマスクされる前に1秒程度文字を確認できる時間があります。

特別な処理が必要かと考えましたが、なんてことはありませんでした。
TextViewに文字を追加するためのメソッドが用意されていました。
textView.append(moji);

これで期待通りの動きになりました。

今までTextViewの操作はほとんどgetText()/setText()で行っていたので、append()の存在に気付きませんでした。
思い込みはダメですね。


posted by t2low at 21:00| Android

2013年11月26日

[Android] 壁紙を取得する

Activityに壁紙を取得するメソッドがあったような気がする、ということで、Eclipseの補完機能を頼りにとりあえず使ってみました。
Drawable wallpaper = getWallpaper();

取得できました。ばんざーい。
一応、リファレンスを見てみます。

Context | Android Developers

API Level5でdeprecatedになっていました。
(Eclipseでは警告が出ないのは何故でしょう…?)
何にしても、WallpaperManagerを使った方が良いようです。
WallpaperManager wpm = WallpaperManager.getInstance(getApplication());
Drawable wallpaper = wpm.getDrawable();
// または
Drawable wallpaper = wpm.getFastDrawable();

これでも問題なく取得できました。
壁紙の取得は簡単ですね。
posted by t2low at 21:00| Android

2013年11月25日

[Android] DialogFragmentの呼び出し元で結果を知るには

ダイアログ内のユーザが入力した情報を、ダイアログを呼び出したFragment側で知る必要が出てきました。
つまり、コールバックする仕組みが欲しいと思ったのです。
public class HogeDialogFragment extends DialogFragment {
// ... HogeDialogFragmentの実装

public interface Callback {
void onFuga();
}
}

ダイアログは上記のようにDialogFragmentを継承して実装しています。
そのため、コンストラクタやメソッドを使ってCallbackを渡すのはよろしくなさそう(※)です。
(※システム側でFragmentが再生成されたときに云々という話がありますね)
他に良い方法があるか調べてみましたが、どうもそういった仕組みはないようです。

いろいろと調べて、これしかないかなぁ、というのが以下のような方法

1.9. DialogFragment 実装例 − Kojionilk

android - Callback to a Fragment from a DialogFragment - Stack Overflow

ただ、この方法だと呼び出す側のクラス(ActivityかFragment)がCallbackを実装しなければならないんですよね。
public class HogeHogeFragment extends Fragment 
implements HogeDialogFragment.Callback {
// ...HogeHogeFragmentの実装
}

うーん、うーん…。なんだかイマイチな…。でも、これしか方法がなさそうな…。うーん…。
posted by t2low at 21:00| Android

2013年11月22日

[Android] AlertDialogのgetButton()がnullを返す

DialogのOKボタン(Positiveボタン)の長押しを判定したくて、以下のようなコードを書きました。
Builder builder = new Builder(getActivity());
builder.setTitle("title").setMessage("message");
builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
}
});

AlertDialog dialog = builder.create();
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});


これを実行すると、NullPointerExceptionが発生してアプリが異常終了しました。
11行目のdialog.getButton()でnullが返却されているためです。
Webを検索してみると以下の情報が見つかりました。

Issue 6360 - android - AlertDialog getButton() return null - Android Open Source Project - Issue Tracker - Google Project Hosting

どうもDialogを表示してからでないとnullになってしまうようです。
ソースコードを読んでみるとDialogのonCreate()を通らないと中のViewが生成されないようですね(たぶん)。
Dialogが表示されたところでOnLongClickListenerをセットすることにしました。


Builder builder = new Builder(getActivity());
// ... 省略 ...

AlertDialog dialog = builder.create();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
}
});


多少めんどくさいですが、これなら大丈夫でした。
posted by t2low at 21:00| Android

2013年11月21日

[Eclipse] override

継承元のクラスのメソッドをoverrideしたい場合、どのようにメソッドを書いていますか?
僕はこれまで、Eclipseのメニューから「Source→Override/Implement Methods...」を選んで、Eclipseに生成してもらっていました。
Windowsを使っていた頃は、Altキーを使ってキーボードだけで操作できていたのですが、Macを使うようになってからマウス(タッチパッド)を使わなければいけなくなり面倒に感じていました。
Macではキーボードショートカットを設定すれば良いのは知っていましたが、今回書きたいのはそこではありません。

いつものようにEclipseでコードを書いていて、ふと気づきました。
クラス内でoverrideしたいメソッド名を書き始めてから、コード補完を行うとそのメソッドの足りない記述を補ってくれるんですね!
override_completion.png
これは知りませんでした。
大変便利です。さすがEclipseですね。すごい。
いやー、コードを書くのが捗ります。

posted by t2low at 21:00| Eclipse

2013年11月20日

[Android] Toast


アプリの動作をちょっと確認したいときにToastを使うと便利です。
開発中によく↓こんな形のコードを組み込みます。

Toast.makeText(context, "hoge", Toast.LENGTH_SHORT).show();


ただ、Toastはキューイングされているようで、いくつも上記コードを入れてしまうと呼び出した回数分表示されてしまい、なかなか消えてくれなくなります。
そんなときはキャンセルしましょう。

if (toast != null) {
toast.cancel();
}
toast = Toast.makeText(context, "hoge", Toast.LENGTH_SHORT);
toast.show();


ただ、cancel()しても、パッと消えません。
また、キューイングされている未表示のToastをcancel()したとしても、少し時間を消費します。
元々連続して表示するようには作られていないので、大量の情報を表示したければLogクラスや他のクラスを使った方が良いですね。

Toastの使い方はこちら
Toasts | Android Developers

ToastのAPIリファレンスはこちら
Toast | Android Developers


(最近アウトプットが大事だと考えて、とりあえず何か書こうと思ったけど、書くこと思いつかなくてこんな記事を書いた次第)
タグ:android
posted by t2low at 21:00| Android

2013年11月15日

[Android] Fragment内のWebViewで先読みするにはどうすれば良いだろう?

仕事でAndroidアプリ開発をしています。
先日こんなリクエストがありました。

「広告が表示されるまでに時間がかかってるので先読みできませんか?」

DialogFragmentにWebViewを載せて、onCreateView()内でURLを読み込むようにしていましたが、その時点で読み込み始めたのでは遅すぎるようです。


・DialogFragmentのコンストラクタでWebViewを生成して読み込ませるのはどうか
→WebViewが生成できません…。

WebViewを生成するにはContextが必要ですが、contextがありません…。
getActivity()はこの時点ではnullを返します。
遅くともonAttach()まで呼ばれればgetActivity()がactivityを返してくれるようになりますが、onAttach()が呼ばれるのはいつかと考えると、DialogFragment#show()を呼んだ後ですので、先読みになりません。


・DialogFragmentを呼び出すクラスがWebViewを作ってはどうか
→WebViewが渡せません…。

Fragmentの使い方としては引数なしのコンストラクタが必要です。
Fragment利用時に値が必要であれば、setArguments()で渡す必要があります。
setArguments()の引数はBundleで、BundleにはViewは詰められません。
Viewを渡すメソッドを作れば渡せないこともないと思いますが、それはちょっとねぇ。


結局、解決策がわかりませんでした。
DialogFragmentを使うのをやめ、Dialog風に見せかけたレイアウトを作ってお茶をにごしました。
タグ:android
posted by t2low at 22:00| Android