Techracho

AppWidgetManager.updateAppWidgetの注意

このエントリーをはてなブックマーク Share
2010.06.11    android, java, 馬場   タグ: , , , —    baba   

Androidのホームスクリーンウィジェットを更新するには、AppWidgetManager.updateAppWidget()を使います。

RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
remoteViews.setTextViewText(R.id.Text01, "hello");

AppWidgetManager manager = AppWidgetManager.getInstance(context);
manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);

これを連続で呼ぶときには、少し注意が必要です。

たとえば、ボタンのクリック操作にPendingIntentを割り当てるような処理は初期化時のみに行い、ウィジェットを使用中には表示の更新のみを行うような場合、以下のようにやりたくなると思います。

private void init(Context context) {
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
	remoteViews.setTextViewText(R.id.Text01, "");

	PendingIntent clickIntent = PendingIntent.getBroadcast(context, 0, new Intent("MYACTION"), 0);
	remoteViews.setOnClickPendingIntent(R.id.Button01, clickIntent);

	AppWidgetManager manager = AppWidgetManager.getInstance(context);
	manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);
}

private void update(Context context, String message) {
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
	remoteViews.setTextViewText(R.id.Text01, message);

	AppWidgetManager manager = AppWidgetManager.getInstance(context);
	manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);
}

このコードは基本的に動くのですが、initの直後にupdateを呼ぶと、以下の問題が発生することがあります。

「同じウィジェットを2つ以上設置したとき、2つめのウィジェットのボタンが反応しない」

init→updateの間にスリープを入れれば動きます。
どうやら、updateAppWidgetが完了しないうちに次の処理をして、ボタンへのpendingIntentの反映が失敗してしまうような動作です。

解決策としては、毎回ボタンの動作を設定する(updateを廃止してinitにmessageを渡せるようにする)か、initの後にスリープを入れるくらいしか、今のところ見つけていません。

なんで2つあったときだけだめなのか、原因を調べてみないといけませんね・・・
何かポカをしていたら教えてください。

androidでのローカル保存

このエントリーをはてなブックマーク Share
2010.06.10    android, java, 小川   タグ: —    kazuma   

Web開発部の小川です。初投稿です。

広く浅くをモットーにいろんなジャンルの話をしていきたいと思います。

今回はandroid開発でのローカル保存についてです。

androidの開発をしているとアプリケーションを終了しても保持してもらいたい情報があったりします。

ローカルで情報を保存する方法にはファイルで保存する方法とアプリケーションのデータ保存領域を使って保存する方法があります。

ちょっとした情報であるならアプリーケーション保存領域を使うのが簡単です。

今回は、アプリケーション領域を使う保存方法について説明します。

使うクラスは「SharedPreferences」と「Editor」です。

import android.content.SharedPreferences;
	import android.content.SharedPreferences.Editor
	import android.content.Context;

2つのクラスをインポートします。

次に、実際の情報保存方法について説明します。

