2013年9月6日金曜日

UIAppearanceの使い方

iOS6の時点の知識です。ついでに半年ほどAndroidしかやってないです。

iOS7でどう変わるんでしょう。試しにDeveloperプレビュー落としたら、手元のlionでは動かなかった。さすがAppleさん。

---

iOS5からUIAppearance Protocolが追加され、ほとんどのUI部品が準拠しました。

これはAppearanceプロキシの提供を宣言するプロトコルです。Appearanceプロキシに対して背景画像やテキストの属性を指定すると、同一クラスのUI部品全てに変更を加えられます。

フレームワークのUI部品に対して直接使用しない方が無難

チュートリアルではUINavigationBarなどを直接書き換えていますが、これは予期せぬ結果を招くことがあります。

例えば、UINavigationBarを書き換えると、UIPopover下で表示されるNavigationBarも変更されてしまいます。UIPopoverの外観を変更する手段は提供されておらず、残念な見栄えになること請け合いです。

+appearanceWhenContainedIn:メソッドを使用して、UIPopoverとして表示されたときには変更を無かったことにするという手法も考えられます。-setBackgroundImage:を書き換えても、nilで上書きすれば元に戻すことができます。

しかしそんな小細工をするくらいなら、最初からUINavigationBarのカスタムなサブクラスを作り、そのクラスのAppearanceプロキシを操作した方が良いと思います。

カスタムなUINavigationBarを用意したならば、それを利用するUINavigationControllerサブクラスも作成する必要があるのでは?と思うところですが、iOS5からUINavigationControllerの生成メソッドとして、任意のUINavigationBarおよびUIToolBarサブクラスを指定できる-initWithNavigationBarClass:toolbarClass:が追加されています。

またカスタムサブクラスを使うメリットとして、Appearance操作の影響範囲を自分のアプリのみに限定することができます。外部のSDK等が出した画面まで書き換わる事態を防ぐことができます。

UIAppearanceを操作するタイミング

大前提として、Viewが表示される前に操作する必要があります。

UIViewControllerのコールバックメソッド、-viewDidLoadで直接Viewに対して-setTintColor:などを実行した場合、その場で変更が適用されますが、Appearanceプロキシを通して加えた変更は、再度-loadViewが実行されないと適用されません。

そのため、より上位のApplicationのスコープ、-application:willFinishLaunchingWithOptions:のタイミングで行うのが一つ目の選択肢です。常套手段としてはこちらになります。

もう一つの選択肢として、当該のViewクラスがObjective-Cランタイムに読み込まれた時点で行う手があります。つまり+loadまたは+initalizeをオーバーライドして、Appearance操作を行います。

後者の方法には、独自のサブクラスを経由して操作する、という規約を作る場合、そのクラス内部に外観変更操作が隠蔽されるというメリットがあります。

しかし、Appearanceプロキシに対する操作を特定のクラスの責務とした方が、規模が大きくなった場合の混乱が小さいかもしれません。

UIButtonへのAppearance操作について

ボタンのサイズは文字列長に応じて変わるため、通常不定です。

-resizableImageWithCapInsets:resizingMode:でマージンと拡縮方式を指定して背景画像とすることで、リサイズに対応したカスタムボタンを作れます。用意するのは一つの画像と、Appearanceプロキシを操作する数行のコードだけです。

しかもどっかの9Patchと違って、パターンの繰り返しに対応しています。いまや完全にどっかの9patchの上位互換です。(ただしiOS6以上限定)

例えば、HGDetermineButtonやらHGCancelButtonのようなサブクラスを作成し、個々の見栄えを変更することで、-loadViewから装飾のためのコードが一掃されます。生成するクラス名から各ボタンの役割も明確となります。

xibを使っている場合は、UIButonを配置した後で、ボタンのクラスを変更するだけです。

ただしAppearanceプロキシによる変更は実行時に行われるため、InterfaceBuilder上ではただのRoundRectButtonとして表示されるので、xibを見ただけでは実際の表示が分からないという事態を引き起こしますが…。

ボタンのLabelを変更する場合、Appearanceプロキシに対してsetTitleColor:forState:で行います。

---

余談として、UIButton内のラベルはUILabelで表示している、ということを利用して、

