如何从 Uri xamarin android 获取实际路径

Posted

技术标签:

【中文标题】如何从 Uri xamarin android 获取实际路径【英文标题】:How to get actual path from Uri xamarin android 【发布时间】:2018-04-09 02:10:35 【问题描述】:

我想从 Uri 获取实际路径,当我使用意图选择文件时,它将返回 URI,但下面的代码行不适用于将 URI 转换为字符串路径

打开文件选择器

public static void PickFile(Activity context)

    Intent intent = new Intent(Intent.ActionGetContent);
    intent.SetType("*/*");
    context.StartActivityForResult(intent, PICKFILE_RESULT_CODE);

获取结果

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)

    if(requestCode == PICKFILE_RESULT_CODE)
    
        string filepath = GetRealPathFromURI(MainActivity.act, data.Data);
     


public static string GetRealPathFromURI(Activity act, android.Net.Uri contentURI)

    try
    
        ICursor cursor = act.ContentResolver.Query(contentURI, null, null, null, null);
        cursor.MoveToFirst();
        string documentId = cursor.GetString(0);
        documentId = documentId.Split(':')[1];
        cursor.Close();

        cursor = act.ContentResolver.Query(
        MediaStore.Images.Media.ExternalContentUri,
        null, MediaStore.Images.Media.InterfaceConsts.Id + " = ? ", new[]  documentId , null);
        cursor.MoveToFirst();
        string path = cursor.GetString(cursor.GetColumnIndex(MediaStore.Images.Media.InterfaceConsts.Data));
        cursor.Close();

        return path;
    
    catch(Exception e)
    
        Log.Debug("TAG_DATA",e.ToString());
        return "";
    

错误

System.IndexOutOfRangeException:索引超出范围 数组。

谁能给我解释一下,我在这段代码中做错了什么。

【问题讨论】:

"I want to get actual path from Uri," 不,你不需要,你需要一个InputStream,阅读ContentResolver API 文档了解如何做到这一点 好的,那我想知道文件名和类型怎么办? 参见 ContentResolverandroid.provider.OpenableColumns 文档 【参考方案1】:

如何从 Uri xamarin android 获取实际路径

我注意到您正在使用MediaStore.Images.Media.ExternalContentUri 属性,您想要的是获取图库图像的真实路径?

如果你想实现这个功能,你可以阅读我的回答:Get Path of Gallery Image ?

private string GetRealPathFromURI(Android.Net.Uri uri)

    string doc_id = "";
    using (var c1 = ContentResolver.Query(uri, null, null, null, null))
    
        c1.MoveToFirst();
        string document_id = c1.GetString(0);
        doc_id = document_id.Substring(document_id.LastIndexOf(":") + 1);
    

    string path = null;

    // The projection contains the columns we want to return in our query.
    string selection = Android.Provider.MediaStore.Images.Media.InterfaceConsts.Id + " =? ";
    using (var cursor = ContentResolver.Query(Android.Provider.MediaStore.Images.Media.ExternalContentUri, null, selection, new string[]  doc_id , null))
    
        if (cursor == null) return path;
        var columnIndex = cursor.GetColumnIndexOrThrow(Android.Provider.MediaStore.Images.Media.InterfaceConsts.Data);
        cursor.MoveToFirst();
        path = cursor.GetString(columnIndex);
    
    return path;

更新:

这是一个解决方案:

private string GetActualPathFromFile(Android.Net.Uri uri)

    bool isKitKat = Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Kitkat;

    if (isKitKat && DocumentsContract.IsDocumentUri(this, uri))
    
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri))
        
            string docId = DocumentsContract.GetDocumentId(uri);

            char[] chars =  ':' ;
            string[] split = docId.Split(chars);
            string type = split[0];

            if ("primary".Equals(type, StringComparison.OrdinalIgnoreCase))
            
                return Android.OS.Environment.ExternalStorageDirectory + "/" + split[1];
            
        
        // DownloadsProvider
        else if (isDownloadsDocument(uri))
        
            string id = DocumentsContract.GetDocumentId(uri);

            Android.Net.Uri contentUri = ContentUris.WithAppendedId(
                            Android.Net.Uri.Parse("content://downloads/public_downloads"), long.Parse(id));

            //System.Diagnostics.Debug.WriteLine(contentUri.ToString());

            return getDataColumn(this, contentUri, null, null);
        
        // MediaProvider
        else if (isMediaDocument(uri))
        
            String docId = DocumentsContract.GetDocumentId(uri);

            char[] chars =  ':' ;
            String[] split = docId.Split(chars);

            String type = split[0];

            Android.Net.Uri contentUri = null;
            if ("image".Equals(type))
            
                contentUri = MediaStore.Images.Media.ExternalContentUri;
            
            else if ("video".Equals(type))
            
                contentUri = MediaStore.Video.Media.ExternalContentUri;
            
            else if ("audio".Equals(type))
            
                contentUri = MediaStore.Audio.Media.ExternalContentUri;
            

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

            return getDataColumn(this, contentUri, selection, selectionArgs);
        
    
    // MediaStore (and general)
    else if ("content".Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase))
    

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.LastPathSegment;

        return getDataColumn(this, uri, null, null);
    
    // File
    else if ("file".Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase))
    
        return uri.Path;
    

    return null;


