筆者の動作確認: Unity 2021.3.29f1
忙しい人向けの結論
[Preserve] internal sealed class MarkerForInternet : UnityWebRequest { }
上記のコードをどこかに書いておけば、UnityWebRequest
を使っている判定になり、自動でインターネット権限をつけてくれる。
インターネット権限の付与
Unity で通信したいときは UnityWebRequest
を使うと、Android の場合はビルド時に自動的にインターネット権限が付与される。
しかし、C# 標準ライブラリの HttpClient
を使うと
SocketException: Access denied
のような例外が出る。あるいは、IL2CPP を通した場合、出る例外が少し違う。
WebException: Error: NameResolutionFailure
どちらにせよ、インターネット権限が自動では付与されないために発生しているようだ。
手動でインターネット権限をつけたい場合、Player Settings -> Other Settings -> Configuration -> Internet Access を Auto
から Require
に変更すれば権限が付く。
ここで問題が起こるのが、Unity 用のライブラリ開発者が HttpClient
を使いたい場合。
パッケージ側からパッケージ利用者のアプリケーションのインターネット権限を付与することはできないため、利用者に Player Settings からインターネット権限を Require
に設定してもらう必要が出てくる。これは非常に不親切。
パッケージ内にエディタ拡張などで権限を変更するような処理を書けば変更できるのかもしれないが、できればそんなことしたくない。プロジェクトの Internet Access を Auto
のまま、UnityWebRequest
を使った時と同じように権限が自動付与されてほしい。
解決策
考えた。
たぶん Unity のことだから UnityWebRequest
を使って通信しているかどうかなんて真面目にチェックしていないだろう。UnityWebRequest
クラスへの参照が存在しているかどうかで判断しているだけだろう。
[assembly: UseInternet(typeof(UnityWebRequest))] internal sealed class UseInternetAttribute : Attribute { public Type Type { get; } public UseInternetAttribute(Type type) { Type = type; } }
上のコードをパッケージ内に書いたら、案の定パッケージを使う側のアプリに自動でインターネット権限がついた。解決。
と思ったら IL2CPP 環境で試したらインターネット権限も付与されず、解決してなかった。ストリップされてしまったのだろう。
要するに、UnityWebRequest
クラスへの参照をアセンブリ内に存在させておけば、Unity は「UnityWebRequest
を使っているのでインターネット権限が必要だ」と判断してくれるみたい。
ならば、UnityWebRequest クラスへの参照を含む実装がストリップによって消えなければいいのだ。 以下、IL2CPP 対応版
[Preserve] internal sealed class MarkerForInternet : UnityWebRequest { }
UnityWebRequest
を継承したクラスを定義しておいて、Preserve
属性を使って IL2CPP で消えないようにした。
これで、実際に問題なく動くことが確認できた。不要なクラスを定義せざるを得ないのは少し気持ち悪いが仕方ない。
継承クラスの定義でなくとも、UnityWebRequest
が IL2CPP によって消えないコードの中で何かに使われていればいいようなので、自分なりの方法でアレンジしてほしい。
これでうまくいくのだがドキュメント記載の仕様ではないため、将来のバージョンでは使えないかもしれないので注意。
ちなみに
上記の UnityWebRequest
の有無の判定が、C# のコンパイル時なのかコンパイル後なのか気になった。
Source Generator などでよく使われるテクニックとして、Conditional
属性に存在しないシンボル名を書くことで、コンパイル時にのみコンパイラに情報を読み取らせてアセンブリには情報を残さないというテクニックがある。
先ほどの例に、[System.Diagnostics.Conditional("COMPILE_TIME_ONLY")]
という属性をつけて、コンパイラには UnityWebRequest
を認識させるがアセンブリには情報を残さないようにしてみた。
// ↓ これはインターネット権限がつかない [assembly: UseInternet(typeof(UnityWebRequest))] [System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] internal sealed class UseInternetAttribute : Attribute { public Type Type { get; } public UseInternetAttribute(Type type) { Type = type; } }
するとインターネット権限がつかない。コンパイル時に Roslyn でソースコードを舐めて UnityWebRequest
の有無をチェックしているならこれでも権限がつくはずだが、どうやらダメなよう。
つまり、ソースコートではなくコンパイル後にアセンブリから情報を拾って UnityWebRequest
の有無を見ているらしい。
なるほど、IL2CPP を通すとダメなわけだ。