2011/02/27

WeatherNowでのAppWidget更新バグについて

WeatherNowでは同じホームウィジェットを複数配置しそれらが同時に更新(「Loading...」表示)した場合、全てがきちんと更新されないバグが存在していました。それの原因と対策について。もっとも、きちんとした対策は出来ていないのですが。


まずWeatherNow 1.1.2時点でのコードについてですが、noxiはウィジェットを更新するサービス内でRunnable-Handlerを使っていました。昔このコードを書いた時は(今もそうですが)ホームウィジェットの画面描画諸々を全く理解していない状態で書いていたので、普通にアプリでのデータダウンロード・画面描画と全く同じ感覚で書いていました。結論として、少なくともHandlerを使う必要は全くありませんでした。

・WeatherNow 1.1.2時点でのAppWidget更新用Serviceのイメージ
(ダメな例)



このコードは同時に複数のホームウィジェットを更新処理に移せましたが、原因はよく分かりませんが更新がうまくいかず最後に更新処理に入った物だけウィジェットの画面が描画されました。
ホームウィジェットの場合このコードからHandlerを外してRunnable内でRemoteViews-AppWidgetManagerの更新を行える様で、Handlerは全くの不要物だったようです。

じゃあRunnableも外して

とした場合どうなるのかというと、一度に一つのホームウィジェットのみ更新処理に入りますがきちんと更新されます。ホームウィジェット一つの更新処理が終わると次のホームウィジェットの処理に入る様で、同時更新にはなりません。更新順序はちゃんと記録されていて(?)、恐らく途中でタスクキルされない限りは順番通りに全て更新される様です。
ただしRunnableを使わない副作用として、更新処理に時間がかかると「時間かかりすぎだゴルァ」ってメッセージが表示されます。(でもRunnable使うと情報が混ざる...どうしろと...と思った)

自分用メモ含めた中途半端な記事でした。

2011/02/21

ContentProviderを利用した画像の出力について

前回の記事で、「ImageView#setImageURI」に直接イメージファイルを設定することが出来ないと分かりました。じゃあ、どうすればいいのか。

・Raw Bitmap Data(?)を出力できる何か

を介せばいいので、それが何かを探しました。結論として、タイトルにあるとおり、ContentProviderを利用することで無事出力できました。


ではではその方法を丸々どどんんと紹介。
ちなみに、基本的なContentProviderの利用方法を紹介する気はありません(というか私が知りません)。ここでは外部に画像を出力する方法のみを紹介します。

Manifest.xmlの一部

以下ではnameを「Provider」、autoritiesを「jp.co.noxi.weathernow.contents」にします。
autoritiesはこのProviderにアクセスするためのものです。

で、Provider本体は

以上です。
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
をオーバーライトすれば画像を出力できました。
今回は画像出力だけが目的なので他のメソッドは何もいじりません。


あとは

上で設定したAuthoritiesはcontent://のあとに続く文字になります。


ここまでたどり着くのに今日の午後全て潰した...疲れた。

ImageView#setImageUriについて

WeatherNowのホームウィジェットで、次に実装する機能をテストしていたらどうやらIntentの転送量オーバーで表示が更新されない。うーむ...

RemoteViewsで使えるImageViewメソッド?は
setImageViewResource (アプリのリソースファイルを表示)
setImageViewBitmap (Bitmapを表示、これで転送量オーバーに)
setImageViewUri (Uriから表示)


SDカード内の画像ファイルを表示させたいので、残りのsetImageViewUriを試してみた。で、ここで問題が。
試したコード:


結果:
Uri failed on bad bitmap uri: file:///sdcard/weathernow/plugin/image/test/icon_100s
と怒られました。でも何が原因なのか、全く分からない。

2時間近く悩んだ挙句、「分からないならAndroidのソースコードを見ればいいじゃない!」という結論に

ということで、ImageView#setImageURIから見てみましょう。

なにやらresolveUri()が呼ばれている。じゃあresolveUri()の中身はというと、

ImageView#resolveUri

今回はFileを読み込みたいので、ContentResolver.SCHEME_FILEの部分

Drawable#createFromStreamが呼ばれ、更にContentResolver#openInputStreamが呼ばれているらしい

ではまずCotentResolver#openInputStreamは

UriがFileの場合、new FileInputStream(uri.getPath())が呼ばれていただけでした。

じゃあDrawable#createFromStreamというと

createFromResourceStreamが呼ばれている。見てやろうじゃないか。

Drawable#createFromResourceStream



で、更にBitmapFactory#decodeResourceStreamを見てみる。


BitmapFactory#decodeStream


...ん?w
The input stream that holds the raw data to be decoded into a bitmap.


つまりあれか。ここまで見て、BitmapのRawデータを渡さないとだめっぽいと理解。(英語の解釈を間違えているだけかもしれないけど)。


結論:
ImageView#setImageURIやRemoteViews#setImageViewUriで普通にファイルを参照することは出来ない。

まして(名前からして出来そうな) 
ウェブ上のイメージファイルなんてもってのほか 
である。


PS.
凄い誤解があるかもしれません。
間違いがあればぜひこちらまで。
次回に続く。