public static String getDataColumn(Context context, Android.Net.Uri uri, String selection, String[] selectionArgs)

    ICursor cursor = null;
    String column = "_data";
    String[] projection = 
    
        column
    ;

    try
    
        cursor = context.ContentResolver.Query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.MoveToFirst())
        
            int index = cursor.GetColumnIndexOrThrow(column);
            return cursor.GetString(index);
        
    
    finally
    
        if (cursor != null)
            cursor.Close();
    
    return null;


//Whether the Uri authority is ExternalStorageProvider.
public static bool isExternalStorageDocument(Android.Net.Uri uri)

    return "com.android.externalstorage.documents".Equals(uri.Authority);


//Whether the Uri authority is DownloadsProvider.
public static bool isDownloadsDocument(Android.Net.Uri uri)

    return "com.android.providers.downloads.documents".Equals(uri.Authority);


//Whether the Uri authority is MediaProvider.
public static bool isMediaDocument(Android.Net.Uri uri)

    return "com.android.providers.media.documents".Equals(uri.Authority);


//Whether the Uri authority is Google Photos.
public static bool isGooglePhotosUri(Android.Net.Uri uri)

    return "com.google.android.apps.photos.content".Equals(uri.Authority);

获取OnActivityResult中的实际路径:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)

    base.OnActivityResult(requestCode, resultCode, data);
    if (requestCode == 0)
    
        var uri = data.Data;
        string path = GetActualPathFromFile(uri);
        System.Diagnostics.Debug.WriteLine("Image path == " + path);

    

【讨论】:

ContentResolver.Query 在 VS2017 中显示以下错误非静态字段需要对象引用。可以改用:Application.Context.ContentResolver.Query 很好,但在 Android 9.0 上失败了。【参考方案2】:

阅读这篇文章,你会得到非常简单的答案。

https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html

我的代码如下:

            var targetPath = $"FileDirPath.DirectorySeparatorCharFileName";

            const int bufferSize = 1024;
            using (var inputStream = Activity.ContentResolver.OpenInputStream(audioFileUri))
            
                using (var outputStream = File.Create(targetPath))
                
                    var buffer = new byte[bufferSize];
                    while (true)
                    
                        var count = inputStream.Read(buffer, 0, bufferSize);
                        if (count > 0)
                        
                            outputStream.Write(buffer, 0, count);
                        

                        if (count < bufferSize) break;
                    
                
            

【讨论】:

这似乎是一致地获取文件内容的唯一方法。是的,它涉及复制文件,这很可惜,但在很多情况下,其他解决方案都不起作用。【参考方案3】:

这是 Xamarin.Android API 21+ 的完整解决方案: https://gist.github.com/IlyaLavrov97/a76063a49515764b60d5fba3ebbb2662

它包括来自私人下载文件夹和谷歌磁盘的文件更新。

