冬月がアプリ開発を進めてない中、
世の中ではだいぶ仕組みが変わっていたようです
もくじ
今回は解説メインなので会話形式は少なめにします
いや、ちゃんとした理由もありますよ
下書きを書いてから会話形式に起こそうと思ったんだけど
会話形式にしてしまうと説明で内容がだれてしまいそうだったのでやめたわ
ただでさえ文章が多いのに、膨れ上がると読みづらくなるしね
これも冬月の文章力のなさによるものだと思うけどね
要点要点で会話を入れておこうかなと思います
Android10以降からデータの読み書きが厳格に管理されるようになった
今までは権限さえあればどこのディレクトリにもファイルの読み書きができていた
Android10以前はManifest.xmlにファイルの書き込み権限である
パーミッション設定しておけば良かったのですが、
Android10以降からはこれを設定してもアプリの関係ない領域に
ファイルを自由に読み書きできなくなってしまいました。
冬月はQRコードを使ったお会計ソフトをUnityで開発してリリースしているのですが、
https://play.google.com/store/apps/details?id=com.StudioCross.DOUZINCashRegister
QRコードの画像データをスマホ端末から簡単に取り出しやすくするために内部ストレージの直下に
というディレクトリを作ってこの中にお会計データやQR画像を保存してました。
このプログラムはAndroid10が発表される前に開発したので
案の定Android10になって動かなくなってしまったというわけです……
そこで、色々調べていくうちに対象範囲別外部ストレージとなるものが有ること発見しAndroid10以降で起こるバグということが原因がわかりました。
冬月の作ったアプリはアプリデータを外部ファイル保存していたんですがセキュリティが厳格になったAndroid10より正しく動かなくなってしまったというわけです。
権限を付与したりと正規の方法を取っていたんだけど、それすらも認められなくなってしまったというわけね
対象範囲別外部ストレージってなんだ?
Android10から導入された対象範囲別外部ストレージですが、
これはAndroidのアプリに対してのファイル管理に関することです。
今まではそのファイルもファイルの書き込み権限である
パーミッション設定しておけばどのファイルをアクセスできましたが
それではセキュリティ的に駄目でしょって事で変えられたみたいです。
例えばの話ですが
Aというゲームが有ってそれはテキストデータでゲームデータを保存してました
BというテキストエディタAの書き出したゲームデータをいじれば
ゲームデータの改ざんが容易にできてしまいます。
対象範囲別外部ストレージを設定することで、
AというゲームとBというテキストエディタは別アプリなので操作することができなくなります。
これを行うことで、セキュリティを高めていこうというわけですね。
ネイティブレベルでいじれば以前のままで保存できると思うのですが、Unityに組み込むとなるとちょっと手間かなと……
プログラム側での変更点
アプリの保存されているディレクトリは逆に権限がなくても
ファイルの読み書きができるようになった
このおかげでファイルの読み書き時に必要だった権限である
パーミッション設定をする必要がなくなりました。
アプリが保存されているディレクトリだったら自由にアクセス出来るようになりました。
この点は良くなった点でしょうか?
ゲームなどの外部からデータをアクセスされないアプリの場合に使えますね。
アプリが保存されている場所以外のディレクトリにアクセスする場合は
アクセスするファイルっごとに設定が必要
ここかイマイチ冬月も理解しきれてないところでは有るのですが、
写真や動画などのメディアデータは
Movies/(動画データが格納されているディレクトリ)
Pictures/(画像データが格納されているディレクトリ)
この3つのディレクトリに保存するようになりました。
(それ以外のフォルダに保存する方法もあるとは思いますが、まだ調査中です)
ソースコードも
を使って読み書きするようです
音楽などのサウンドデータは
Alarms/(プレイリストやアルバム情報が格納されているディレクトリ)
このあたりに保存する必要があるようです。
ソースコードも
を使って読み書きするようです。
専用の保存処理を使って共有ディレクトリに保存するようになりました
じゃあ、冬月のやりたいテキストデータはどこに保存すればよいの……?
情報が出てこない……
Android10からデータの書き出し方法が変わるぞー
っていう情報はたくさん出てくるのですが、
保存方法まで解説しているページがなかったんですよね……
英文文章で全く読んでなかったから重要性に気が付かずに今まで経過してしまったぞ……
手間がかかったりするのでなるべくやりたくないんですよね……
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日ほどしか調べてないので
何かわかりましたら改めて書いていきたいと思います。
アプリ内のディレクトリに保存するように変更しました
次回は別な方法を思いつきましたので、そちらを試してみたいと思うぞ
いつかは今リリースされているアプリもこちらに変更しようと思います
“UnityでAndroidアプリを作る際にAndroid10(Q)だとデータの保存に手こずった話” への1件の返信