Android 10 (API 29)対応で画像や動画ファイルをサーバに送信させたときの話


targetSdkVersionを29したときに画像のサーバ送信で苦戦したので備忘録として

問題点

「ファイル アクセスに必要なパーミッション」の項にあるこの部分

READ_EXTERNAL_STORAGE パーミッションが付与されていたとしても、外部ストレージ デバイスの未加工ファイル システム ビューにアクセスするアプリの場合、アクセスできるのは、アプリ固有ディレクトリに限られます。アプリが未加工ファイル システム ビューを使用してアプリ固有ディレクトリの外部にあるファイルを開こうとすると、エラーが発生します

どうやら
「自分が作成した画像(動画)以外は使っちゃだめよ!
ユーザが画像を選択後に、内部的に別のアプリがその画像を加工してたとしたら大変でしょ?(意訳」
という事らしい

一応逃げ道として

アプリが対象範囲別ストレージに完全に対応するまでは、アプリのターゲット SDK レベルや requestLegacyExternalStorage マニフェスト属性に基づいて、一時的に対象範囲別ストレージ機能を無効にすることができます。

とあるがいつまで使えるかはよくわからん

要件まとめ

・targetSdkVersionを29に変更する
・画像をサーバーに送れるようにする
・アプリは複数ある(minSdkVersionはまちまち
)
・すぐに更新しなければならなくなる事を考えて requestLegacyExternalStorage は使いたくない

解決策

選択したファイルを自アプリのキャッシュに一度保存してそかからサーバにアップする

苦戦した事

・minSdkVersionがまちまちのため、使える手段が少なかった(Paths も Files も使えない)
・FileInputStreamしただけでも怒られる
・1Byteづつコピーするのは時間がかかりすぎる

実際の対応

URIからInputStreamを作ってそっからChannelをかませて自アプリのキャッシュDirに出力した

実際の実装(一部抜粋)

        cacheFile = new File(activity.getCacheDir(), fileName);
        try {
            InputStream inputStream = activity.getContentResolver().openInputStream(fileUri);

            FileOutputStream fOutputStream = new FileOutputStream(cacheFile);


            try {
                fOutputStream.getChannel().transferFrom(Channels.newChannel(inputStream), 0, Long.MAX_VALUE);
            } finally {
                fOutputStream.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return cacheFile.getPath();

最後に

色々長時間ごにょごにょしてた気がするけど終わってみれば「こんなものですむのか・・・」といったエンジニアあるある

何かご指摘事項等ありましたらお手柔らかにお願いします