18

わかりました、検索して検索しましたが、誰も正確な答えを持っていないか、見逃しました。ユーザーに次の方法でディレクトリを選択させています。

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 に関連していますが、正確には役に立ちません。私はどこかで何かが欠けているか、それを艶消ししたに違いありません。どんな助けでも大歓迎です。ありがとう。

4

6 に答える 6

46

これにより、選択したフォルダーの実際のパスが得られます。これは、ローカル ストレージに属するファイル/フォルダーに対してのみ機能します。

Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this); 

FileUtil は次のクラスです

public final class FileUtil {

    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;
    }


    private static String getVolumePath(final String volumeId, Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
            return null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
            return getVolumePathForAndroid11AndAbove(volumeId, context);
        else
            return getVolumePathBeforeAndroid11(volumeId, context);
    }


    private static String getVolumePathBeforeAndroid11(final String volumeId, Context context){
        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);

                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))    // primary volume?
                    return (String) getPath.invoke(storageVolumeElement);

                if (uuid != null && uuid.equals(volumeId))    // other volumes?
                    return (String) getPath.invoke(storageVolumeElement);
            }
            // not found.
            return null;
        } catch (Exception ex) {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.R)
    private static String getVolumePathForAndroid11AndAbove(final String volumeId, Context context) {
        try {
            StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
            for (StorageVolume storageVolume : storageVolumes) {
                // primary volume?
                if (storageVolume.isPrimary() && PRIMARY_VOLUME_NAME.equals(volumeId))
                    return storageVolume.getDirectory().getPath();

                // other volumes?
                String uuid = storageVolume.getUuid();
                if (uuid != null && uuid.equals(volumeId))
                    return storageVolume.getDirectory().getPath();

            }
            // 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;
    }
}

アップデート:

コメントに記載されているダウンロードの問題に対処するには:Downloadsデフォルトの Android ファイル ピッカーの左のドロワーから選択すると、実際にはディレクトリを選択していません。ダウンロードはプロバイダーです。通常のフォルダ ツリーの uri は次のようになります。

content://com.android.externalstorage.documents/tree/primary%3ADCIM

ダウンロードのツリー uri は

content://com.android.providers.downloads.documents/tree/downloads 

externalstorage一方が言っているのに対し、もう一方が言っていることがわかりますproviders。そのため、ファイル システム内のディレクトリに一致させることができません。ディレクトリではないためです。

解決策: 等価チェックを追加し、ツリー uri がそれと等しい場合は、次のように取得できるデフォルトのダウンロード フォルダー パスを返すことができます。

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 

必要に応じて、すべてのプロバイダーに対して同様のことを行います。そして、それは正しく動作most of the timeすると思います。しかし、そうではないエッジケースがあると思います。

Android R ケースをサポートしてくれた @DuhVir に感謝します

于 2016-03-22T18:44:59.583 に答える
3

私の活動では、実際のパスをキャプチャしたいと考えていますが、これは不可能に思えます。

これは、アクセスできるパスは言うまでもなく、実際のパスがない可能性があるためです。考えられるドキュメント プロバイダーは多数ありますが、すべてのドキュメントをデバイス上にローカルに保持しているものはほとんどなく、外部ストレージにファイルを保持して作業できるものはほとんどありません。

ダウンロードして保存するかなりの量のデータがあり、ユーザーが好きな場所に配置できるようにしたい

次に、ストレージ アクセス フレームワークから取得するドキュメント/ツリーが常にローカルであると考えるのではなく、ストレージ アクセス フレームワーク APIを使用します。または、使用しないでくださいACTION_OPEN_DOCUMENT_TREE

5.0 では、ユーザーがそのフォルダへの読み取り/書き込み権限を取得できるようにしたい

これは、ユーザーがそのストレージ プロバイダーと対話する方法の一部として、ストレージ プロバイダーによって処理されます。あなたは関与していません。

于 2016-01-21T15:56:55.417 に答える