AWS SDK for .NETを使うUnityアプリをiOS/Android向けにビルドする

2018.04.17

以下の記事で作成したUnityアプリをiOS/Android向けにビルドします。

ゲームビューで動作確認

Unityエディタの再生ボタンを押して、正常に動作するか確認します。

このとき、実行環境によってはTlsExceptionが以下のようなエラーがでるようです。(スタックトレースは見やすいように加工しています)

Amazon.Runtime.AmazonServiceException: A WebException with status TrustFailure was thrown.
---> System.Net.WebException: Error: TrustFailure (The authentication or decryption has failed.)
---> System.IO.IOException: The authentication or decryption has failed.
---> Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from server. Error code: 0xffffffff800b010a

これは、Unity Scripting RuntimeのMonoに標準でルート証明書がインストールされていないために発生します。後のiOS/Androidでも同様の問題が発生するのですが、まずはUnityエディタに限定して対応します。以下のURLでも報告されている通り、mozrootsというツールを使って証明書を取得することで解決します。

FAQ: Security - Mono

Windowsでは以下のようにコマンドを実行します。この時、Unity付属のmonoを使うことを忘れないでください。

mozroots.exe --imort --sync

Macの場合は、新しいもの(10.13.4で確認)では発生しませんでした。

Android向けにビルドする

ビルド環境のセットアップ

公式ドキュメントはこちらです。
このドキュメントを参考にしつつ、以下の作業を行いました。

  • Android Studioのインストールと、HelloWorldアプリを作成して実行(追加コンポーネントが後からダウンロードされるため)
  • Android NDKのダウンロードとインストール
  • UnityのFile > Build Settings...でPlatformをAndroidに変更して、SDK/JDK/NDKのパスを設定
    • その際にUnityのAndroid moduleインストールを案内されるので実施
  • デバッグモードにしたAndroid端末を接続してBuild and Run

執筆時点では過去バージョンのNDKを使わなければエラーが発生します。

  • windows: https://dl.google.com/android/repository/android-ndk-r13b-darwin-x86_64.zip
  • mac: https://dl.google.com/android/repository/android-ndk-r13b-darwin-x86_64.zip

参考URL: UnityからAndroid実機ビルド手順(2017.08版) - Qiita

TLSの問題について

執筆時点で、Androidでビルドすると、AWS SDKを呼び出す箇所でゲームビューの時と同じTlsExceptionが発生します。
以下のStackOverflowについている回答が参考になるかと思いましたが、回答のコメントで指摘されている通り、Bad SSLでテストしてもtureが返されるため、検証できていないようです。

Mono https webrequest fails with The authentication or decryption has failed"- StackOverflow

Androidビルドではmozrootsを使えないので、代替案を探しているところです。ご存知の方はコメントをお願いします。

iOS向けにビルドする

ビルド環境のセットアップ

ドキュメントはこちらです。
Androidとは違い、developer.apple.comでの作業が発生します。
気をつける点としては、Player Settingsの Automatic Signing Team ID は任意オプションとなっていますが、ここにTeamIDを入力しない場合は、Unityのビルドで生成されるXCode ProjectにTeam ID設定が入らず、ビルドエラーになります。
iPhoneを繋ぎっぱなしにしてビルドしながら開発するスタイルの場合は、設定することをお勧めします。

link.xmlについて

この記事の本題です。
ビルドしたiOSアプリでAWS SDKの処理を呼ぶと、以下のスタックトレースを出してアプリが停止します。例によって、スタックトレースは抜粋です。

MissingMethodException: Default constructor not found for type Amazon.Util.Internal.PlatformServices.NetworkReachability
  at System.RuntimeType.CreateInstanceMono (System.Boolean nonPublic) [0x00000] in <00000000000000000000000000000000>:0 
  at System.RuntimeType.CreateInstanceSlow (System.Boolean publicOnly, System.Boolean skipCheckThis, System.Boolean fillCache, System.Threading.StackCrawlMark& stackMark) [0x00000] in <00000000000000000000000000000000>:0

このエラーは、Unityが行なっている最適化に起因します。
AppStoreでリリースするアプリをビルドするためには、Unity Scripting BackendとしてIL2CPPを使用しなければなりません。以下、ドキュメントからの引用です。

IL2CPP は iOS ARM 64ビットのデプロイをサポートするスクリプトバックエンドなので、新しいアプリのリリースで Apple の App Store を利用するためには必須です。

AWS SDK for .NETのUnity向けReadmeによると、IL2CPPの strip bytecode がUnityビルド時に常に有効になっているので、AWS SDKがリフレクションしているメソッドが削減されるため、ランタイムエラーが発生するそうです。
したがって、AWS SDKのReadmeとUnityのIL2CPPドキュメントに従って、必要なライブラリを link.xml に記載して、Assetsディレクトリ直下に配置する必要があります。link.xmlに書いたライブラリは、最適化時の削減対象になりません。
ランタイムエラーのスタックトレースを見ながら、少しずつ編集したものが以下のlink.xmlです。

<linker>
  <assembly fullname="UnityEngine">
      <type fullname="UnityEngine.Networking.UnityWebRequest" preserve="all" />
      <type fullname="UnityEngine.Networking.UploadHandlerRaw" preserve="all" />
      <type fullname="UnityEngine.Networking.UploadHandler" preserve="all" />
      <type fullname="UnityEngine.Networking.DownloadHandler" preserve="all" />
      <type fullname="UnityEngine.Networking.DownloadHandlerBuffer" preserve="all" />
  </assembly>

  <assembly fullname="mscorlib">
      <namespace fullname="System.Security.Cryptography" preserve="all"/>
  </assembly>

  <assembly fullname="System">
      <namespace fullname="System.Security.Cryptography" preserve="all"/>
      <namespace fullname="System.ComponentModel" preserve="all" />
  </assembly>
  <assembly fullname="System.Configuration">
      <namespace fullname="System.Configuration" preserve="all" />
  </assembly>
  <assembly fullname="AWSSDK.Core" preserve="all">
      <namespace fullname="Amazon.Util.Internal.PlatformServices" preserve="all"/>
  </assembly>
  <assembly fullname="AWSSDK.CognitoIdentity" preserve="all"/>
  <assembly fullname="AWSSDK.CognitoIdentityProvider" preserve="all"/>
  <assembly fullname="AWSSDK.SecurityToken" preserve="all"/>
  <assembly fullname="AWSSDK.Extensions.CognitoAuthentication" preserve="all"/>
</linker>

この問題とは別にTlsExceptionも発生するため、Android同様の課題があります。

まとめ

UnityアプリをiOS/Android向けにビルドする際の注意点を書きました。
サーバーレス環境ではクライアントが直接AWS SDKを実行する機会が増えるので、このような問題に対処できるナレッジを蓄積して行きましょう。