バックグラウンド
ファイルの同期に SD カードを多用するアプリがいくつかあります。Kitkat での外部 SD カード アクセスの破損は依然として大きな問題ですが、Lollipop で利用できる新しい API を使用してこれを解決しようとしています。
SD カードへのアクセス許可を正常に要求して保持し、アクセス許可アクティビティから返されたルート Uri にファイルを一覧表示できます。
これがどのように行われるかについての詳細は、こちらを参照してください: how-to-use-the-new-sd-card-access-api-presented-for-lollipop
その後、ユーザーは任意のフォルダー/サブフォルダーを選択して同期することができ、フォルダー ドキュメント Uri を文字列としてデータベースに保存します。
問題
後で、アプリが再起動された後、ファイルの同期が開始される可能性があります。次に、サブフォルダー内のファイルを一覧表示しようとします (覚えておいてください、私には適切なアクセス許可が付与されており、この永続性が機能し、すべての子へのアクセスも許可されます)。
次に、保存された文字列から DocumentFile の新しいインスタンスを作成し、ファイルを一覧表示しようとします。
DocumentFile dir = DocumentFile.fromTreeUri(ctx, Uri.parse(storedUri));
dir.listFiles();
問題は、listFiles が常に付与されたルート Uri の子を返し、DocumentFile.fromTreeUri メソッドで指定した実際の Uri の子を返さないことです。
DocumentFileのソース コードを調べたところ、そこにバグがあるようです。具体的には、Uri をさらに変更する必要はありません。
public static DocumentFile fromTreeUri(Context context, Uri treeUri) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return new TreeDocumentFile(null, context,
DocumentsContractApi21.prepareTreeUri(treeUri));
} else {
return null;
}
DocumentsContractApi21.prepareTreeUri のソースを見ると、Uri が再構築されていることがわかります。
public static Uri prepareTreeUri(Uri treeUri) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri,
DocumentsContract.getTreeDocumentId(treeUri));
}
そしてそれが呼び出すメソッド:
public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(treeUri.getAuthority()).appendPath(PATH_TREE)
.appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
.appendPath(documentId).build();
}
public static String getTreeDocumentId(Uri documentUri) {
final List<String> paths = documentUri.getPathSegments();
if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
return paths.get(1);
}
throw new IllegalArgumentException("Invalid URI: " + documentUri);
}
getTreeDocumentId によって検出されたドキュメント ID は、メソッドが呼び出される Uri に関係なく、常にルート Uri ID に対応します。これにより、提供されたフレームワーク メソッドを使用してサブ フォルダーの子を一覧表示することができなくなります。
解決
fromTreeUri メソッドを修正して、ルート ドキュメントの Uri ID を常に使用しないようにしてください。
次の醜いハックを実行すると、問題が修正されますが、これは避けたいと思います。
Class<?> c = Class.forName("android.support.v4.provider.TreeDocumentFile");
Constructor<?> constructor = c.getDeclaredConstructor(DocumentFile.class, Context.class, Uri.class);
constructor.setAccessible(true);
DocumenFile dir = (DocumentFile) constructor.newInstance(null, mCtx, treeUri);
dir.listFiles();