Content 의 Uri 로부터 FilePath 가져오기


Android 개발을 하다보면, 폰의 스토리지에서 파일을 불러오는 기능을 구현할 때가 많습니다.

예를들면, 사진 편집을 위해서 앨범에서 파일을 불러온다든지, 파일 전송을 위해서 음악 파일을 가져온다든지 하는 기능들 말입니다.




짜증나는 Uri



보통 파일을 읽어올때, 아래와 같은 형식으로 Intent 를 사용합니다.

다음은 wav 파일을 선택하는 경우에 대한 코드입니다.



Intent intent_upload = new Intent();
                intent_upload.setType("audio/x-wav");
                intent_upload.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(intent_upload, 1);


심플하게 이렇게 사용하지요. 그리고나서 아시다시피 아래처럼 Uri 를 가져옵니다.



@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == 1 && resultCode == RESULT_OK) {
            Uri fileUri = data.getData();
        }
    }

이제 위의 fileUri 를 가지고 절대적인 filePath 를 뽑아내려면 어떻게 해야 할까요?

"/storage/emulated/0/xxx.wav" 요런 형태 말입니다.


fileUri.getPath() 를 하면, 운이 좋은 경우에는 한번에 가져와 지는 경우가 있습니다.

"/storage/" 형태 말입니다.


즉 이것은 호출되는(실행한) Intent 가 어떻게 주느냐에 달린것이라 할 수 있겠지요.

그런데, Android 6.0.1 에서는 거지같이 넘어옵니다.


악명높은 media id 가 넘어오죠.

아래처럼 말이죠.


"/document/audio:2898"


어쩌라고...............

개인적으로 이런 형태의 Uri 를 아무런 쓰잘데기가 없습니다.

(열받아서 하는 소리입니다. ㅎ)



아무튼 이런 놈들은 Media DB 에서 Query 를 통해, absolute path 를 뽑아내야 합니다.

아래 코드에 보면 MediaStore.Files ~ 로 시작하는 값들이 있는데,,

뽑아낸 것이 Image 이냐, Audio 이냐 Video 이냐에 따라서, 조금씩 바뀔 수 있습니다.

그런데 아마도 코드에서처럼 Files. 는 웬만해서 제대로 다 뽑아질 겁니다.


대략적인 형태는 아래와 같습니다.


private String getRealPathFromURI(Uri contentUri) {
        if (contentUri.getPath().startsWith("/storage")) {
            return contentUri.getPath();
        }

        String id = DocumentsContract.getDocumentId(contentUri).split(":")[1];
        String[] columns = { MediaStore.Files.FileColumns.DATA };
        String selection = MediaStore.Files.FileColumns._ID + " = " + id;
        Cursor cursor = getContentResolver().query(MediaStore.Files.getContentUri("external"), columns, selection, null, null);
        try {
            int columnIndex = cursor.getColumnIndex(columns[0]);
            if (cursor.moveToFirst()) {
                return cursor.getString(columnIndex);
            }
        } finally {
            cursor.close();
        }
        return null;
    }


사실 포스팅 할만한 주제는 아니었는데,
매번 찾아보기도 너무 귀찮고, 빡쳐서~~ 적어봅니다.

라이브러리나 Android 내부적으로는 저 id 형태로 된 uri 를 쓰는 경우를 종종 보는데요.
정확히 어떤 용도를 위해서 쓰는지는 잘 모르겠네요.
대충 MediaStore 에 파싱한 메타데이터 관리를 편하게 하려고 쓰는것 같긴 한데요.
마치 No-SQL 에서 ID 같은 존재랄까..

아무튼 잘 아시는분 있으면 코멘트 부탁드려요.


그럼 포스팅 마칩니다 :)





P.S - text/plain 과 같은 파일들은 위 코드로 해결되지 않네요.
아래는 StackOverflow 에서 주워온 코드입니다.
잘 동작하네요 :)


   /**
 * 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());
}


출처 : http://stackoverflow.com/a/27271131