IT story

URI, Android KitKat 새로운 스토리지 액세스 프레임 워크에서 실제 경로 확보

hot-time 2020. 5. 10. 10:25
반응형

URI, Android KitKat 새로운 스토리지 액세스 프레임 워크에서 실제 경로 확보


Android 4.4 (KitKat) 에서 새 갤러리에 액세스하기 전에이 방법으로 SD 카드의 실제 경로를 얻었습니다.

public String getPath(Uri uri) {
   String[] projection = { MediaStore.Images.Media.DATA };
   Cursor cursor = managedQuery(uri, projection, null, null, null);
   startManagingCursor(cursor);
   int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
   cursor.moveToFirst();
 return cursor.getString(column_index);
}

이제 Intent.ACTION_GET_CONTENT는 다른 데이터를 반환합니다.

전에:

content://media/external/images/media/62

지금:

content://com.android.providers.media.documents/document/image:62

SD 카드에서 실제 경로를 얻으려면 어떻게해야합니까?


참고 :이 답변은 문제의 일부를 해결합니다. 완전한 해결책 (도서관 형태)은 Paul Burke의 답변을보십시오 .

URI를 사용하여을 얻은 document id다음 MediaStore.Images.Media.EXTERNAL_CONTENT_URI또는 MediaStore.Images.Media.INTERNAL_CONTENT_URI(SD 카드 상황에 따라) 쿼리 할 수 있습니다 .

문서 ID를 얻으려면

// Will return "image:x*"
String wholeID = DocumentsContract.getDocumentId(uriThatYouCurrentlyHave);

// Split at colon, use second item in the array
String id = wholeID.split(":")[1];

String[] column = { MediaStore.Images.Media.DATA };     

// where id is equal to             
String sel = MediaStore.Images.Media._ID + "=?";

Cursor cursor = getContentResolver().
                          query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                          column, sel, new String[]{ id }, null);

String filePath = "";

int columnIndex = cursor.getColumnIndex(column[0]);

if (cursor.moveToFirst()) {
    filePath = cursor.getString(columnIndex);
}   

cursor.close();

참조 :이 솔루션의 출처를 찾을 수 없습니다. 원본 포스터에 여기에 기여해달라고 요청하고 싶었습니다. 오늘 밤 좀 더 보일 것입니다.


