iOS Enterpriseプログラムで作成したMonoTouch AppをIISからOTA配信してみた
OTA配信するためには以下の物が必要になる。
- MonoTouchのライセンス
- Appを正式に署名できる証明書等
- デバイスがインターネットに接続できる環境(3GやWiFi等)
- 画像2種
- info.plistの設定
- plistファイル
- ipaファイル
- plistファイルへのリンクが書かれたHTMLファイル
- IISサーバー
MonoTouchのライセンス
そもそもライセンスが無ければデバイス用のバイナリをビルドできない。
Appを正式に署名できる証明書等
デバイス用のバイナリをビルドするために必要。iOS Dev Centerで発行する。
デバイスがインターネットに接続できる環境(3GやWiFi等)
AppをMonoDevelopからインストールしたときと違い、OTA経由でインストールした場合はApp起動時に証明書の有効性確認の為ocsp.apple.comにアクセスしようとする模様。よってインターネットへの接続環境が必要になる。
アイコン画像2種
一つはインストール中にホームに表示される57x57ピクセルのPNG画像。
一つはiTunesに表示される512x512ピクセルのPNG画像。
57x57の画像はAppにバンドルするアイコンを流用した。
512x512の画像はOTA配信では表示される所が無かったので大きさだけ合わせた白一色画像を用意した。
plistファイル
Appの情報やipaファイルへのリンクを記述したファイル。
以下のテンプレートを元に作成した。
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- array of downloads. -->
<key>items</key>
<array>
<dict>
<!-- an array of assets to download -->
<key>assets</key>
<array>
<!-- software-package: the ipa to install. -->
<dict>
<!-- required. the asset kind. -->
<key>kind</key>
<string>software-package</string>
<!-- required. the URL of the file to download. -->
<key>url</key>
<string>ipaファイルへのURL(フルパス)</string>
</dict>
<!-- display-image: the icon to display during download .-->
<dict>
<key>kind</key>
<string>display-image</string>
<!-- optional. indicates if icon needs shine effect applied. -->
<key>needs-shine</key>
<true/>
<key>url</key>
<string>57x57ピクセルpngファイルへのURL(フルパス)</string>
</dict>
<!-- full-size-image: the large 512x512 icon used by iTunes. -->
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>512x512ピクセルファイルへのURL(フルパス)</string>
</dict>
</array><key>metadata</key>
<dict>
<!-- required -->
<key>bundle-identifier</key>
<string>Appを識別するためのIdentifier文字列</string>
<!-- optional (software only) -->
<key>bundle-version</key>
<string>バージョン番号文字列</string>
<!-- required. the download kind. -->
<key>kind</key>
<string>software</string>
<!-- optional. displayed during download; typically company name -->
<key>subtitle</key>
<string>会社名</string>
<!-- required. the title to display during the download. -->
<key>title</key>
<string>App名</string>
</dict>
</dict>
</array>
</dict>
</plist>
今回は以下の様にした。ファイル名はtestapp.plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <!-- array of downloads. --> <key>items</key> <array> <dict> <!-- an array of assets to download --> <key>assets</key> <array> <!-- software-package: the ipa to install. --> <dict> <!-- required. the asset kind. --> <key>kind</key> <string>software-package</string> <!-- required. the URL of the file to download. --> <key>url</key> <string>http://myserver/testapp-1.0.ipa</string> </dict> <!-- display-image: the icon to display during download .--> <dict> <key>kind</key> <string>display-image</string> <!-- optional. indicates if icon needs shine effect applied. --> <key>needs-shine</key> <true/> <key>url</key> <string>http://myserver/testapp/image.57x57.png</string> </dict> <!-- full-size-image: the large 512x512 icon used by iTunes. --> <dict> <key>kind</key> <string>full-size-image</string> <key>needs-shine</key> <true/> <key>url</key> <string>http://myserver/testapp/image.512x512.png</string> </dict> </array><key>metadata</key> <dict> <!-- required --> <key>bundle-identifier</key> <string>com.myapp.testapp</string> <!-- optional (software only) --> <key>bundle-version</key> <string>1.0</string> <!-- required. the download kind. --> <key>kind</key> <string>software</string> <!-- optional. displayed during download; typically company name --> <key>subtitle</key> <string>マイカンパニー</string> <!-- required. the title to display during the download. --> <key>title</key> <string>テストApp</string> </dict> </dict> </array> </dict> </plist>
※bundle-identifierの部分にinfo.plistのIdentifierと同じ物を記述する
※bundle-versionの部分にinfo.plistのVersionと同じ物を記述する
※日本語を含む時はUTF-8エンコードで保存しなければならない。
※画像やipaファイルに対してMD5によるチェックサムを指定できる機能がある。今回は使わなかった。これは巨大なファイルに対してネットワーク転送時のエラーチェック機能になる。
ipaファイル
MonoDevelopからプロジェクト設定を変更するだけでビルド先に自動生成してくれる。
ファイル名は特に設定をしないとプロジェクト名+バージョン番号文字列+.ipaになる模様。
plistファイルへのリンクが書かれたHTMLファイル
Aタグのhref内を↓の用に記述する必要がある。
<a href="itms-services://?action=download-manifest&url=plistファイルへのフルパス">App名</a>
例えば以下の様になる。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>OTA sample</title> </head> <body> <a href="itms-services://?action=download-manifest&url=http://myserver/testapp/testapp.plist">Test App</a> </body> </html>
今回はこのファイルをhttp://myserver/index.htmlとして作成した。
IISサーバー
通常のWEBサイトを作成する。そのWEBサイトに対してipaファイルとplistファイルのMIMEタイプを追加する
そしてipaファイル、plistファイル、png画像2種をWEBサイトに配置する。
これで準備が完了した。デバイスのSafariからhttp://myserver/index.htmlにアクセスして、リンクをクリックするとインストールが始まる。
インストール出来ないとき
"準備が出来ていない"と言われるとき。
- plistファイルへのリンクが書かれたHTMLファイルは正しいか?
- plist内の記述は正しいか?
インストールの最後の方で失敗する場合
- plist内のbundle-identifierとinfo.plistのIdentifierが同じになっているか?
- plist内のbundle-versionとinfo.plistのVersionと同じになっているか?
- Appは正しく署名されているか?
- そのAppはMonoDevelop経由でインストールして起動することが可能か?
Mac版MonoDevelopとプロキシ その2
Mac版MonoDevelopの自動更新を有効にしたかったのでこの方法でプロキシを経由できるようにしていた。
しかし最近になってMonoTouch製アプリをiOSシミュレータ内で動かしているとき、プロキシをバイパスできていないことが判った。
正確にはMonoTouch製Appの中でHttpWebRequestをインスタンス化した時、ProxyプロパティのBypassListが無いHttpWebRequestが生成されていた。
そのためローカルエリアへのアクセスであっても全てプロキシを経由*1しようとしていた。
Monoのソースコードを追って、HttpWebRequest生成時に環境変数no_proxyを読み取っていることが判ったので/etc/launchd.confを以下のように変更してみた。
setenv HTTP_PROXY http://10.0.0.1:8080 setenv NO_PROXY 10.*,testserver
しかしこれだとHttpWebRequestが正常に動いてくれなかった。そればかりかMonoDevelopの自動更新も動かなくなってしまった。NO_PROXY変数の記述が間違っているのかと色々試行錯誤したがうまくいくことは無かった。
結局バイパス方式はあきらめて自動更新したいときだけシェルからMonoDevelopを起動するようにした。
1. /etc/launchd.confからプロキシの記述を削除後Macを再起動。
2. ~/.bashrcファイルに以下を記述
export HTTP_PROXY=http://10.0.0.1:8080
3. 自動更新したいときだけシェルから以下のコマンドでMonoDevelopを起動
open /Applications/MonoDevelop.app
*1:残念ながらプロキシ側がローカルエリアへのアクセスをバイパスしてくれるように設定されることはない
Mono for Android v4 とx86版ICSエミュレータを組み合わせて開発する
止まっているのかと勘違いするほどクソ遅いARM版ICSエミュレータでMono for Androidアプリを開発するのは非常にストレスがある。エミュレータの起動まではスナップショットを使えば回避できるが、ビルド->転送->実行->ブレーク->ステップ等はとてつもなく遅い。
そこでx86版ICSエミュレータで開発ができないか試してみたら以外とあっさりうまくいった。ARM版と比較にならないぐらい速い。
ここでの開発環境はMac OS。おそらくWindowsでも可能だろう。
使った物
- VirtualBox for Mac
- VirtualBox用 ICS x86エミュレータイメージ : ここからAndroid-v4.7zをダウンロードする。このイメージはマウス、ネットワークとSDカードをサポートしている。
- Android SDK : エミュレータイメージ内のICSはv4.0.1なので4.0.3(2012/05時点)のSDKをインストールする。
- MonoDevelop v3
- Mono for Android v4.2.1 Trial版
手順
- MonoDevelop、AndroidSDK、VirtualBoxをインストール。
- ダウンロードしたエミュレータイメージを適当なところに解凍。
- VirtualBoxから解凍したエミュレータイメージを読み込む。
- ホストのターミナルで以下のコマンド実行
$ adb kill-server
$ adb start-server
$ adb devices
adb devicesを実行してemulator-5554等と表示されればadbがx86版エミュレータを認識してくれている。
MonoDevelopでのプロジェクト設定
x86版バイナリを作成しなければならないので以下の部分でx86にチェックを入れる。
またエミュレータ内のICSバージョンがv4.0.1なのでTarget framework等を4.0にする。
ここと
ここ
以上でx86版エミュレータMonoDevelopから使えるようになる。ブレークポイントも可能。SDカードへのファイル転送はAndroidSDK付属のddmsから可能。
ネットワークについて
エミュレータのネットワーク設定はNATにしておけばadbからも自動認識された。
SecureString型を使ってみた
SecureString型がMonoTouchでも使えるかどうか検証してみた。
using System.Security; using System.Runtime.InteropServices; SecureString ss = new SecureString(); foreach (var v in "ABCDFG") { ss.AppendChar(v); } ss.MakeReadOnly(); IntPtr ssPtr = IntPtr.Zero; try { ssPtr = Marshal.SecureStringToBSTR(ss); string s = Marshal.PtrToStringUni(ssPtr); // なにかしらの処理 // Console.WriteLine(s); } finally { if (ssPtr != IntPtr.Zero) Marshal.ZeroFreeBSTR(ssPtr); } ss.Dispose();
ポインタからString型に変換するときにMarshal.PtrToStringBSTRを使うサンプルが多い。しかしMonoTouch 5.2.5とMono framework 2.10.6の組み合わせでは実機で実行するときにMarshal.PtrToStringBSTRが見つからない旨の例外が発生する。よってこのサンプルではMarshal.PtrToStringUniを使っている。
cerファイルからp12ファイルへの変換方法
Appを公開するために作成する公開用証明書。これを異なる別のMac上で使う為に証明書のファイル形式をcerからp12に変換したときの備忘録。
必要なのは
- distribution_identity.cer 公開用証明書
- mykey.key 秘密鍵ファイル(上記証明書を作るときに使った鍵ペアの物)
手順
1.公開用証明書をPEM証明書形式に変換する
$ openssl x509 -in distribution_identity.cer -inform DER -out distribution_identity.pem -outform PEM
2.PEM証明書形式をP12証明書形式に変換する
$ openssl pkcs12 -export -inkey mykey.key -in distribution_identity.pem -out distribution_identity.p12
この時パスワードを聞かれるので適宜入力する
3.P12証明書を開いてキーチェーンに取り込む(取り込み時に上記パスワードを入力する)
メインスレッド以外のスレッドからUIViewにアクセスしてはいけない
基本的なルールすぎて忘れがちだがメインスレッド以外のスレッドからUIViewにアクセスしてはいけない。
というわけですっかり忘れていて数時間無駄にしてしまった。
場所はUITextViewに対して文字列を設定する箇所。
Application Outputには以下の様に出力されていた。
. . . Thread started: bool _WebTryThreadLock(bool), 0xe415c80: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now... 1 WebThreadLock 2 -[UITextView setText:] 3 0x12fc2838 4 0xfcf016c 5 0x158475a1 6 0x15847414 7 0x15846842 . . .
メッセージを読めばすぐに気づく。でもハマった。
言い訳になるがこの例外は必ず起きるわけではなく、代入する文字列によって例外が発生しないのだ。その結果、代入している文字列に問題が!?と、思考がそっちに傾いてしまっていた。