Android:コンテンツURIからファイルURIを取得する


133

私のアプリでは、ユーザーがアプリが処理するオーディオファイルを選択します。問題は、アプリがオーディオファイルで実行したいことを実行するために、URIがファイル形式である必要があることです。Androidのネイティブミュージックプレーヤーを使用してアプリ内のオーディオファイルを参照する場合、URIは次のようなコンテンツURIです。

content://media/external/audio/media/710

しかし、人気のあるファイルマネージャーアプリケーションAstroを使用すると、次のようになります。

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

後者の方がはるかにアクセスしやすくなっていますが、もちろん、コレクションを参照するために使用するプログラムに関係なく、ユーザーが選択したオーディオファイルをアプリに機能させる必要があります。だから私の質問は、content://スタイルURIをURIに変換する方法はありfile://ますか?それ以外の場合、この問題を解決するために私に何を勧めますか?参考までに、セレクターを呼び出すコードを次に示します。

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

私はコンテンツURIで次のことを行います。

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

次に、上記のファイルを使用してFileInputStreamを実行します。


1
コンテンツURIとは異なる、どの呼び出しを使用していますか?
Phil Lello、2011

1
いないすべてのコンテンツのURIがためにあなたは、ファイルパスを取得することはできませんコンテンツのURIがたくさんあります持っているファイルパスが。ファイルパスは使用しないでください。
Mooing Duck

回答:


152

URIからgetContentResolver().openInputStream(uri)を取得するために使用しますInputStream

http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)


12
チューザアクティビティから返されたURIのスキームを確認します。uri.getScheme.equals( "content")の場合は、コンテンツリゾルバーで開きます。uri.Scheme.equals( "file")の場合は、通常のファイルメソッドを使用して開きます。どちらの方法でも、一般的なコードを使用して処理できるInputStreamになります。
Jason LeBrun、2011

16
実際、私はgetContentResolver()。openInputStream()のドキュメントを再読するだけで、「コンテンツ」または「ファイル」のスキームに対して自動的に機能するので、スキームを確認する必要ありません ...安全にできれば常にcontent://またはfile://になると想定すると、openInputStream()が常に機能します。
Jason LeBrun、2011

57
(コンテンツ:...から)InputStreamの代わりにファイルを取得する方法はありますか?
AlikElzin-kilaka 2012年

4
@kilakaファイルのパスを取得することはできますが、苦痛です。stackoverflow.com/a/20559418/294855を
Danyal Aytekin 2014年

7
この回答は、FileStreamsではなくFilesに依存するクローズドソースAPIを使用しているが、OSを使用してユーザーがファイルを選択できるようにしたい人には不十分です。@DanyalAytekinが参照した答えは、まさに私が必要とするものでした(実際、私が扱っているファイルの種類を正確に知っているので、多くの脂肪を取り除くことができました)。
monkey0506 2015年

45

これは古い回答であり、特定のコンテンツリゾルバーの問題点を克服する非推奨のハックな方法です。いくつかの巨大な塩でそれを取り、可能であれば適切なopenInputStream APIを使用してください。

Content Resolverを使用してfile://content://URI からパスを取得できます。

String filePath = null;
Uri _uri = data.getData();
Log.d("","URI = "+ _uri);                                       
if (_uri != null && "content".equals(_uri.getScheme())) {
    Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
    cursor.moveToFirst();   
    filePath = cursor.getString(0);
    cursor.close();
} else {
    filePath = _uri.getPath();
}
Log.d("","Chosen path = "+ filePath);

1
ありがとう、これは完全に機能しました。受け入れられた回答が示唆するように、InputStreamを使用できませんでした。
ldam

4
これは、それがGoogleドライブのために仕事をしません例えば、ローカルファイルに対してのみ動作します
bluewhile

1
時々動作し、時々存在しないfile:/// storage / emulated / 0 / ...を返します。
Reza Mohammadi

1
android.provider.MediaStore.Images.ImageColumns.DATAスキームが存在する場合、列「_data」()は常に存在することが保証されていcontent://ますか?
エドワードフォーク2016年

2
これは主要なアンチパターンです。一部のContentProviderはその列を提供しますが、FileContentResolverをバイパスしようとしたときに読み取り/書き込みアクセス権があるとは限りません。ContentResolverメソッドを使用してURIを操作しますcontent://。これは、Googleエンジニアが推奨する公式のアプローチです。
user1643723 2017年

9

コンテンツUri content://com.externalstorage...がある場合、このメソッドを使用して、Android 19以降のフォルダーまたはファイルの絶対パスを取得できます。

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)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