이렇게하면 MediaProvider, DownloadsProvider 및 ExternalStorageProvider에서 파일 경로가 표시되지만 언급 한 비공식 ContentProvider 메서드로 돌아갑니다.

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static String getDataColumn(Context context, Uri uri, String selection,
        String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

이것들은 오픈 소스 라이브러리 aFileChooser 에서 가져 왔습니다 .


아래 답변은 https://stackoverflow.com/users/3082682/cvizv의해 더 이상 존재하지 않는 페이지에 작성되었습니다 . 질문에 대답 할 충분한 담당자가 없기 때문에 게시하고 있습니다. 나에 의해 크레딧이 없습니다.

public String getImagePath(Uri uri){
   Cursor cursor = getContentResolver().query(uri, null, null, null, null);
   cursor.moveToFirst();
   String document_id = cursor.getString(0);
   document_id = document_id.substring(document_id.lastIndexOf(":")+1);
   cursor.close();

   cursor = getContentResolver().query( 
   android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
   null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
   cursor.moveToFirst();
   String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
   cursor.close();

   return path;
}

편집 : 코드에 흐름이 있습니다. 장치에 둘 이상의 외부 저장소 (외부 sdcard, 외부 USB 등)가있는 경우 코드보다 위의 기본 저장소가 작동하지 않습니다.


Before new gallery access in KitKat I got my real path in sdcard with this method

That was never reliable. There is no requirement that the Uri that you are returned from an ACTION_GET_CONTENT or ACTION_PICK request has to be indexed by the MediaStore, or even has to represent a file on the file system. The Uri could, for example, represent a stream, where an encrypted file is decrypted for you on the fly.

How could I manage to obtain the real path in sdcard?

There is no requirement that there is a file corresponding to the Uri.

Yes, I really need a path

Then copy the file from the stream to your own temporary file, and use it. Better yet, just use the stream directly, and avoid the temporary file.

I have changed my Intent.ACTION_GET_CONTENT for Intent.ACTION_PICK

That will not help your situation. There is no requirement that an ACTION_PICK response be for a Uri that has a file on the filesystem that you can somehow magically derive.


This answer is based on your somewhat vague description. I assume that you fired an intent with action: Intent.ACTION_GET_CONTENT

And now you get content://com.android.providers.media.documents/document/image:62 back instead of the previously media provider URI, correct?

On Android 4.4 (KitKat) the new DocumentsActivity gets opened when an Intent.ACTION_GET_CONTENT is fired thus leading to grid view (or list view) where you can pick an image, this will return the following URIs to calling context (example): content://com.android.providers.media.documents/document/image:62 (these are the URIs to the new document provider, it abstracts away the underlying data by providing generic document provider URIs to clients).

You can however access both gallery and other activities responding to Intent.ACTION_GET_CONTENT by using the drawer in the DocumentsActivity (drag from left to right and you'll see a drawer UI with Gallery to choose from). Just as pre KitKat.

If you still which to pick in DocumentsActivity class and need the file URI, you should be able to do the following (warning this is hacky!) query (with contentresolver):content://com.android.providers.media.documents/document/image:62 URI and read the _display_name value from the cursor. This is somewhat unique name (just the filename on local files) and use that in a selection (when querying) to mediaprovider to get the correct row corresponding to this selection from here you can fetch the file URI as well.

The recommended ways of accessing document provider can be found here (get an inputstream or file descriptor to read file/bitmap):

Examples of using documentprovider


I had the exact same problem. I need the filename so to be able to upload it to a website.

It worked for me, if I changed the intent to PICK. This was tested in AVD for Android 4.4 and in AVD for Android 2.1.

Add permission READ_EXTERNAL_STORAGE :

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Change the Intent :

Intent i = new Intent(
  Intent.ACTION_PICK,
  android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  );
startActivityForResult(i, 66453666);

/* OLD CODE
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
  Intent.createChooser( intent, "Select Image" ),
  66453666
  );
*/

I did not have to change my code the get the actual path:

// Convert the image URI to the direct file system path of the image file
 public String mf_szGetRealPathFromURI(final Context context, final Uri ac_Uri )
 {
     String result = "";
     boolean isok = false;

     Cursor cursor = null;
      try { 
        String[] proj = { MediaStore.Images.Media.DATA };
        cursor = context.getContentResolver().query(ac_Uri,  proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        result = cursor.getString(column_index);
        isok = true;
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }

      return isok ? result : "";
 }

Try this:

//KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);

Use the following in the onActivityResult:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);

Here is an updated version of Paul Burke's answer. In versions below Android 4.4 (KitKat) we don't have the DocumentsContract class.

In order to work on versions below KitKat create this class:

public class DocumentsContract {
    private static final String DOCUMENT_URIS =
        "com.android.providers.media.documents " +
        "com.android.externalstorage.documents " +
        "com.android.providers.downloads.documents " +
        "com.android.providers.media.documents";

    private static final String PATH_DOCUMENT = "document";
    private static final String TAG = DocumentsContract.class.getSimpleName();

    public static String getDocumentId(Uri documentUri) {
        final List<String> paths = documentUri.getPathSegments();
        if (paths.size() < 2) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }

        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }
        return paths.get(1);
    }

    public static boolean isDocumentUri(Uri uri) {
        final List<String> paths = uri.getPathSegments();
        Logger.v(TAG, "paths[" + paths + "]");
        if (paths.size() < 2) {
            return false;
        }
        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            return false;
        }
        return DOCUMENT_URIS.contains(uri.getAuthority());
    }
}

We need to do the following changes/fixes in our earlier onActivityResult()'s gallery picker code to run seamlessly on Android 4.4 (KitKat) and on all other earlier versions as well.

Uri selectedImgFileUri = data.getData();

if (selectedImgFileUri == null ) {

    // The user has not selected any photo
}

try {

   InputStream input = mActivity.getContentResolver().openInputStream(selectedImgFileUri);
   mSelectedPhotoBmp = BitmapFactory.decodeStream(input);
}
catch (Throwable tr) {

    // Show message to try again
}

참고URL : https://stackoverflow.com/questions/20067508/get-real-path-from-uri-android-kitkat-new-storage-access-framework

반응형