2013年8月28日水曜日

【C】URLデコードする(UTF-8)

独自鯖を作ってる最中,
クライアントからのpostリクエストボディに「\r\n」も「\0」も何も終端記号が含まれず
feof()とかで終了条件設定しててもむだで
そのままだとfgetc()なりfgets()なりのところで固まってしまうため
Content-Lengthの分だけ監視する必要があった
…のにもまず驚かされたが,
マルチバイト文字がURLエンコード(パーセントエンコード)されることを忘れていて
デコーダを実装するのに軽く絶望していた.

が,こんな簡単に実装できるとは.

まずはUTF-8のおさらい.
Wikipedia[UTF-8]の表を見る.
ん,そういえばこれどこかでやったな…
と,思ったが,残念ながら以前やったのはUTF-16toUTF-8だった.
しかし,土台部分や考えは流用できそうだ.

%XX%XX%XX%XX…と続くエンコード文字列のまず先頭を取得する.
先頭の%XXこいつのXX部分を見て,Wikipediaの表と対応させて
それが0x80未満なら1byte.
0xC2-0xDFなら2bytes.
0xE0-0xEFであれば3bytes…
まぁ4bytes以上の文字は置いといてそんな感じか.
1byteということは%XXこれが1文字.
2bytesということは%XX%XXこれで1文字,
3bytesも同様に%XX%XX%XXこれで1文字.
UTF-8というのは1文字のバイト長が1byte~6bytesという可変長で表現される.

URLエンコードではそのバイト列をバイトずつ区切って,
頭に%をつけてASCII文字のみで送れるようにしたものである.
また,半角スペースが「+」に変換される.

URLデコードではその逆をやる.
まず「+」を全て半角スペースに置き換える.
次に1つ目の%を探して次の2文字を取得する.
こいつの値を見て,その文字が何バイト続くかを判定,
そのバイト分取得したら文字に直す.

…と言葉で書くよりプログラムで書いたほうがわかりやすいな.
今回のプログラムではURLデコードを行うurlDecode関数と,
文字列を置換するreplace関数がある(「+」を半角スペースに直すため用いる).


【Windows8.1】MSDNでもWin8.1RTM配布は10/18

あまりにも反対が多すぎたためか9/9に撤回されました.
9/10現在MSDNパートナーにWindows8.1 RTM公開がされています.

Windows8.1及び8.1RTのリリースがHWパートナーに対し行われた.
従来,MSDNやTechNetでの配布も2週間以内には行われていた.


Windows 8
RTM: 2012-08-01, MSDN/TechNet Availability: 2012-08-15, GA: 2012-10-26

Windows 7
RTM: 2009-07-22, MSDN/TechNet Availability: 2009-08-06, GA: 2009-10-22

Windows Vista
RTM: 2006-11-08, MSDN/TechNet Availability: 2006-11-17, GA: 2007-01-30


ということはもう数週間以内にはMSDNやTechNetではRTMが入手可能に…
と思いたかったのだが,
今回はどうもそうはいかないようである.

ソースはWindows Blog.
http://blogs.windows.com/windows/b/bloggingwindows/archive/2013/08/27/readying-windows-8-1-for-release.aspx

While our partners are preparing these exciting new devices
we will continue to work closely with them as we put
the finishing touches on Windows 8.1 to ensure a quality
experience at general availability on October 18th.
This is the date when Windows 8.1 will be broadly available for
commercial customers with or without volume licensing agreements,
our broad partner ecosystem, subscribers to MSDN and TechNet,
as well as consumers.

暴訳すると,HWパートナーにはWindows8.1は配った.
サブスクライバは一般消費者と同じ10/18な!
といったところか.

コレに対し,其のWindows Blogでのコメント.

controlz controlz 145 Posts
August 27, 2013
I'm disappointed that it won't be available on TechNet until October - it may not be perfectly stable yet, but the RTM build is surely more stable and complete than the preview build (9431) or the recently leaked 9471 build.
「10月まで手に入らないのは失望したわ.Preview版とか最近リークされた9471ビルド版みたいにRTMも不完全なんじゃね」

segobi segobi 0 Posts
August 27, 2013
Congratulations for releasing it to Technet and MSDN so late. You are losing your partners.
「サブスクライバへの悪対応おめでとう!パートナー失うわ」

…以下,サブスクライバへの先行リリースがなくなったことに対する
皮肉と失望の渦巻くコメント欄であった.

そりゃあシステム屋としては8.1リリースと共に新OSを勧めることが難しくなるし,
SW屋やPCメーカー以外のHW屋も即対応を謳うことができなくなる.
そうするとユーザーの導入も伸びづらくなる.

