UnityでAndroidアプリを作る際にAndroid10(Q)だとデータの保存に手こずった話

クロ【喜び】
久しぶりのプログラムのお話ですね
冬月がアプリ開発を進めてない中、
世の中ではだいぶ仕組みが変わっていたようです

霊夢【悲しみ】
冬月が作るアプリはセーブデータが外部から操作できる前提で作ってて、端末のストレージ直下に保存していたんだけど、それが使えなくなってしまったのよね……

魔理沙【悲しみ】
今までは権限があればどのディレクトリにでも保存できたんだけど、セキュリティ関連の強化でディレクトリ関連が厳格になったんだよな

妖夢【普通】
今回はAndroid開発をしたことある人前提で内容が進みますので少々わかりにくかもしれません……すみません。

今回は解説メインなので会話形式は少なめにします

クロ【悲しみ】
冬月はボキャブラリが少ないからね仕方ないね
いや、ちゃんとした理由もありますよ

霊夢【悲しみ】
今回はちゃんと説明するために、
下書きを書いてから会話形式に起こそうと思ったんだけど
会話形式にしてしまうと説明で内容がだれてしまいそうだったのでやめたわ

魔理沙【悲しみ】
会話形式だと文章が2倍から3倍に膨れ上がってしまうからね
ただでさえ文章が多いのに、膨れ上がると読みづらくなるしね
これも冬月の文章力のなさによるものだと思うけどね

妖夢【普通】
ということで、通常は普通の文章形式でいきます
要点要点で会話を入れておこうかなと思います

Android10以降からデータの読み書きが厳格に管理されるようになった

今までは権限さえあればどこのディレクトリにもファイルの読み書きができていた

Android10以前はManifest.xmlにファイルの書き込み権限である

android.permission.WRITE_EXTERNAL_STORAGE

パーミッション設定しておけば良かったのですが、
Android10以降からはこれを設定してもアプリの関係ない領域に
ファイルを自由に読み書きできなくなってしまいました。

冬月はQRコードを使ったお会計ソフトをUnityで開発してリリースしているのですが、

https://play.google.com/store/apps/details?id=com.StudioCross.DOUZINCashRegister

QRコードの画像データをスマホ端末から簡単に取り出しやすくするために内部ストレージの直下に

StudioCross/QR_CashRegister/

というディレクトリを作ってこの中にお会計データやQR画像を保存してました。

このプログラムはAndroid10が発表される前に開発したので
案の定Android10になって動かなくなってしまったというわけです……

そこで、色々調べていくうちに対象範囲別外部ストレージとなるものが有ること発見しAndroid10以降で起こるバグということが原因がわかりました。

クロ【怒り】
かんたんに説明しますと
冬月の作ったアプリはアプリデータを外部ファイル保存していたんですがセキュリティが厳格になったAndroid10より正しく動かなくなってしまったというわけです。

霊夢【悲しみ】
別に無策で保存してたわけじゃなくて
権限を付与したりと正規の方法を取っていたんだけど、それすらも認められなくなってしまったというわけね

対象範囲別外部ストレージってなんだ?

Android10から導入された対象範囲別外部ストレージですが、
これはAndroidのアプリに対してのファイル管理に関することです。

今まではそのファイルもファイルの書き込み権限である

android.permission.WRITE_EXTERNAL_STORAGE

パーミッション設定しておけばどのファイルをアクセスできましたが
それではセキュリティ的に駄目でしょって事で変えられたみたいです。

例えばの話ですが
Aというゲームが有ってそれはテキストデータでゲームデータを保存してました
BというテキストエディタAの書き出したゲームデータをいじれば
ゲームデータの改ざんが容易にできてしまいます。

対象範囲別外部ストレージを設定することで、
AというゲームとBというテキストエディタは別アプリなので操作することができなくなります。

これを行うことで、セキュリティを高めていこうというわけですね。

魔理沙【驚き】
今まで権限さえあればどのディレクトリにでも保存できたんだけど違うアプリの領域にデータを読み書きできるのはセキュティ的に駄目でしょってことになって、権限を持っていても自分のアプリ領域家共有ディレクトリ以外は保存できなくなってしまったんだな

妖夢【普通】
冬月のアプリはストレージ直下にディレクトリを作ってそこにデータを保存する形式だったので、許容できなくなったみたいです
ネイティブレベルでいじれば以前のままで保存できると思うのですが、Unityに組み込むとなるとちょっと手間かなと……

プログラム側での変更点
アプリの保存されているディレクトリは逆に権限がなくても
ファイルの読み書きができるようになった

このおかげでファイルの読み書き時に必要だった権限である

android.permission.WRITE_EXTERNAL_STORAGE

パーミッション設定をする必要がなくなりました。
アプリが保存されているディレクトリだったら自由にアクセス出来るようになりました。

この点は良くなった点でしょうか?
ゲームなどの外部からデータをアクセスされないアプリの場合に使えますね。

アプリが保存されている場所以外のディレクトリにアクセスする場合は
アクセスするファイルっごとに設定が必要

ここかイマイチ冬月も理解しきれてないところでは有るのですが、