[[[UILabel class] appearanceWhenContainedIn:[HogeButton class], nil] setTextColor:[UIColor redColor]];

のような記述ができると考えるかもしれませんが、このコードは無視されます。setTitleColor:forState:で設定された色をボタンに適用するタイミングより、前に実行されてしまうためでしょうか。

しかし、2011年の10月に書かれたUIAppearance で色や画像を変える - Cocoaの日々ではUILabelの色を操作するとボタンの色も変わると書いてある(iOS6では変わりません)ので、iOS5.0時点だと挙動が変わるかもしれません。

どの属性がUIAppearanceに対応しているのか?

UIAppearanceが使いにくい理由の9割は、Appearanceプロキシに変更を加えたとして、実際にその処理が適用されるかどうかが分からない点です。

「UIAppearance Protocolに準拠している」という事実は+appearanceメソッドでAppearanceプロキシを返却することを宣言しているだけで、実際どのプロパティ、どのメソッドがプロキシ経由の操作を受け付けるのかについては分からないのです。

これについては当該Viewのヘッダを見て、プロパティやメソッドの定義にUI_APPEARANCE_SELECTORが付いているか確認する、という手段しかないと思います。

あれ、さっき使ったsetTitle:forState:にはUI_APPEARANCE_SELECTORは付いてなかったんですけど…。

---

このUI_APPEARANCE_SELECTORは、UI Appearance Proxy APIに参加していることを宣言するだけで、実際に何らかの機能を伴っているわけではないようです。

なぜか抜けているプロパティやメソッドもあるため、書いていないのに機能するケースもあります。

しかもリファレンスにはどのメソッド/プロパティがAppearance Proxy API対応しているのかの記述はなく、またバージョンによって対応箇所が増えている一方、API Diffsでは確認できないようです。

もしiOS5対応が必要な場合はiOS5のframeworkのヘッダを読むわけですが、しかしそのヘッダの情報が正しいという保証もなく、結局のところiOS5実機を用意して確認するのが一番確実でしょう。

UIAppearanceContainerプロトコルの意味は?

UIAppearance準拠クラスを見ると、UIAppearanceContainerプロトコルにも準拠しているものがあると思うんですけど、何も宣言していないんですよね。

何かのマーカーだったと思いますけど、忘れました。

結局のところUIAppearanceは使うべきなのか?

プロトタイプをさくっと完成させるのには超便利だと思います。

しかしプロダクションコードで使うには、リファレンスに書いてないブラックボックスが怖すぎるのと、そもそも国内開発者でこの機能を把握してる人の割合が謎で、多人数開発ではとても怖くて使えないかなぁ…。

2013年9月1日日曜日

Geocoderクラスは確実に使えるとは限らない

Androidにはジオコーディング/逆ジオコーディング(地理座標から住所の逆引きなど)を行うことのできるandroid.location.Geocoderというクラスが用意されています。

Google APIs付きのSDKでビルドするだけで利用できるので、非常にお手軽なのですが、Geocoderはプロキシクラスで、実際の処理を行っているのはバックエンドで動作しているLocationManagerServiceとなります。

このLocationManagerServiceが何らかの理由で死ぬ(メモリや電源が極端に消耗するとシステムが自動的に殺すことがある)と、端末を再起動するまでGeocoderは使用不可能になります。この状況でもGeocoder.isPresent()はtrueを返すので、実行するまで成功するかどうか分かりません。

Service not AvailableというメッセージのIOExceptionで失敗した場合、大体これのせいです。

どうもLocationManagerServiceをServiceManagerに登録にする処理が、端末起動時のSystemServer起動時に一度だけ行われるらしく、LocationManagerServiceが死ぬ事態を想定していないのが原因っぽいのですが…。

自力で復帰させるコードを書くことは可能なんでしょうか。まあ仮にできたとしても、今後Androidの起動プロセスが不変だと保証することができない以上、やるべきではないんでしょうけども。

---

そんなわけで、確実性に欠けるGeocoderクラスは使わない方がいいと思います。

代替として、Web APIとして用意されているThe Google Geocoding APIを使うことができます。

このGeocoding API v3の呼び出しをJavaから行えるラッパーライブラリ、Java API for Google geocoder v3も提供されているのですが、やっていることは結局maps/api/geocode/jsonを叩いて結果のJSONをパースするだけなので、自前で軽量な実装を書いた方がいいと思います。

