android - SD 卡写入权限被拒绝 (EACCES)

android - sd card writing permission denied (EACCES)

我正在尝试写入 SD 卡,而不是写入安装在设备本身上的 "external" 存储(调用 getExternalStorageDirectory 时返回的存储)。我可以读取 SD 卡中的每个文件,但不能在其 PUBLIC 目录中更改或写入任何内容,尝试时检索此错误:"java.io.IOException: open failed: EACCES (Permission denied)".

我在这里看到了很多类似问题的答案,但大多数只谈论 "external storage"(主要),这不是 SD 卡(现在大多数设备上)。我知道我可以写入 SD 卡中我的应用程序的私有目录,但这不是我要找的,我想更改 public 个文件。最后,有人告诉我我不能在 Android 6.0 Marshmallow 中写入 sd 卡的 public 目录,但我不相信 Android 会以这种方式损害开发人员。

注意:我是在运行时请求权限,但是在运行时授权后,错误仍然存​​在,直接在应用程序管理器中授权,设备与USB连接或断开连接。

我使用的代码如下:

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import static android.widget.Toast.LENGTH_LONG;

public class MainActivity extends AppCompatActivity {

    private static String TAG = "SdCardTest";
    private static final int REQUEST_WRITE_STORAGE = 101;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int permission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permission != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "Permission to record denied");

            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage("Permission is required to access SD card.").setTitle("Permission required");

                builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int id) {
                        Log.i(TAG, "Clicked");
                        makeRequest();
                    }
                });

                AlertDialog dialog = builder.create();
                dialog.show();

            } else {
                makeRequest();
            }
        }
    }

    protected void makeRequest() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_WRITE_STORAGE: {

                if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {

                    Log.i(TAG, "Permission has been denied by user");
                    Toast.makeText(this, "Permission has been denied by user", LENGTH_LONG).show();

                } else {

                    Log.i(TAG, "Permission has been granted by user");
                    Toast.makeText(this, "Permission has been granted by user", LENGTH_LONG).show();

                    //Here I WRITE to SD card

                    } catch (IOException e) {
                        e.printStackTrace();
                        Toast.makeText(this, "Not working", LENGTH_LONG).show();
                    }

                }
                return;
            }
        }
    }
}

我的清单文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.patrick.testasdcompermissaoemexecucao">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

感谢@VladMatvienko,我能够编写允许我读取、写入和编辑 SD 卡上的任何目录或子目录的代码。

这是我的完整代码,您可以将其复制并粘贴到 MainActivity 中以测试存储访问框架:

import android.app.Activity;
import android.content.Intent;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.v4.provider.DocumentFile;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    int SAVE_REQUEST_CODE = 102;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }


    public void saveFile(View view) {
        List<UriPermission> permissions = getContentResolver().getPersistedUriPermissions();
        if (permissions != null && permissions.size() > 0) {

            //pickedDir is the directory the user selected (I chose SD's root).
            DocumentFile pickedDir = DocumentFile.fromTreeUri(this, permissions.get(0).getUri());



//=====You can use these lines to write into an existent folder or directory========================
            DocumentFile aux = pickedDir.findFile("aaabrao");//aaabrao is the name of a existing folder in my SD.

    //Use DocumentFile file = pickedDir.createDirectory("aaabrao"); to create a new folder in the pickedDir directory.

            if(aux != null) {
                //Creating the object "file" is essencial for you to write "some text" INSIDE the TXT file. Otherwise, your TXT will be a blank.
                DocumentFile file = aux.createFile("text/plain", "try5.txt");
                writeFileContent(file.getUri());
            }
//==================================================================================================



//=====You can use these lines to write to the primary chosen directory (SD's root, in my case)====
            DocumentFile file2 = pickedDir.createFile("text/plain", "try5.txt");
            writeFileContent(file2.getUri());
//==================================================================================================

        } else {

            startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), SAVE_REQUEST_CODE);
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {

        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == SAVE_REQUEST_CODE) {
                if (resultData != null) {
                    Uri treeUri=resultData.getData();

                    //This line gets your persistent permission to write SD (or anywhere else) without prompting a request everytime.
                    getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                    //These lines will write to the choosen directory (SD's root was my chosen one). You could also write to an existent folder inside SD's root like me in saveFile().
                    DocumentFile pickedDir = DocumentFile.fromTreeUri(getBaseContext(), treeUri);
                    DocumentFile file = pickedDir.createFile("text/plain", "try5.txt");
                    writeFileContent(file.getUri());

                }
            }
        }
    }


    private void writeFileContent(Uri uri) {
        try {
            ParcelFileDescriptor pfd = this.getContentResolver().openFileDescriptor(uri, "w");

            FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());

            String textContent = "some text";

            fileOutputStream.write(textContent.getBytes());

            fileOutputStream.close();
            pfd.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我做这个 activity_main 是为了使用按钮调用 saveFile():

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.patrick.testasdcompermissaoemexecucao.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button2"
        android:text="Write"
        android:onClick="saveFile"
        android:layout_below="@+id/imageButton"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        tools:ignore="UnknownId"
        android:layout_gravity="center" />
</RelativeLayout>

你必须改变

tools:context="com.example.patrick.testasdcompermissaoemexecucao.MainActivity">

用于您的应用目录。如果您不知道如何操作,请创建一个空白项目并复制相应的行,然后再将我的代码粘贴到那里。

清单文件中无需更改。

祝你好运!