はじめに
ソーシャルゲーム事業部の中島と申します。よろしくお願い致します。
カヤックUnityアドベントカレンダー2018の14日目の記事となります。
Android端末上で動作するアプリで情報をローカル保存する際の注意点について書いてみます。
(Unityは2017.4.8f1を、実機はSHL25 Androidバージョン5.0.2を使用しています)
ローカル保存の方法
Unityで情報を端末のローカル領域に保存するには、大まかに以下の2パターンの方法があります。
UnityEngine.PlayerPrefs
を利用する- key-value形式で保存が可能
- 自前でファイルを作成し、以下を利用して取得したパスに保存する
UnityEngine.Application.persistentDataPath
(永続性のある情報向け)UnityEngine.Application.temporaryCachePath
(一時的な情報向け)
UnityEngine.Application.persistentDataPath をAndroidで呼び出すとどうなるか
今回は UnityEngine.Application.persistentDataPath
(をAndroidで呼び出したとき)の話です。
(ローカル保存のより全体的な話はこちらにまとめられているので、ぜひご活用ください)
UnityEngine.Application.persistentDataPath
は永続性のあるデータを保存するためのパスを返します。
早速ですが、 Android上で UnityEngine.Application.persistentDataPath
を参照した際に得られる値を見てみます。
Debug.Log(UnityEngine.Application.persistentDataPath);
上記で、 /storage/emulated/0/Android/data/{アプリのパッケージ名}/files/
の様な出力が得られると思います。
(得られる出力はAndroidのバージョンやUnityでアプリをビルドする際の設定に依存する模様です)
つまりAndroid上で永続性のあるデータを保存するには /storage/emulated/0/Android/data/{アプリのパッケージ名}/files/hoge
の様な形でファイルを配置することとなります。
何が問題か
/storage/emulated/0/Android/data/{アプリのパッケージ名}/files/
というパスはAndroidの 外部ストレージ
という領域に属しています。
外部ストレージ
がどんな特性を持つ領域かは こちら に詳しく記載されていますが、特に注意したいのはその領域がユーザーや他のアプリに対してパブリックであるという点です。
External storage: *中略* - It's world-readable, so files saved here may be read outside of your control.
例えばユーザー識別情報をこの領域に保存する作りになっていたとすると、ユーザーや他のアプリはそれを参照が可能ということになります。
これはそのままチート行為やアカウントの譲渡などにつながるリスクとなります。
では実際にアプリで作成したデータをユーザーが読み出せるかを確認してみます。
using System.IO; using UnityEngine; public class PdpChecker : MonoBehaviour { private void Start() { SaveText( Application.persistentDataPath, "hoge", "カヤックUnityアドベントカレンダー2018の14日目" ); } private void SaveText(string filePath, string fileName, string textToSave) { var combinedPath = Path.Combine(filePath, fileName); using (var streamWriter = new StreamWriter(combinedPath)) { streamWriter.WriteLine(textToSave); } } }
上記を組み込んだapkをAndroid端末にインストールして実行すると、/storage/emulated/0/Android/data/{アプリのパッケージ名}/files/
にhogeというファイルが出力されるはずです。
(パッケージ名は com.hoge.pdpchecker
としました)
$ adb shell shell@SHL25:/ $ cd /storage/emulated/0/Android/data/com.hoge.pdpchecker/files /system/bin/sh: cd: /storage/emulated/0/Android/data/com.hoge.pdpchecker/files: No such file or directory # 目的地の実体は違う場所にある? shell@SHL25:/ $ cd /storage/emulated shell@SHL25:/storage/emulated $ ls -la lrwxrwxrwx root root 1974-03-13 02:45 legacy -> /mnt/shell/emulated/0 # 実体は /mnt/shell/emulated/0 の模様 shell@SHL25:/storage/emulated $ cd /mnt/shell/emulated/0/Android/data/com.hoge.pdpchecker/files shell@SHL25:/mnt/shell/emulated/0/Android/data/com.hoge.pdpchecker/files $ ls hoge # 目的のファイルを発見 shell@SHL25:/mnt/shell/emulated/0/Android/data/com.hoge.pdpchecker/files $ cat hoge カヤックUnityアドベントカレンダー2018の14日目 # 読み出すことが出来た
この様にファイルの内容を読み出すことが可能でした。
以上から UnityEngine.Application.persistentDataPath
で取得したパスに配置する情報は、以下に限る必要があります。
- 永続性がある (ない場合は
UnityEngine.Application.temporaryCachePath
へ) - 秘匿する必要がない
秘匿情報を保存するには
/storage/emulated/0/Android/data/{アプリのパッケージ名}/files/
というパスはAndroidが外部ストレージ
として用意している領域に属しています。
UnityEngine.Application.persistentDataPath
で取得できるパスが 外部ストレージ
を示すケースがある、と記載しましたが、Androidは対となる 内部ストレージ
という領域も提供しています。
Internal storage: *中略* - Files saved here are accessible by only your app.
内部ストレージ
のパスを取得することができればユーザーに公開せずにファイルを保持することが可能なはずですが、Unityのスクリプトリファレンスを眺めて見てもその様なパスを取得する方法は見つかりませんでした。
Android Nativeではそれが可能な様なので、C#からJavaクラスを制御できる AndroidJavaClass
などを利用して取得してみます。
最終的に呼び出すメソッドは Context.getFilesDir()
で取得したFileオブジェクトの getCanonicalPath()
です。
#if !UNITY_EDITOR && UNITY_ANDROID using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) using (var getFilesDir = currentActivity.Call<AndroidJavaObject>("getFilesDir")) { string secureDataPathForAndroid = getFilesDir.Call<string>("getCanonicalPath"); Debug.Log(secureDataPathForAndroid); } #endif
出力は /data/data/{アプリのパッケージ名}/files
となりました。
それでは先ほどのスクリプトを以下の様に書き換えて、再度Android実機での読み出しを検証してみます。
using System.IO; using UnityEngine; public class PdpChecker : MonoBehaviour { private void Start() { SaveText( GetSecureDataPath(), "hoge", "カヤックUnityアドベントカレンダー2018の14日目" ); } private void SaveText(string filePath, string fileName, string textToSave) { var combinedPath = Path.Combine(filePath, fileName); using (var streamWriter = new StreamWriter(combinedPath)) { streamWriter.WriteLine(textToSave); } } private string GetSecureDataPath() { #if !UNITY_EDITOR && UNITY_ANDROID using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) using (var getFilesDir = currentActivity.Call<AndroidJavaObject>("getFilesDir")) { string secureDataPathForAndroid = getFilesDir.Call<string>("getCanonicalPath"); return secureDataPathForAndroid; } #else // TODO: 本来は各プラットフォームに対応した処理が必要 return Application.persistentDataPath; #endif } }
$ adb shell shell@SHL25:/ $ cd /data/data/com.hoge.pdpchecker/files shell@SHL25:/data/data/com.hoge.pdpchecker/files $ ls opendir failed, Permission denied 255|shell@SHL25:/data/data/com.hoge.pdpchecker/files $ cat hoge /system/bin/sh: cat: hoge: Permission denied # アクセス権がない
ユーザーには読み出すことが出来ない様です。
おわりに
Android端末で情報をローカル保存する際の注意点について記載しました。
今回は UnityEngine.Application.persistentDataPath
を例に記載しましたが、複数のプラットフォーム上で動作するアプリである以上、これ以外にも注意すべきポイントがたくさんあるものと思います。
そういう罠に嵌る人が減ること、さらにはUnity側でプラットフォーム差分が吸収されていくことを祈りながら本記事は終わりに致します。
明日は小笠原さんによる RGBをHSVに変換して明るさとかを変えるシェーダー
の話になります。