なお、APIの使用制限として、2500リクエスト/日という制限が書いてありますが、その他に1リクエスト/1~2秒程度の制限もあるようで、2件以上の住所データを同時に変換しようとすると失敗します。もし複数件の住所を処理する必要がある場合、ExecutorService等で制御する必要があります。

2013年8月31日土曜日

LayerListを利用してカードUIを作る

カードUI(流行ってるの?)を作る手法はいくらか考えられるけど、
  1. Layout.xml上でカード風に見せるViewを定義する
  2. カードUI風の画像を用意する
  3. ShapeDrawableを利用する
  4. カスタムViewを実装してonDrawメソッドを実装する
個人的な考えとしては、1を選択するのは、ないなぁ。

とはいえ、本業がプログラマだと2のように素材をさくっと作ってしまおう!とはいかない。4が恐らく処理速度上でもっとも軽量で、カスタマイズ性も高いものの、ちゃんとしたカスタムViewを作成するには知識がいるので、一番お手軽なのは3の方法でしょうか。

Layer ListでShape Drawableを重ねれば、そこそこな見栄えのカードUIが作れると思います。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:id="@+id/shadow" android:top="1dp" android:left="1dp">
    <shape >
      <solid android:color="#868686" />
    </shape>
  </item>
  <item android:id="@+id/front" android:bottom="1dp" android:right="1dp">
    <shape >
      <gradient android:startColor="#FFFFFF" android:endColor="#EDEDED" android:angle="270"/>
      <stroke android:color="#DBDBDB" android:width="1px" />
    </shape>
  </item>
</layer-list>

xmlに上のように定義して、利用する際はbackgroundのdrawableとして指定するだけ。なお、色はあまり考慮していないのと、State Drawableとして定義していないので、実用はしないでください。


AndroidにはiOSのCore Graphicsのレイヤ操作に相当する機能がないので、もう少しリッチにドロップシャドウを実現したい場合、9patchを用意するか、4を採用して、onDrawメソッドで実装することになります。

---

ところで「9patch画像を作成したら、/drawable/ディレクトリに保存する」とは公式にもそう書いてあるのだけど、「だから9patch画像は一つだけ用意すればいいんだ」という誤解が生じているような気がする。(9patch画像は生の/drawable/に保存する、と書いてあるチュートリアルが異様に多い)

公式ドキュメントが指す/drawable/ディレクトリとは、リソース分岐(/drawable-hdpi/とか)も含めた全体を指していて、9patch画像もピクセル密度に応じて複数の画像を用意する必要がある

このことはAndroid SDKをインストールしたディレクトリの、platforms/android-XX/data/res内を見れば分かります。Androidはボタンなどで9patchを多用しているのですが、ピクセル密度に応じた9patchを用意しているのです。

---

/drawable/に突っ込んだ9patch画像がなぜ問題かというと、実際には/drawable-mdpi/か何かとして認識されているから、例えばNexus 7などの中途半端なピクセル密度で表示されてしまうと、まず画像の本体部分が約1.3倍に引き伸ばされた後、9patchの指定位置ピクセルに補正が掛かるため、ここでズレが生じて破綻するケースがある。

それはまだマシなケースで、画像の拡大縮小の結果、9patchとして違法となった場合、getNinePatchChunk()でnullが返却され、Runtime Exceptionでアプリが強制終了することになる。

もっとも、ピクセル密度に応じて引き伸ばし位置を定義するのは、画像が複雑になると非常に面倒ですし、引き伸ばしだけでタイルパターンには使えないですし、デザイナーの方に9patchの詳細な仕様(Android開発者も知っているか怪しい)を伝える手間もありますし、なかなか難しいところです。

2013年8月30日金曜日

Effective Androidを読んでる。

「Effective~」というタイトルから、「デザインを定義するならばThemeを利用し、レイアウトと分離せよ」的な、格言めいた実用系ノウハウが載っているんだろうと思っていたんだけど、1章でいきなりその辺を外部の書籍に投げていて(というより自著の宣伝か)、出オチ感が…。

「すぐに使えるテクニック集」と「その辺に転がってるチュートリアル」と「マニアックな知識」が玉石混交で、ネイティブアプリ開発者もWeb系開発者もデザイナーもごった煮で対象としていて、ノリとしてはAndroid関係者向けの専門誌のような感じの本でした。