まぁサービスパックと考えるとこれまでが大げさな対応だったのかもしれない.
Vista→7以上にSP感がするし.
これはWindows8SP1だ…Windows8SP1…SP1…


わざわざWindows離れを加速させてる気がしなくもない.

2013年8月27日火曜日

【未検証】モバイルブラウザの不都合な動作

未検証シリーズ.検証するつもりもない.
ここ数日,クロスブラウザと闘い続けているのだが,
(デスクトップブラウザが正しいとすれば)想定外の動作をしてくれることがある.

iOS SafariでiframeのVisibilityがおかしい
<iframe ~~~~ style="visibility:hidden"></iframe>
といった形でiframeにhiddenを設定し,
JavascriptからvisibilityをvisibleにしたりhiddenにしたりするようなHTML.
突然iframeが表示されることがあり,
また,iframeは表示したらhiddenにしても非表示にならない.
なお,デスクトップブラウザやAndroidブラウザではちゃんと非表示になる.
iframeを使うのがそもそもの間違いなんだろうが,
Google Mapsの埋め込みはiframeを使うんだよな.
opacityで非表示にしたりz-indexでレイアウトの後ろにしたり
そういったことで対応できるのかもしれない.
モバイルブラウザ的には埋め込みの表示/非表示ではなく,
Google Mapsのサイトへのリンクが正解なのではと思い,リンクを貼ることにした.

Android 標準ブラウザの画像に対するmax-heightの挙動
<img ~~~ style="max-height:100%" />
といった形で画像をブラウザサイズに抑制する場合,
ページロード時に表示されるアドレスバーの領域を除いて100%と計算される.
まずはまぁこれはいい.
アドレスバーは描画領域に含まれないんだからちゃんと描画している部分が100%だから正しい.
しかし,この状態で画面を引き上げ,アドレスバーをスクロールアウトさせると,
ブロックはスクロール後のより広くなった領域を100%と考えているのに,
imgは描画がおかしくなる.
具体的には描画領域の高さが変更されたのは確認したが,
画像の高さを反映させずに幅だけ変更させる.
つまり,画像が横に伸びるのだ.
スクロールさせて表示するような作りのページならいいが,
1枚絵を表示させるだけのページだと,できれば絵を画面内に収めたい.
だからmax-height指定して画面内に収めたのにアドレスバーが変な挙動を誘発させる.
これは今のところお手上げ.

2013年8月26日月曜日

【Windows8.1 RP】RT版のIEは横スクロールを避けるように作られている

Modern UI(旧称:Metro Style)のIEは横スクロールを避けるように作られている.
可能な限り.

今回のエントリは画像がメイン.
(領域の)大きい画像が続くので拡大しないと分かりづらいかも.

Webサーバを作っていて,クロスブラウザのレイアウトを目指すため
いろいろなブラウザで動作を確認していたのだが,
Modern UI版IEの挙動が面白かったのでメモ.

Modern UIといえばWindows8で導入された
フルスクリーンアプリのプラットフォームである.

他のモバイルプラットフォームでは同時起動アプリは1つのものが多いが,
Modern UIでは左右どちらかに細長い画面でアプリを配置して,
狭い領域と広い領域の2つまで同時にアプリを表示させることができる.
これをスナップというのだが,
Windows8ではそのスナップする狭い領域のアプリの幅は固定されていた.
Windows8.1(Preview)ではそのスナップが進化し,
アプリの幅をユーザがある程度自由に動かすことができるようになった.
これは過去のエントリでも書いている.

そこで,フルスクリーン状態のIE,スナップ状態のIEと
いろいろなサイズで独自Webサーバの挙動を見てみて,
レイアウト崩れが起きないか確認していた.

今回テストで用意したHTMLファイルの内容は以下のとおりである.


青の領域は本来のディスプレイの半分の領域で,
今回使用しているディスプレイが1920x1200なので,960x1200の領域である.
赤の領域はナゾの領域(後述)で1024x1295の領域である.
緑の領域は何でもいいから大きいの,ということで,
4k2kの3840x2160の領域である.

まずはフルスクリーン状態(幅1920)で表示するとこんな感じ.
青領域下部の枠線がぴったり表示されているのが分かる.
フルスクリーン状態


次に,その状態からだんだんと幅を狭くしていこう.
幅1397
幅1397


幅1308
幅1308


…と綺麗に幅のみが縮小しているのが分かる.
幅を削ったのだから幅が減るのは当然で,
この挙動がWebUIとして想定される挙動である.