写真や動画などのメディアデータ

DCIM/(写真データが格納されているディレクトリ)
Movies/(動画データが格納されているディレクトリ)
Pictures/(画像データが格納されているディレクトリ)

この3つのディレクトリに保存するようになりました。
(それ以外のフォルダに保存する方法もあるとは思いますが、まだ調査中です)

ソースコードも

MediaStore.Images

を使って読み書きするようです

音楽などのサウンドデータ

Music/(音楽データが格納されているディレクトリ)
Alarms/(プレイリストやアルバム情報が格納されているディレクトリ)

このあたりに保存する必要があるようです。

ソースコードも

MediaStore.Audio

を使って読み書きするようです。

クロ【喜び】
で、新しいシステムだと
専用の保存処理を使って共有ディレクトリに保存するようになりました

霊夢【喜び】
画像ならPictureディレクトリ、音声ならMusicディレクトリ、文章ならDocumentディレクトリに保存するようになったわけね

じゃあ、冬月のやりたいテキストデータはどこに保存すればよいの……?
情報が出てこない……

Android10からデータの書き出し方法が変わるぞー
っていう情報はたくさん出てくるのですが、
保存方法まで解説しているページがなかったんですよね……

魔理沙【喜び】
実はメールで何通か着てたんだよな
英文文章で全く読んでなかったから重要性に気が付かずに今まで経過してしまったぞ……

妖夢【普通】
正直Androidへの書き出しって何かとエラーが出たり
手間がかかったりするのでなるべくやりたくないんですよね……

Storage Access Frameworkを使って保存する方法?
Storage Access Framework

を使えばドキュメントやいろんなファイルが保存できるみたいなんですが

これ、Windowsのファイルの読み込み書き込みみたいにファイル名や保存先をユーザーさんに投げかける物なんですよね……

んー、テキストエディタとかだったらファイル名や保存先の変更を
ユーザーさんに決めてもらうのは有りだと思うのですが
今回のアプリはそれは無いほうが良いんですけどねぇ……

しかもUnityで拙作しているので、アクセス方法が難しそう……

冬月の取った解決方法(暫定)

https://play.google.com/store/apps/details?id=com.StudioCross.DOUZINCashRegister

今回のこのアプリでは、データの保存するディレクトリをアプリインストールディレクトリに変更して暫定対応しました。

  

    // 保存先ディレクトリを取得する    
    string folderPath = "";

    // Android10以下は今までの方法でOK
    using (AndroidJavaClass env = new AndroidJavaClass("android.os.Environment"))
    {
        using (AndroidJavaObject storageDir = env.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
        {
            folderPath = storageDir.Call<string>("getCanonicalPath");
        }
    }

/** 以下保存処理 **/

これが今までのAndroidのルートディレクトリを取得する方法です
(Android10以降でもディレクトリの取得はできますが読み書きができなくなります)


/// <summary>
/// Androidのバージョンを取得
/// </summary>
/// <returns>バージョン番号</returns>
public static int CallGetVersion()
{
#if !UNITY_EDITOR && UNITY_ANDROID
    var cls = new AndroidJavaClass("android.os.Build$VERSION");
    var apiLevel = cls.GetStatic<int>("SDK_INT");
    return apiLevel;
#else
    return -1;
#endif
}

/** 略 **/

    // 保存先ディレクトリを取得する
    string folderPath = "";    

    if(CallGetVersion() < 10)
    {
        // Android10以下は今までの方法でOK
        using (AndroidJavaClass env = new AndroidJavaClass("android.os.Environment"))
        {
            using (AndroidJavaObject storageDir = env.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
            {
                folderPath = storageDir.Call<string>("getCanonicalPath");
            }
        }
    }
    else
    {
        // AndroidQ(10)より保存方法が厳格になったので暫定対応……
        folderPath = Application.persistentDataPath;
    }

/** 以下保存処理 **/

こちらが暫定的に対応した方法です
まずはAndroidのバージョンを取得する関数を用意しましてそれで動かしているAndroidのバージョンを取得します。

Android10以前は今までの処理で動くのでそのままにして、
Android10以降はUnityのデフォルトで指定できるアプリディレクトリ取得してその中で保存するようにしました。

うう、かっこ悪いよぉ……

なんとか書き込み権限を取得して今まで通りのやり方で
やりたかったんですけどね……

1、2日ほどしか調べてないので
何かわかりましたら改めて書いていきたいと思います。

クロ【悲しみ】
これは、今までの方法をやめて
アプリ内のディレクトリに保存するように変更しました

霊夢【悲しみ】
QR会計のアプリは画像データとか売上データが有るからあんまり奥まったディレクトリに保存したくなんだけどねぇ……

魔理沙【悲しみ】
今回、もうリリースされたアプリなので安全策を取りましたが
次回は別な方法を思いつきましたので、そちらを試してみたいと思うぞ

妖夢【普通】
別な方法というのは、「名前を付けて保存」画面を表示させて保存させる方法です
いつかは今リリースされているアプリもこちらに変更しようと思います

 

Unity | ゲーム製作の関連記事

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です