以下は、読んでて「う~~ん…」とか、「うおー!」ってなった部分。

DP値を実行時に取得するならば、dimension resourceで名前をつけて取得しよう。

<resources>
    <dimen name="card_padding">8dp</dimen>
</resources>
getResource().getDimension(R.dimen.card_padding);

Androidはレイアウトとスタイルとコードを分離できるとはいえ、コード上でdpとpx間の相互変換を行うこともよくある。ユーティリティメソッドを用意してもいいけれど、dimension resourceを使用し、dp値に名前をつけて管理した方がソースの可読性も高く保つことができる。

dimension resourceとリソースのバージョン分岐を組み合わせると、端末のフラグメンテーション対策にも応用できる。dimension resourceはもっと普及されるべきだと思うんだけど、なぜかプッシュする人が少ない印象…。何かトラップでもあるのだろうか。

AndroidでレイアウトをするならMetrics and Gridsは読もう。

最近Illustratorなども触っているので、3章のデザイナー視点で使いやすいpxなどを述べたお話は、面白い視点を提供してくれたのだけど、もうちょっと掘り下げて欲しいなという部分もあった。

  • 現実としてAndroidの案件の多くは、iOSとのマルチや移植であり、UITableViewCellが44ポイントな限り、3で割れない素材との戦いは終わらない。44ポイント(44px&88px)の素材に対してどう対抗すればいいのか?
  • さらにtvdpi(というかNexus 7)の登場せいで3の倍数で用意すれば万事解決、というほど単純でなくなってしまった。
  • dpに対してspが存在する説明がゼロだけど、spはユーザーがフォントサイズに対する多少の自由度を得るためのものだから、pt固定値素材を用意してしまうのは、ユーザービリティの低下に繋がるデメリットがあることも説明すべきでは。
  • iOSのユーザーインターフェースガイドラインほど厳密ではないものの、Androidにも類似したデザインガイドラインは提供されている。特にMetrics and Gridsは全ての開発者が読むべき。48dpのリズム、8dpの「mind the gap」(この言葉遊びのセンスが素晴らしい)は知っていて当然であって欲しい。

The AndroidManifest.xml Fileに一度は目を通そう

章が進むごとに内容が濃くなっていって、5章辺りからすごく面白くなる。

ただ、「AndroidManifestを活用しよう」というタイトルとは裏腹に、マニアックすぎて実用的か疑問符をつけたくなるような、AndroidManifest.xmlのマイナー設定の解説記事なのがもったいない。

著者的にはリファレンスに書いてあるからそれを読めば分かるでしょ?ってことなのかもしれないけど、現実的にはclearTaskOnLaunchとかnoHistoryとかの存在を知らずに、コード上で実装してるのをちらほらみたことがある。

実際、リファレンスは長い上に意味が通じない箇所もちらほらあるし、真面目に読むと非常に疲れるからね。

それでも、一度でいいからThe AndroidManifest.xml Fileを通して見るべきだと思う。

流し読みで「こんな設定があったのかー」と知るだけでも十分。それだけでもAndroidの可能性が広がったように感じる。まあ錯覚で終わると思うけど。

Eclipseの設定ファイルが何をやっているのか理解しよう

6章ではEclipseの設定ファイルに関する解説を行っているのだけど、これは必見だと思う。

Androidのプロジェクトをバージョン管理して、多人数で開発する方法論として、自分レベルのヌルい知識で語ると、
  • Eclipse関連のファイルをcommitせず、pullした純粋なAndroidコードをExisting Android Code Into Workspaceでインポートする
  • .projectや.classpathもバージョン管理の対象とした上で、eclipse import existing projects into workspaceでインポートする
の二通りが思いつく。

前者はpureなAndroidコードのみをバージョン管理の対象とするため美しく思えるけど、この方法はまずプロジェクトの規模が大きくなるとまず破綻する。

複数のプロジェクトが絡んで、class pathが入り乱れているような場合、Eclipseの設定ファイルが何をやっているのか理解していないと、自力でビルドパスを解決できずにハマったりするからだ。

幸いなことにEclipseの設定ファイルは人間にも読みやすい形式となっていて、マージツールで差分を取ることで原因追及することはできた。この記事にもう少し早く出会えていれば…。