先ほどの状態から更に幅を減らすと,
幅を自由に決められず,
自動的にディスプレイの半分のサイズになる領域になる.

アプリとアプリの間にスペーサーがあるのでディスプレイの丁度半分の幅ではないが,
幅949
ディスプレイの半分の幅


さて,ここで高さが変わってしまった.
(アプリの高さが青線よりも高くなった…というか縮小された)
後述すると書いたナゾの領域の幅・高さになったのである.
この赤領域の数値はナゾであって,
HTMLで指定した値はピクセル調整して丁度になった値なのである.

そこから更に幅を狭めていくと,
より縮小が進んで
幅811
幅811


幅569ではとうとう4k2kサイズの高さに並び,
幅569


最終的には
幅320(これ以上は縮小できない)
最小幅


こんな状態になった.
特筆すべきなのは縮小率は固定で,
半分の状態の赤枠の幅が表示されたまま縮小されていく.

つまり,Modern UI版IEでは
スナップ幅がディスプレイの半分以下になると
その幅のまま表示が縮小される.
幅が固定なので,変な作りにしない限り
横にスクロールすることはほぼないだろう.
ただし,幅を狭くしていくと表示倍率が下がっていくため,
文字が豆粒になっていくのである.
読めない…

2013年8月23日金曜日

【Android】View.INVISIBLEとView.GONEの違い

さきほどのエントリでView.INVISIBLEとView.GONEについて触れたが,
この2つ,一言で表せば
「VIEWを消す」
で同じなのだが,
厳密には大きく違う.

INVISIBLEに関しては,
触れないし(Clickイベント等が発行されない),見えないけど,確かにそこにいる
オカルティックな存在.

GONEは
アイツはもう消した!
というだからお前は誰なんだよという存在.

分かりづらいかと思うので,サンプルアプリを作ってキャプチャしてみた.

1つめの例
LinearLayoutの中に
RとGとBのTextViewがある状態.
RとBはそれぞれ50dipの幅を持っており,
Gはlayout_weight指定してその残りを占めるようにしている.
ボタンはBの処遇を決めるボタンである.

B:Visible



B:Invisible



B:Gone

これで分かるのが,
Bが消えたときにInvisible/GoneとでGの挙動が違う.
Invisibleの場合は見えなくなったけど配置としてそこにBがあるという判定で
Gは振舞っているが,
Goneの場合は配置からBが消された状態で
GがBのあった場所も占めるようになっている.

これでどう実践的になるかというと,
例えば画面上部にタイトル,進むボタン,戻るボタンがあるようなUIを考える.
さきほどの例と同じようなUIなのだが.
そんななかである条件で進むボタンや戻るボタンを非表示にしたい.
といったときに,Goneを指定してしまうとタイトルがセンターからずれる.
Goneを指定してセンターからずれた状態
タイトルはセンターを死守して欲しい場合はInvisibleを使うべきである.
Invisibleならセンターのままでいてくれる


2つめの例
先程はLinearLayoutだったが,今回はRelativeLayout.
RelativeLayoutでレイアウト依存される側のViewが消えた場合にどうなるか.
今回はGをRelativeLayoutのCenterに配置し,
Gの左にRを,Gの右にBを配置.
ボタンによってGの処遇を決定する.

G:Visible



G:Invisible



G:Gone


Invisibleはレイアウトが崩れなかったが,
GoneにするとGの配置に依存していたBやRが行き場をなくして
親であるRelativeLayoutの左上に配置された.

これは注意すべきである.

こう見ると,InvisibleもGoneも一言でいえば確かに
「Viewを消す」
なのだが,圧倒的に使い方を考えなければいけないことが分かる.


ちなみにこの他に「消す」ことのできるメソッドとして
・setAlpha ※API LV.11~
・setAnimation(AlphaAnimation)
があるが,これらは共に半透過を扱うものであるため,
完全に消しても実物はそこに存在し,
レイアウトに影響を及ぼさず,
触れる(クリックイベント等が発生する)
見えないだけである.


【Android】Spinnerは配置しないとイベント発行しない

表題でSpinnerと書いたが,あらゆるViewがそうかもしれない.
Spinner(いわゆるドロップダウンリスト)では
OnItemSelectedListenerを使って,選択即反映をさせることが多い.
リストのアイテムが選択されたときに発行されるイベントである.
そのイベントだが,Spinner自体をActivityに配置しないと発行されない.

配置しないとそもそもドロップダウンリストが表示できない…
というわけではないから悩むことになった.
具体的には


