了解しました。検索して検索しましたが、正確な答えが誰にもわからないか、見逃しました。ユーザーに次の方法でディレクトリを選択させています。
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, READ_REQUEST_CODE);
私の活動では、実際の経路をキャプチャしたいのですが、それは不可能のようです。
protected void onActivityResult(int requestCode, int resultCode, Intent intent){
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M){
//Marshmallow
} else if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.Lollipop){
//Set directory as default in preferences
Uri treeUri = intent.getData();
//grant write permissions
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//File myFile = new File(uri.getPath());
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
選択したフォルダは次のとおりです。
Device storage/test/
正確なパス名を取得するために、次のすべての方法を試しましたが、役に立ちませんでした。
File myFile = new File (uri.getPath());
//returns: /tree/1AF6-3708:test
treeUri.getPath();
//returns: /tree/1AF6-3708:test/
pickedDir.getName()
//returns: test
pickedDir.getParentFile()
//returns: null
基本的に、/tree/1AF6-3708:
を/storage/emulated/0/
に変換するか、各デバイスがその保存場所と呼ぶものに変換する必要があります。他のすべての利用可能なオプションも/tree/1AF6-37u08:
を返します。
このようにしたい理由は2つあります。
1)私のアプリでは、ファイルの場所はユーザー固有であるため、共有設定として保存します。ダウンロードして保存するデータがかなりあるので、特に追加の保存場所がある場合は、ユーザーが好きな場所にデータを配置できるようにしたいと思います。デフォルトを設定しますが、次の専用の場所ではなく、汎用性が必要です。
Device storage/Android/data/com.app.name/
2)5.0では、ユーザーがそのフォルダーへの読み取り/書き込み権限を取得できるようにしたいのですが、これが唯一の方法のようです。この問題を修正する文字列から読み取り/書き込み権限を取得できる場合。
私が見つけたすべての解決策はMediastoreに関連していますが、それは私を正確に助けてくれません。どこかで何かが足りないか、釉薬をかけているに違いありません。どんな助けでもいただければ幸いです。ありがとう。
これにより、選択したフォルダの実際のパスがわかります(選択したフォルダが外部ストレージまたは外部SDカードにあると仮定)
Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this);
ここで、FileUtilは次のクラスです。
public final class FileUtil {
static String TAG="TAG";
private static final String PRIMARY_VOLUME_NAME = "primary";
@Nullable
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
}
else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Lollipop) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("Android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.Lollipop)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.Lollipop)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}
私の活動では、実際の経路をキャプチャしたいのですが、それは不可能のようです。
これは、アクセスできるパスはもちろん、実際のパスがない可能性があるためです。考えられるドキュメントプロバイダーはたくさんありますが、その中にはすべてのドキュメントがデバイス上にローカルにあるものもあれば、外部ストレージにファイルがあり、そこで作業できるものもあります。
ダウンロードして保存するデータがかなりあり、ユーザーが好きな場所に配置できるようにしたい
次に、Storage Access Frameworkから取得するドキュメント/ツリーが常にローカルであると考えるのではなく、 Storage Access Framework API を使用します。または、ACTION_OPEN_DOCUMENT_TREE
を使用しないでください。
5.0では、ユーザーがそのフォルダーへの読み取り/書き込み権限を取得できるようにしたい
これは、ユーザーがそのストレージプロバイダーと対話する方法の一部として、ストレージプロバイダーによって処理されます。あなたは関与していません。
アプリの設定画面でSAFUIを使用してカスタムディレクトリを選択する前、または選択しなかった場合に、デフォルトの保存ディレクトリを追加しようとしました。ユーザーがフォルダの選択を見逃す可能性があり、アプリがクラッシュする可能性があります。デバイスメモリにデフォルトのフォルダを追加するには、
DocumentFile saveDir = null;
saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
String uriString = saveDir.getUri().toString();
List<UriPermission> perms = getContentResolver().getPersistedUriPermissions();
for (UriPermission p : perms) {
if (p.getUri().toString().equals(uriString) && p.isWritePermission()) {
canWrite = true;
break;
}
}
// Permitted to create a direct child of parent directory
DocumentFile newDir = null;
if (canWrite) {
newDir = saveDir.createDirectory("MyFolder");
}
if (newDir != null && newDir.exists()) {
return newDir;
}
このスニペットは、デバイスのメインメモリ内にディレクトリを作成し、そのフォルダとサブフォルダに読み取り/書き込み権限を付与します。 MyFolder/MySubFolder階層を直接作成することはできません。別のディレクトリを再度作成する必要があります。
そのディレクトリに書き込み権限があるかどうかを確認できます。3つのデバイスで見た限り、DocumentFile
クラスではなくFile
を使用して作成された場合はtrueを返します。これは、ACTION_OPEN_DOCUMENT_TREE
を使用せずにAndroid> = 5.0の書き込み権限を作成して付与するための簡単な方法です。