gcm.jarはdeprecatedなので、Google Play Service APIを使いましょう

11章の内容はやや古い(7月ごろにGoogle Cloud Messaging APIの一部刷新があったのですが、反映されていない)ため、公式のものを読んだ方がいいです。有志の方による日本語翻訳も最新仕様には対応してません。

また用語も公式に定義されているものと対応していない(Registration IDをIDと大雑把に扱ったり、Sender IDをproductIDと呼称するなど)ので、技術書としても微妙な内容と思います。

結論.Theme ResourceとかFragmentの活用とかそういう話がもっと欲しかった

Android開発者の標準レベルって、流行り始めた2.3とかで止まってるような気がする。

巷には基本中の基本しか触れていない入門書が多いし、面白い本も何冊か知ってはいるけれど、進化スピードの速いモバイルの世界ではすぐに陳腐化してしまう。「先」を目指すとなると、英語の公式ドキュメントを読み漁るしかない。

「Effective~」系書籍は小手先のテクニックじゃなくて、フレームワークを十全に引き出すエッセイ集のようなイメージがあって、しかも同人というスピード感が加われば、「先」を切り拓いてくれるんじゃないかという期待があった。まあ勝手にイメージを押し付けてるだけなんだけど。

例えば、Activityと密結合を回避するための様々なテクニック、モデルクラスを作成することでActivity非依存のテストコードを書けるようにするとか、AsyncTaskをAsyncTaskLoaderにリプレースするとか、AlertDialogよりもDialogFragmentを好むべきだとか、それで全て上手くいくわけではなく、メリットもあれば落とし穴もある…。

…そういうノウハウが載っているのかな~と期待して買ったので、満足はできなかったけど、一部のマニアックな話については面白かった。増補改定にも期待。

2013年8月25日日曜日

iOS開発とAndroid開発の比較

iOS開発 vs. Android開発

という記事を読んで面白かったので、両端末開発が可能な(しかし最近は.NETが面白いので仕事以上では触っていない。iOS7についても少し調べる時間を作らないと…)自分も何か書いてみる。

iOS開発


最初に覚えることは多い。Objective-C、Cocoa Touch API。一般的な入門書で扱っている知識はここまでだけど、iOS Developerを名乗るのであれば、blocksやGCDなどモダンな知識、MRCやObj-C2.0以前のKVO、KVCなどレガシーな知識、Cocoa Touchの土台であるCore系技術についても習得する必要がある。

Core Imageは現状ではiOS端末で実用的な動作を実現できないのでどうでもいいとして、最低限Core GraphicsとCore Animationは覚えたい。Core Dataを覚えればフレームワークの性能を引き出し、iCloudとの連携についても取り計らってくれるのだけど、Core Dataフレームワークを理解するのは結構しんどいので、業務上で使うときには、FMDB経由でSQLiteを使うという方法が安牌かもしれない。

まあとにかく覚えることは多いけど、言語として面白いのでそれほど苦にならなかった。上の記事ではドキュメントの質が低いとあるけれど、確かにiOSのドキュメントはあまり整備されないのでバージョンがバラバラなドキュメントが入り乱れていて、困るときもあるのだけれど、Class Referenceに関して言えばAndroid DevelperのJava Docよりも充実していると思う。Discussionに何度助けられたことか。

ネイティブレベルの英語力があれば、読めるドキュメントの幅も広がるのでまた見えるものが違うのかもしれない。

Interface Builderはデザイナーとしては使えない(特にUIAppearanceが実行時にしか反映されないのは厳しい)けど、「オブジェクトを配置して相関を定義してシリアライズするもの」として割り切れば、「使い物にならない」と切り捨てるほどでもないと思う。ただ4インチ対応が必要になって以降、Auto Layout関連の挙動が直感的じゃないので、ひどく使いにくくなったのは事実。

Android開発


Javaの知識がある程度(Effective Javaが読める程度)あれば、Androidフレームワークを齧りながらでも開発ができるので敷居は低い。

しかしiOSのように低レベルフレームワークにアクセスすることができないのは致命的な弱点でもあって、例えばAndroidにはCore Textに相当するような低レベルで高品質なテキストレンダリングエンジンがないので、Androidの電子書籍アプリは端末によって見栄えの違いが生じたり、動作がもっさりしていたりと総じて質が低くなってしまっている。

