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.
凄い誤解があるかもしれません。
間違いがあればぜひこちらまで。
次回に続く。

2011/02/13

Bitmapの透過率を変更する

WeatherNow 1.0.5ではAppWidgetの背景画像の透過率を自由に設定していますが、その方法の解説です。


いつも不思議に思っていたのですが、一部アプリのAppWidgetで「背景画像の透過率を設定で変更できる」ものがあります。普通のActivityですと

(レイアウトxmlの一部)

(Javaファイルの一部)

とやれば一瞬で終わる話なのですが、AppWidgetに何か表示するにはRemoteViewを挟まねばなりません。AppWidget上のImageViewに表示させるにはRemoteViewの「setImageViewResource」や「setImageViewBitmap」が提起されていますが、「setImageViewAlpha」的なものが存在しないため、透過率を直接設定することは不可能です(多分)。そこで今回はResourcesに仕込んだイメージファイルをBitmapに読み込み、そこで透過率を変更した上でAppWidgetに表示する方法を解説します。リソースに透過率をいくつも設定したイメージファイル置いてもいいんですけどね、パッケージの肥大化になるだけですよね。
こちらのサイトを見ているときに気付きました。
これ、どこ探しても載ってなかったんだよな...
偶然の閃きバンザイ

本題に入る前に、Androidで扱う16ビットの色情報について解説します。例えばTextViewでフォントの色を変更する時私は

と指定しています。私もあまりJavaに詳しくないので適当な説明となりますが、「0xff112233」は16ビットのintで「ff」が透過率(00-ff = 0-255、以下同様)、「11」が赤、「22」が緑、「33」が青の情報を表しています。かっこ内に記載しましたが16進法表記となっているのでその点は注意して下さい。
ここまで書いてお気付きの方がいらっしゃるかも知れませんが、Bitmapで透過率の変更とは「112233」はそのまま「ff」の部分だけを書き換えれば可能でした。

次にこの「0xff112233」を2進法表記にすると
「1111 1111 0001 0001 0010 0010 0011 0011 」となります。16進法の文字一つが2進法では0と1、4つの組み合わせで表されています。

やっと本題に入ります。
レイアウトxmlに関していじるものは特にありません。


以上です。これでBitmapの透過率を自由に設定できます。(主に透過率を上げる方向にですが。濃いものを薄くする方が良いと思います。)

まず1の部分についてですが、取得する色情報を
0xAA123456(1010 1010 0001 0010 0011 0100 0101 0110)
とします。これはint情報なのでそのままでは「305419896」となってしまうため、1で色情報の入っている右から24のビットを破棄するためビットを右に24個移動します。

0xAA123456 (1010 1010 0001 0010 0011 0100 0101 0110) が
0x000000AA (0000 0000 0000 0000 0000 0000 1010 1010) になる。

次に2の部分でこの透過情報から引き算で透過率を上げています。先に書いたように透過情報はintだと0-255で表され、0が完全透過、255が透過無しです。上の例の様に透過情報から100を引くと

0x000000AA (170, 0000 0000 0000 0000 0000 0000 1010 1010) が
0x00000046 (70, 0000 0000 0000 0000 0000 0000 0100 0110) になる。

そしてこれを3の部分で左に24個ビットをずらすことで色情報を持たない透過情報のみのintを得ることが出来ます。4,5の部分では左に8ビットずらした後元に戻すことで透過情報を持たない色情報のみのintを作成しています。最後にこの2つのintを合成することで、色は変わらず新たな透過情報を持ったintが完成します。後はこのBitmapをRemoteview#setImageViewBitmapで当てはめるだけです。

2011/02/04

ブログ始めました・ω・

WeatherNowが無事にマーケットに公開出来たということで、ブログ始めてみました。ついったーには書けない長いことや、アプリを作っていく上で感じたことを書いていければいいなと思います。


ちなみに先に公開されていた自動更新プラグインですが、WeatherNow本体を公開する直前での有効インストール数は200程度。意外に使っている人が多いようで大変ありがったかったです。