SharedPreferences common = getSharedPreferences(COMMON_KEY,MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
Editor editor = common.edit();
editor.putString(NAME, name);
editor.putString(PASSWORD,password);
editor.commit();

1行目のgetSharedPreferencesはContextクラスに抽象メソッドとして登録されています。第一引数には共通キーを指定します。

例えば、COMMON_KEY=”hoge”とします。そうすると、別ActivityのgetSharedPreferencesの第一引数で”hoge”を指定した場合、同じプリファレンスを得ることができます。

3行目、4行目にあるputStringは文字列を保存するときに使用し、NAMEとPASSWORDは保存した情報を得るためのキーとなっています。

putString以外にも以下のようなメソッドがあります。

  • getBoolean(String key, boolean defValue)
  • getFloat(String key, float defValue)
  • getInt(String key, int defValue)
  • getLong(String key, long defValue)

5行目のcommitは忘れないように。putしただけでは保存されません。

次に情報の取り出し方です。

SharedPreferences common = getSharedPreferences(COMMON_KEY,MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
String name = common.getString(NAME,"");
String password = common.getString(PASSWORD,"");

getStringの第2引数はキーに対して値がないときのデフォルト値となります。

apktool

このエントリーをはてなブックマーク Share
    android, java, 馬場   タグ: —    baba   

apkファイルからmanifestなどを確認するやり方 ですが、apktoolを使えば簡単完全にできました。

apktoolとは、apk解凍ツールみたいなものです。

http://code.google.com/p/android-apktool/
上記URLから、apktool-1.1.1.tar.bz2 みたいなのを落として、中にあるapktool.jarをC:/Windowsに置きます。
apktool-install-windows-*.zip みたいなのを落として、中にあるファイルたちにパスを通します。

あとは、

apktool d hoge.apk hoge

とやれば、hoge.apkをhogeフォルダに展開してくれます。

これならmanifestファイルも全部読めますし、画像データなども確認できます。
色々参考になりそうですね。

なお、ソースコードはコンパイルされていて .smali という拡張子になっています。
マシン語じゃないので頑張れば読めますが、少し骨が折れます。
Intentの受け渡し部分を解読するくらいならできそうです。

MusicFlyのお気に入り登録件数が増えました

このエントリーをはてなブックマーク Share
2010.06.07    MusicFly, android, java, 芝原      shibachan   

MusicFlyではバージョン2.0からの目玉新機能として、
気になったアーティストやアルバムをお気に入りとしてブックマークしておける仕組みが実装されています。
大変好評を得ている機能なのですが、サービスの負荷対策として利用者毎のお気に入り登録件数を3件に制限させていただいておりました。

ですが、先日ある利用者の方から『とても魅力的な機能なのに、たった3件だけでは悲しすぎる』という意見をいただき、
さらには負荷対策の目途が立ってきたこともあり、本日より登録件数を3件か5件へ引き上げることにしました。
今回は微小な増加ではありますが、少しでもユーザーのみなさまに満足いただけるよう今後も取り組んでいきます。

○得情報

お気に入り件数を5件以上に増やしたい方は、

  • 端末のデバイスID(IMEI)
  • 使用感想
  • 改善点
  • 意見、要望など

をコメントフォームかbpsdroid@gmail.comまでお願いします。

開発に役立つコメントをお待ちしております。

なお、デバイスIDの取得方法についてはこちらを参考にしてください。

MusicFlyバグレポート

このエントリーをはてなブックマーク Share
2010.06.04    MusicFly, android, java, 芝原      shibachan   

バージョン2.0からMusicFlyにユーザーからバグレポートを送信していただく機能を付けました。
これがまた便利なもので、開発時に気付けなかった実行環境の違いによって生じてしまうものなどにも気づけ、
なによりその数によって優先順位もつけやすくなりました。

寄せられるバグをひとつひとつ潰していくうちに
公開2日目で4回のマイナーアップデートを行うことができました。

これも一重にMusicFlyを使用してくださっているユーザーの皆さまのおかげです。
バグレポートの数が0となり、より安定性の高いサービスを提供できるよう開発者一同がんばります。

MusicFlyが快適になりました

このエントリーをはてなブックマーク Share
    MusicFly, android, java, 芝原      shibachan   

先日新バージョンとなったMusicFlyですが、すでに多くの方にご利用いただき好評を得ています。
そんな順調なMusicFlyですが、昨夜から予想以上のアクセスが集中した結果、レスポンスが遅いというコメントをいただきました。

早急に対応するため、本日サーバー側のサービスをチューニングし、快適なレスポンスを実現することができました。
ご不便をかけましたユーザーの皆さま申し訳ございませんでした。
格段の速さの違いにびっくりしてください。

Android アプリケーションを強制終了させる

このエントリーをはてなブックマーク Share
2010.06.01    android, java, 芝原      shibachan   

アプリケーション上で起動しているActivityをすべて終了する方法。

ActivityMangerがうまくやってくれるようです。

ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
activityManager.restartPackage(getPackageName());

こんなスマートなやり方があったとは。

AppWidgetからダイアログを起動

このエントリーをはてなブックマーク Share
    android, java, 馬場   タグ: —    baba   

Androidのウィジェットでも、設定画面が欲しいことは多々あります。

マニフェストに直接書くと、設置時に毎回出てきて鬱陶しいので、タッチしたら設定画面、などとやるのが普通だと思います。

簡単な設定はダイアログで済ませたり、startActivityForResult をやりたくなりますが、AppWidgetからこれらはできません。

例:AppWidgetProviderからAlertDialogを起動した場合

ERROR/AndroidRuntime(1840): Caused by: android.view.WindowManager$BadTokenException: Unable to add window — token null is not for an application

AppWidgetは通常の表示状態のActivityでは無いので、ダイアログの親にはなれないということですね。

ということで、どうしてもダイアログの設定画面にしたい場合は、

  1. PendingIntentでActivityを起動
  2. 起動されるActivityには、マニフェストでandroid:theme=”@android:style/Theme.Translucent.NoTitleBar” を指定しておく
  3. Activityの起動直後に、ダイアログを出力する

という手順が必要そうです。

透明にしてダイアログを出せば、終了処理を間違えない限り、まあバレないですよね。

startActivityForResultの代わりとしては、ブロードキャストインテントを使います。

Intent intent = new Intent("jp.bpsinc.android.example.ACTION_NOTIFY");
sendBroadcast(intent);

ACTION_SCREEN_ONを受け取る(2)

このエントリーをはてなブックマーク Share
2010.05.31    android, java, 馬場      baba   

ACTION_SCREEN_ONを受け取るの続きです。

ACTION_SCREEN_ONを受け取るには、コードで明示的にregisterReceiverする必要がありました。

しかし、AppWidgetは表示時に生成されたあと、すぐに終了してしまいます。
コンストラクタでログを吐けば、onUpdate()とonDeleted()が呼ばれたときでは、それぞれでコンストラクタが呼ばれて、別のインスタンスが生成されるのが確認できると思います。

したがって、onUpdate()でregisterReceiverしてしまうと、終了時にunregisterReceiverする手段が無くなってしまうため、そのままでは使えません(どんどんReceiverが増えていきます)。

これを解決するには、サービスを使えば良さそうです。

たとえば以下のようなサービスを作っておいて、

public class ScreenStateService extends Service {
	private BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
				sendBroadcast(new Intent("jp.bpsinc.ScreenStateService.SCREEN_ON"));
			} else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
				sendBroadcast(new Intent("jp.bpsinc.ScreenStateService.SCREEN_OFF"));
			}
		}
	};

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		getApplicationContext().registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
		getApplicationContext().registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
	}

	@Override
	public void onDestroy() {
		getApplicationContext().unregisterReceiver(mReceiver);
		super.onDestroy();
	}
}