「Androidといえばフラグメンテーション(断片化)問題」と思い込んでいる人が多いけれど、実際のところ多画面、多バージョン対応用向けの機能は充実していて、リソース分岐や9patchを駆使することができれば、遥かに多種多様なデバイスに対応しなければならないWebサイトよりも、ずっとずっと楽だと思う。

この辺り、Androidフレームワークを理解した上でそれに合わせて多画面でも動作するアプリケーションを作る、というやり方をせずに、固定サイズの画面に向けた既存のデザインをAndroidに流用しようとするから無理が出るだけじゃないのかなぁと思っている。

Androidの画面作りは、Webの画面作りに似たところがあって、まずボタンなどのコンポーネント見栄えをThemeリソース(CSSに相当)で定義して、それをXMLで定義していくというボトムアップなアプローチが向いている。GUIのレイアウトエディタは実際の編集に使うのではなく、実機出力のプレビューとして使う方がいい。Android Studioで実用的な速度で使えるようになったけど、XMLでの書き方を覚えた方が効率的なビューを作れる。

そういえば昔はエミュレータでしか階層ビューワが使えなくて、そのためだけにエミュレータを使っていたのだけど、今では実機でも使えるようになったので久しくエミュレータを起動していない。

長期的視点


Androidは2から4でまるで別物のように生まれ変わったけど、開発者視点ではJDK7への対応が異様に遅かったり(そういえばGAEもなぜかJDK7対応は異様に遅かった)や、JUnitはいまだに3で書かないといけないし、進化はのんびりしている。

それでいて、バックポートライブラリが提供されるため、4系で新規に追加された機能群を2系に対応することができる。遅すぎる感はあるものの、ようやくAction Barも対応されました。

このためAndroid 2.3で作成したアプリを4で動かすこともできるし、4向けAPIであるAsyncTaskLoaderやActionBarを利用したアプリを作っても、2系でも起動することができる。

対するiOSはバージョンが変わる度にAPIの内部仕様が目ぐるましく変化し、言語の構文や規則までもが変わるといったことがままにある。このためiOSの開発者で居続けるためには、常に最新の情報にアップデートしていく必要がある。

「ユーザーの端末もほとんどが最新のものに切り替わっていくので、iOSにはフラグメンテーションの問題はない」みたいな話を良く聞くけど、それは環境などによる。iOS4以前に作られたレガシーなフレームワークを利用したいときもあるし、流行り始めたiOS4時代のAPIで書かれたアプリをiOS6~7に対応せよというメンテナンス案件やら、サポートするリスクリターン考えずにiOS4からの対応にせよという命令も多いので。

このため長期的な保守については、現時点ではAndroidの方が圧倒的に楽だと思う。もっともiOSも成熟期に入り、今後マルチタスク化などの大きなパラダイムシフトはないと思うし、XCode5でテスト環境が充実していくという話もあるので、今後の見通しは分からないけれど。

2013年7月26日金曜日

AndroidのSSL通信をキャプチャする方法

Burp ProxyCharlesなどのリバースプロキシを使いましょう。

リバースプロキシとは、外部サーバーへの接続を肩代わりするプロキシサーバーです。

http通信ならば、これらのプログラムを立ち上げればそれで通信をキャプチャすることができますが、SSL通信では一手間必要です。サーバーとクライアント間でお互いに承認した乱数(マスターシークレット)を用いて暗号化するため、中間に入って通信を傍受することはできないからです。

そこでリバースプロキシのCA証明書を入手し、端末に信頼できるルートCAとして登録する必要があります。

Charlesの場合、FAQにiPhone用の証明書ファイルへのリンクがありますので、Androidのブラウザでアクセスしてインストールします。iPhone用と書いてありますが、Androidでも問題なく使えます。

Burp Proxyの場合、Burp Proxyが生成した証明書を取得する必要があります。