Uriの各部分がprintlnを使用して確認できます。SDカードとデバイスのメインメモリの戻り値を以下に示します。ファイルがメモリ上にある場合はアクセスして削除できますが、この方法を使用してSDカードからファイル削除することはできませんでした。この絶対パスを使用して画像を読み取るか開くだけです。この方法を使用して削除する解決策を見つけた場合は、共有してください。SDカード

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

メインメモリ

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

file:///パスを取得した後でUriを取得したい場合

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri

私はそれがアプリケーションでそれを行う正しい方法だとは思いません。悲しいことに、私はそれをすぐに使えるプロジェクトで使用しています
Bondax

@Bondaxはい、ファイルパスやファイルUrisではなく、コンテンツUrisを使用する必要があります。これがストレージアクセスフレームワークの導入方法です。ただし、ファイルuriを取得する場合は、DocumentsContractクラスを使用するため、これが他の回答の最も正しい方法です。GithubでGoogleサンプルをチェックアウトすると、これらのクラスがこのクラスを使用してフォルダーのサブフォルダーを取得することもわかります。
トラキアの

また、DocumentFileクラスは、API 19での新しい追加と、SAFからURIを使用する方法も追加しています。正しい方法は、アプリの標準パスを使用して、SAF uiを介してフォルダーへのアクセス許可を与え、共有設定にUri文字列を保存し、DocumentFileオブジェクトを含むフォルダーへのアクセスが必要な場合
Thracian

7

呼び出しによってcontent://スキームでURIを処理しようとすることはContentResolver.query()、良い解決策ではありません。4.2.2を実行しているHTC Desireでは、クエリ結果としてNULLを取得する可能性があります。

代わりにContentResolverを使用しないのはなぜですか? https://stackoverflow.com/a/29141800/3205334


しかし、時にはパスだけが必要な場合もあります。実際にファイルをメモリにロードする必要はありません。
Kimi Chiu

2
「パス」は、アクセス権がないと役に立ちません。たとえば、アプリケーションがそのcontent://プライベート内部ディレクトリ内のファイルに対応するURI を提供する場合、そのURIをFile新しいAndroidバージョンのAPIで使用することはできません。ContentResolverは、この種のセキュリティ制限を克服するように設計されています。ContentResolverからURIを取得した場合、それが機能することを期待できます。
user1643723 2017年

5

刺激を受けた答えはJason LaBrunDarth Ravenです。既に回答済みのアプローチを試すと、以下の解決策につながりました。これは、ほとんどの場合カーソルnullのケースとcontent:// からfile://への変換をカバーする可能性があります。

ファイルを変換するには、取得したURIからファイルを読み書きします

public static Uri getFilePathFromUri(Uri uri) throws IOException {
    String fileName = getFileName(uri);
    File file = new File(myContext.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
        FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
        outputStream.flush();
    }
    return Uri.fromFile(file);
}

ファイル名の使用を取得するには、カーソルがnullの場合をカバーします

public static String getFileName(Uri uri) {
    String fileName = getFileNameFromCursor(uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

MIMEタイプからファイル拡張子に変換するための適切なオプションがあります

 public static String getFileExtension(Uri uri) {
    String fileType = myContext.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

ファイル名を取得するためのカーソル

public static String getFileNameFromCursor(Uri uri) {
    Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}

ありがとう、これを一週間見てきました。このためにファイルをコピーする必要はありませんが、機能します。
justdan0227

3

さて、私は答えに少し遅れましたが、私のコードはテストされています

uriからのスキームの確認:

 byte[] videoBytes;

if (uri.getScheme().equals("content")){
        InputStream iStream =   context.getContentResolver().openInputStream(uri);
            videoBytes = getBytes(iStream);
        }else{
            File file = new File(uri.getPath());
            FileInputStream fileInputStream = new FileInputStream(file);     
            videoBytes = getBytes(fileInputStream);
        }

上記の回答では、私は、アレイバイトにビデオURIを変換し、それが問題に関連していない、私はちょうどの使用を示すために私の完全なコードをコピーFileInputStreamし、InputStream両方として私のコードで同じ作業しています。

私はフラグメントでgetActivity()である変数コンテキストを使用し、アクティビティでは単にActivityName.thisになりました

context=getActivity(); //フラグメントで

context=ActivityName.this;//活動中


私はそれが質問に関連していないことを知っていますが、どのように使用しbyte[] videoBytes;ますか?ほとんどの回答InputStreamは、画像の使い方だけを示しています。
KRK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.