別に用意したTextViewをクリックするとリストだけ表示する場合.
Spinnerは画面に配置せずに,
TextViewがクリックされたときにperformClickメソッドでSpinnerを叩く.
(このperformClickメソッドはViewからの継承のため,他のViewでも使えるはず.)
すると,リストは表示される.
リストが多いとスクロールもできるし,
タップすれば選択色が光ってリストも消える.
まるでちゃんと動作しているようにみえる.

しかし,OnItemSelectedイベントは飛んでこない.

ここで,Spinnerを画面に配置してやる必要があった.
これは配置サイズが0dipでもいいし,
Visibility:INVISIBLEでもいい.

ただし,配置してないのと同様の扱いになるVisibility:GONEではやはり
イベントが発生しない.

width/heightともに0dipに設定した上でVisibility:INVISIBLEに指定した
Spinnerをレイアウト最下部に配置することにした.

余談だが,Spinnerは画面に配置されると同時に
position:0選択のイベントが発行されるらしく,
何らかの処理を設定していたらそれに注意が必要である.
選択してActivityを移動とかの処理を入れていると
配置と同時にActivity移動なんてことになりかねない.

2013年8月21日水曜日

【Server】Content-Lengthをきちんと指定しないとインライン画像が表示されない

今回はHTTPサーバのハナシ.
HTTPサーバなんてApache使えばいいから基本知らなくていいことなんだろうけど
特殊鯖を作るとなるとそういうわけにもいかない.

IMGタグなんかでウェブサイト内に表示する画像,
つまりインライン画像が表示されないことがある.
ことがある…というのも,ブラウザ環境に左右されるのである.

Firefox23,Android2.1 標準ブラウザ,iOS6 Safari
ではちゃんと表示されるのに
Chrome28/29,Android4.0 Chrome
では表示されない.
おかしいな,MIMETYPEもちゃんと指定しているし…
と調べまわった結果,
インラインではサムネイルを表示させるのに,
Content-Lengthは元画像のサイズを送っていたことが判明.
(90*90のサムネイルなのに4MBとかいうContent-Lengthを送っていた)
つまり鯖のミスだったのである.
Chromeはそこんところ厳格らしい.

Content-Lengthはこのサイズだろ?
俺がもらったのと違うぜ,壊れてんじゃね?
ひょっとしたらウイルスとかなんかなんじゃね?
ってことなのかもしれない.

Content-LengthをきちんとChromeに送ってやったら
インラインで表示された,めでたしめでたし.

2013年8月7日水曜日

【Android】Serviceを設定したら終わらせよう

AndroidのWidgetを作る場合には
クリックイベントを処理したり,
はたまた時計を作ろうとAlarmManagerをセットしたり
Activityを作るのと違ってServiceを多用することになると思う.


こんな感じでサービスを登録するが,
サービスはアプリ(Activity/Widget)を終了させても終わってくれない.
再起動するか,
ユーザが手動で【設定>アプリ>実行中】から停止するかしない限り
サービスが残りっぱなしになる.

時計ウィジェットを作ろうとAlarmManagerを再帰呼び出しさせた日には
サービスを手動で終了させても次のアラーム時にサービスが勝手に復帰する.
こうなったらアプリをアンインストールするか
再起動するかしないと消えない.

なので,アプリを終了するときにサービスを停止させよう.
ウィジェットならばウィジェットがHOME配置から消されたら終了させよう.

ウィジェットの場合,HOME配置から消されるとonDeletedイベントが発生する.
なので,onDeletedをオーバーライドして,
上記の例に倣うと


サービスを登録したときと同じ方法でIntentを宣言し,
stopServiceを呼び出す.
これでサービスの停止が完了である.

なお,サービス登録時にIntentを作成しているからといって
Intentをクラスフィールドにして停止のときもそのまま再利用~
などと考えてもIntentの中身はnullになっているため,不可能である.
クラスフィールドにしてもいいが,
再度Intentのコンストラクタを呼ばないといけない.

開始と停止とで(作り方は同じだとしても)違うオブジェクトを作って
それで本当に停止できるのかとちょっとオブジェクト指向の考えからいくと
不安になるかもだが,これで停止ができるのである.

次に,時計ウィジェットなどでアラームを設定した場合にも
作成時と同じ方法で
Intent/PendingIntent/AlarmManagerを作成し,
キャンセルしてやることで解除される.
すなわち,


このようにセットしたのなら


このようにキャンセルすることになる.
サービスの停止の前にアラームの解除をする必要がある
IntentのsetActionも解除時に必要.
このsetActionが足りなくても解除に失敗する.

何度「まだいる!」と思わされたことか.