Androidの標準ブラウザやChoromeでは証明書の取得を行うことができないので、適当なデスクトップ用ブラウザを仕様します。Burp Proxyを起動->ブラウザ(OS)でプロキシサーバを設定->ブラウザでhttpsのサイトへアクセス->ルート証明書をエクスポート(firefoxの場合、「接続の安全性を確認できません」という警告が出るので、「危険性を理解した上で接続するには」->「例外を追加...」をクリックします。「セキュリティ例外の追加」ダイアログが表示されるので、証明書の状態の「表示(V)...」をクリックすると、証明書の詳細が表示されます。ここでルート証明書となっているBurp Suite PortSwiggerを選択し出力します。拡張子が付いていない場合.cerを付けます)で、証明書を取得することができます。あとは証明書をメール等でAndroid端末に送付してインストールします。

CA証明書のインストールは、SDカードのルートに保存(adb push hoge.cer /mnt/sdcard)して、「設定」->「セキュリティ」->「SDカードからのインストール」からでもできます。

CA証明書のインストールができたら、Macの設定から「インターネット共有」をONにしてWifiスポットにし、AndroidのWifi接続先をMacにします。そしてWifi接続の設定からプロキシを手動で追加し、Macのアドレスと8080番ポートを指定します。(同一のネットワークセグメントに接続されていれば、「インターネット共有」を使う必要はありません)

なおWifi接続の設定には、「HTTPプロキシはブラウザで使用されていますが、他のアプリでは使用できません」というような説明が書いてあるかと思いますが、実際には普通にアプリの通信でも有効です。(説明が間違っているのか、端末依存があるかどうかは分かりませんが、手元のAndroid4.0以降の端末だと可能です。httpClientのProxy設定を変更することでそれ以前のAndroidでも可能です)

---

リバースプロキシを使えば、通信内容をキャプチャするだけでなく、いろいろとやれます。
  • リクエスト内容を書き換える。
  • 特定の通信処理のみをInterceptすることができる。例えば「通信Aが成功したらその結果を使って通信Bを行う」といった処理で、通信Bが失敗したときの挙動を確実にテストできる(タイミングよくWifiを切る、とかそういう原始的な方法でテストしている人も世の中にはいるらしい)
  • CharlesのMap Localを使うと、レスポンスとしてローカルファイルを返すことができる。本番環境サーバーからデータを取得する処理を、ローカルファイルに切り替えることで、コードを変更せずにテストデータの変更を行える。

2013年7月21日日曜日

Java FX2のアニメーションをラムダで書き直してみる

Java FX2 を触ってみた。の続き。

Java8のラムダを使って記述したら、確かにTimelineにKeyFrameが並び、アニメーションが順番に実行される、という流れが非常に分かりやすくなりました。コード量も約半減。

@FXML
private Label resultText;

private TranslateTransition slideInAnimation;
private FadeTransition fadeInAnimation;
private FadeTransition fadeOutAnimation;

final private Timeline animation = new Timeline(
 new KeyFrame(Duration.seconds(0),e -> slideInAnimation.play()),
 new KeyFrame(Duration.seconds(0),e -> fadeInAnimation.play()),
 new KeyFrame(Duration.seconds(2),e -> fadeOutAnimation.play())
);

@Override
public void initialize(URL arg0, ResourceBundle arg1) {
 slideInAnimation = TranslateTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.4))
  .fromX(100.0)
  .toX(0)
  .interpolator(Interpolator.EASE_OUT)
  .build();

 fadeInAnimation = FadeTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.5))
  .fromValue(0)
  .toValue(1)
  .build();

 fadeOutAnimation = FadeTransitionBuilder.create()
  .node(resultText)
  .duration(Duration.seconds(0.5))
  .fromValue(1)
  .toValue(0)
  .build();
}

private void showMessage(String text){
 animation.stop();
 resultText.setOpacity(0);
 resultText.setVisible(true);
 resultText.setStyle("-fx-text-fill:#606060");
 resultText.setText(text);
 animation.play();
}


あとnew Duration()ではなくて、Duration#seconds()の方が分かりやすいね。

ついでにアニメーションをinitializeのタイミングで一度だけ生成するように修正するとともに、TransitionBuilderを利用するように修正しました。

しかし、JDK1.8にした際にJava FXも同梱のものに変えてみたのですが、TransitionBuilderはDeprecatedになっているようです。どうもjavaFX8のドキュメントを見るとBuilder系のクラスが軒並みDeprecatedになってるようで、次期バージョンでは消えるらしいのですが、理由が見当たらない…。