无法在Android应用中的SD卡上写文件

时间:2018-04-04 07:47:36

标签: android xamarin.android

我无法在物理SD卡上写文件。我想问用户是否允许 写入整个SD卡。用户授予权限。现在我想在SD卡上的特定目录中创建文件(目录已经存在)。当我尝试打开输出流时,我得到异常:

  

Java.Lang.SecurityException:Permission Denial:编写com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/0FFF170E%3AMyDir/MyFile.MyExt from pid = 3563,uid = 10082需要android.permission.MANAGE_DOCUMENTS或grantUriPermission()

以下是活动的代码:

using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Net;
using Android.OS;
using Android.OS.Storage;

namespace MyApp
{
    [Activity(Label = "MyApp", MainLauncher = true)]
    public class MainActivity : Activity
    {
        private const int AccessRequest = 101;

        private TaskCompletionSource<string> AccessIntentTaskCompletionSource { get; set; }

        protected override async void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.Main);

            string uriWithPermission = await AccessIntentAsync();

            if (!string.IsNullOrEmpty(uriWithPermission))
            {
                string targetUri = uriWithPermission + "MyDir/MyFile.MyExt"; // MyDir exists on SD card

                // Example of targetUri:
                // content://com.android.externalstorage.documents/tree/0FFF-170E%3AMyDir/MyFile.MyExt

                WriteFileToUri(targetUri);
            }
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);

            if (requestCode == AccessRequest)
            {
                string path = null;

                if (resultCode == Result.Ok)
                {
                    path = data.DataString;
                    Android.Net.Uri uriPath = data.Data;
                    ContentResolver.TakePersistableUriPermission(uriPath, ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission);
                }

                this.AccessIntentTaskCompletionSource?.SetResult(path);
            }
        }

        private async Task<string> AccessIntentAsync()
        {
            this.AccessIntentTaskCompletionSource = new TaskCompletionSource<string>();

            StorageManager sm = GetSystemService(StorageService) as StorageManager;
            if (sm != null)
            {
                StorageVolume volume = sm.StorageVolumes.FirstOrDefault(sv => !sv.IsPrimary); // Assume only one non-primary storage volume.

                if (volume != null)
                {
                    Intent intent = volume.CreateAccessIntent(null); // request access to the entire volume
                    StartActivityForResult(intent, AccessRequest);

                    return await this.AccessIntentTaskCompletionSource.Task;
                }
            }

            return null;
        }

        private void WriteFileToUri(string targetUri)
        {
            Android.Net.Uri outputUri = Uri.Parse(targetUri);

            byte[] source = System.Text.Encoding.UTF8.GetBytes("MyFileContent");

            using (System.IO.Stream outputStream = this.ContentResolver.OpenOutputStream(outputUri)) // throws Exception
            {
                outputStream.Write(source, 0, source.Length);
                outputStream.Close();
            }
        }
    }
}

这是清单

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="MyApp.MyApp" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application android:allowBackup="true" android:label="@string/app_name"></application>
</manifest>

我在模拟器和真实设备上测试过它。两者均采用Android 7.1。

SD卡安装在&#34;用于传输照片和媒体&#34;模式。

修改 请求权限android.permission.WRITE_EXTERNAL_STORAGE在运行时不起作用。仅当我想写入 PRIMARY 外部存储时才有效。

3 个答案:

答案 0 :(得分:0)

您必须提供运行时权限

if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    Log.v(TAG,"Permission is granted");
    //File write logic here
    return true;
} else {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE)
}

答案 1 :(得分:0)

string targetUri = uriWithPermission + "MyDir/MyFile.MyExt"; // MyDir exists

即使存在MyDir,您的代码也无法正常工作,因为它不是正确的uri文件。

此外,如果用户选择了MyDir目录并且您的coude看起来像

string targetUri = uriWithPermission + "/MyFile.MyExt";

使用输出流创建文件的代码仍无效。

相反,您应该使用DocumentFile来获取所选目录的实例。之后,您可以使用DocumentFile.createFile()创建子目录或文件。

您不需要清单和运行时的常用权限。

答案 2 :(得分:0)

检查以下代码,它将帮助您理解文件保存在内存中。

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE && resultCode == RESULT_OK) {
        // Show the thumbnail on ImageView
        previewMedia();
    } else if (requestCode == REQUEST_GALLERY_PHOTO && resultCode == RESULT_OK) {
        // Show the thumbnail on ImageView
        try {
            imageUri = data.getData();
            String[] filePathColumn = {MediaStore.Images.Media.DATA};

            Cursor cursor = getActivity().getContentResolver().query(imageUri,
                    filePathColumn, null, null, null);
            cursor.moveToFirst();

            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            mCurrentPhotoPath = cursor.getString(columnIndex);
            cursor.close();
            bitmap = BitmapFactory.decodeFile(compressImage(mCurrentPhotoPath));
            preview_imageview.setImageBitmap(bitmap);
        } catch (Exception e) {
            e.getMessage();
        }

        // ScanFile so it will be appeared on Gallery
        MediaScannerConnection.scanFile(getActivity(),
                new String[]{imageUri.getPath()}, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    public void onScanCompleted(String path, Uri uri) {
                    }
                });
    }
}