AppWidgetProviderのonUpdate()で、このサービスを起動します。

@Override
public function onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    context.getApplicationContext().startService(context.getApplicationContext(), ScreenStateService.class));
}

ウィジェットの終了時には、忘れずにサービスを止めておきます。

@Override
public void onDeleted(Context context, int[] appWidgetIds) {
    context.getApplicationContext().stopService(context.getApplicationContext(), ScreenStateService.class));
    super.onDeleted(context, appWidgetIds);
}

こうしておけば、マニフェストファイルで指定することで jp.bpsinc.android.SCREEN_ON などのイベントを受信できます。

このままだと複数ウィジェットに対応していないので、カウンタ方式などでもう少し工夫した方が良いですが・・・

Android AppWidgetで使えるコンポーネント

このエントリーをはてなブックマーク Share
2010.05.29    android, java, 馬場      baba   

BPS Android Libraryにも、かなりたくさんのクラスが溜まってきました。

ImageViewなどを継承した便利なクラスもいくつかあるのですが、AppWidget開発の際に使ったら ClassNotFoundException が発生してしまいました。

理由は単純で、AppWidgetでは使えるコンポーネントが制限されていて、ImageViewなどのサブクラスは使えないです。
完全に見落としていました。

http://developer.android.com/guide/topics/appwidgets/index.html

A RemoteViews object (and, consequently, an App Widget) can support the following layout classes:

FrameLayout
LinearLayout
RelativeLayout
And the following widget classes:

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView

Descendants of these classes are not supported.

リファレンスはしっかり読みましょう。

« 新しい投稿古い投稿 »

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.