解决方案的主要部分如下:

    /// <summary>
    /// Main feature. Return actual path for file from uri. 
    /// </summary>
    /// <param name="uri">File's uri</param>
    /// <param name="context">Current context</param>
    /// <returns>Actual path</returns>
    private static string GetActualPathForFile(global::Android.Net.Uri uri, Context context)
    
        bool isKitKat = Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat;

        if (isKitKat && DocumentsContract.IsDocumentUri(context, uri))
        
            // ExternalStorageProvider
            if (IsExternalStorageDocument(uri))
            
                string docId = DocumentsContract.GetDocumentId(uri);

                char[] chars =  ':' ;
                string[] split = docId.Split(chars);
                string type = split[0];

                if ("primary".Equals(type, StringComparison.OrdinalIgnoreCase))
                    return global::Android.OS.Environment.ExternalStorageDirectory + "/" + split[1];
            
            // Google Drive
            else if (IsDiskContentUri(uri))
                return GetDriveFileAbsolutePath(context, uri);
            // DownloadsProvider
            else if (IsDownloadsDocument(uri))
            
                try
                
                    string id = DocumentsContract.GetDocumentId(uri);

                    if (!TextUtils.IsEmpty(id))
                    
                        if (id.StartsWith("raw:"))
                            return id.Replace("raw:", "");

                        string[] contentUriPrefixesToTry = new string[]
                                "content://downloads/public_downloads",
                                "content://downloads/my_downloads",
                                "content://downloads/all_downloads"
                        ;

                        string path = null;

                        foreach (string contentUriPrefix in contentUriPrefixesToTry)
                        
                            global::Android.Net.Uri contentUri = ContentUris.WithAppendedId(
                                    global::Android.Net.Uri.Parse(contentUriPrefix), long.Parse(id));

                            path = GetDataColumn(context, contentUri, null, null);

                            if (!string.IsNullOrEmpty(path))
                                return path;
                        

                        // path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
                        string fileName = GetFileName(context, uri);
                        Java.IO.File cacheDir = GetDocumentCacheDir(context);
                        Java.IO.File file = GenerateFileName(fileName, cacheDir);

                        if (file != null)
                        
                            path = file.AbsolutePath;
                            SaveFileFromUri(context, uri, path);
                        

                        // last try
                        if (string.IsNullOrEmpty(path))
                            return global::Android.OS.Environment.ExternalStorageDirectory.ToString() + "/Download/" + GetFileName(context, uri);

                        return path;
                    
                
                catch
                
                    return global::Android.OS.Environment.ExternalStorageDirectory.ToString() + "/Download/" + GetFileName(context, uri);
                
            
            // MediaProvider
            else if (IsMediaDocument(uri))
            
                string docId = DocumentsContract.GetDocumentId(uri);

                char[] chars =  ':' ;
                string[] split = docId.Split(chars);

                string type = split[0];

                global::Android.Net.Uri contentUri = null;
                if ("image".Equals(type))
                    contentUri = MediaStore.Images.Media.ExternalContentUri;
                else if ("video".Equals(type))
                    contentUri = MediaStore.Video.Media.ExternalContentUri;
                else if ("audio".Equals(type))
                    contentUri = MediaStore.Audio.Media.ExternalContentUri;

                string selection = "_id=?";
                string[] selectionArgs = new string[]  split[1] ;

                return GetDataColumn(context, contentUri, selection, selectionArgs);
            

        
        // MediaStore (and general)
        else if ("content".Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase))
        
            // Return the remote address
            if (IsGooglePhotosUri(uri))
                return uri.LastPathSegment;

            // Google Disk document .legacy
            if (IsDiskLegacyContentUri(uri))
                return GetDriveFileAbsolutePath(context, uri);
            return GetDataColumn(context, uri, null, null);
        
        // File
        else if ("file".Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase))
            return uri.Path;

        return null;
    

【讨论】:

【参考方案4】:

简而言之,这很有帮助。

public static string GetRealPathFromURI(Activity act, Android.Net.Uri contentURI)

    try
    
        Android.Database.ICursor cursor = act.ContentResolver.Query(contentURI, null, null, null, null);
        cursor.MoveToFirst();
        string documentId = cursor.GetString(0);
        documentId = documentId.Split(':')[1];
        cursor.Close();

        cursor = act.ContentResolver.Query(
        MediaStore.Images.Media.ExternalContentUri,
        null, MediaStore.Images.Media.InterfaceConsts.Id + " = ? ", new[]  documentId , null);
        cursor.MoveToFirst();
        string path = cursor.GetString(cursor.GetColumnIndex(MediaStore.Images.Media.InterfaceConsts.Data));
        cursor.Close();

        return path;
    
    catch (Exception e)
    
        Console.WriteLine(e.ToString());
        return "";
    

【讨论】:

以上是关于如何从 Uri xamarin android 获取实际路径的主要内容,如果未能解决你的问题,请参考以下文章

错误 400:分发 Xamarin Forms 应用程序时的 redirect_uri_mismatch

Xamarin 从视图模型传递列表数据以进行视图绑定

如何从列表视图中从 onItemClick() 获得的列表项的联系人 Uri 中检索数据?

Uri 图像未显示在 android 设备上 (Xamarin)

如果我有内容 URI 而不是路径,是不是可以在 Android 中使用 Xamarin.Essentials.Launcher 启动文件?

Xamarin iOS 从 HealthKit 读取步数