无法访问“下载”文件夹

Fails to access the `Downloads` folder

我正在尝试将一个 txt 文件保存到我的第一个 abndroid 应用程序的下载文件夹中。


public class DisplaySettingsActivity extends AppCompatActivity implements View.OnClickListener {

    private H300sVoipSettings settings;

    Button saveIntoFile;

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

        this.settings = (H300sVoipSettings) getIntent().getSerializableExtra("H300sVoipSettings");

        this.saveIntoFile = (Button)findViewById(R.id.save);
        this.saveIntoFile.setOnClickListener(this);
    }

private String saveFile(){
        Log.d("Η300s","Saving");
        String state = Environment.getExternalStorageState();
        if (!Environment.MEDIA_MOUNTED.equals(state)) {
            Log.e("H300s","Unable to detect external storage");
            return null;
        }

        DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyMMdd");
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

        file = new File( file.getAbsolutePath(),"voip_h300s_"+pattern.format(LocalDate.now())+".txt");
        Log.d("H300s",file.toString());
        try {
            Log.d("H300s","Saving");
            this.settings.save(file);
            Log.d("H300s","Saved");
            Log.d("H300s",file.getAbsolutePath());
            return file.getAbsolutePath();
        } catch (Exception e) {
            Log.e("H300s",e.toString());
            Log.e("H300s",Log.getStackTraceString(e));
            return null;
        }
    }
}

但是我函数中的这段代码:

 this.settings.save(file);

由于权限不足,保存失败。如下日志所示:

2021-04-24 14:48:18.498 8208-8208/com.example.vodafone_fu_h300s E/H300s: java.io.IOException: Permission denied
2021-04-24 14:48:18.499 8208-8208/com.example.vodafone_fu_h300s E/H300s: java.io.IOException: Permission denied
        at java.io.UnixFileSystem.createFileExclusively0(Native Method)
        at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:281)
        at java.io.File.createNewFile(File.java:1008)
        at pc_magas.vodafone_fu_h300s.screens.DisplaySettingsActivity.saveFile(DisplaySettingsActivity.java:97)
        at pc_magas.vodafone_fu_h300s.screens.DisplaySettingsActivity.onClick(DisplaySettingsActivity.java:120)
        at android.view.View.performClick(View.java:6597)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:967)
        at android.view.View.performClickInternal(View.java:6574)
        at android.view.View.access00(View.java:778)
        at android.view.View$PerformClick.run(View.java:25906)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6718)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

我想要做的是将一个 txt 文件保存到下载文件夹中,并让用户可以通过文件管理器访问它。 class H300sVoipSettings 有以下内容:

public class H300sVoipSettings implements Serializable
{
    private String username = null;

    private String password = null;

    private String primary_registar =  null;

    private String primary_registar_port = null;

    private String secondary_registar = null;

    private String secondary_registar_port = null;

    private String primary_proxy = null;

    private String primary_proxy_port = null;

    private String secondary_proxy = null;

    private String secondary_proxy_port = null;

    private String sip_domain = null;

    private String sip_number = null;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPrimary_registar() {
        return primary_registar;
    }

    public void setPrimary_registar(String primary_registar) {
        this.primary_registar = primary_registar;
    }

    public String getPrimary_registar_port() {
        return primary_registar_port;
    }

    public void setPrimary_registar_port(String primary_registar_port) {
        this.primary_registar_port = primary_registar_port;
    }

    public String getSecondary_registar() {
        if(secondary_registar == null || secondary_registar.trim().equals("")){
            return null;
        }
        return secondary_registar;
    }

    public void setSecondary_registar(String secondary_registar) {
        this.secondary_registar = secondary_registar;
    }

    public String getSecondary_registar_port() {
        return secondary_registar_port;
    }

    public void setSecondary_registar_port(String secondary_registar_port) {
        this.secondary_registar_port = secondary_registar_port;
    }

    public String getPrimary_proxy() {
        return primary_proxy;
    }

    public void setPrimary_proxy(String primary_proxy) {
        this.primary_proxy = primary_proxy;
    }

    public String getPrimary_proxy_port() {
        return primary_proxy_port;
    }

    public void setPrimary_proxy_port(String primary_proxy_port) {
        this.primary_proxy_port = primary_proxy_port;
    }

    public String getSecondary_proxy() {
        return secondary_proxy;
    }

    public void setSecondary_proxy(String secondary_proxy) {
        this.secondary_proxy = secondary_proxy;
    }

    public String getSecondary_proxy_port() {
        return secondary_proxy_port;
    }

    public void setSecondary_proxy_port(String secondary_proxy_port) {
        this.secondary_proxy_port = secondary_proxy_port;
    }

    public String getSip_domain() {
        return sip_domain;
    }

    public void setSip_domain(String sip_domain) {
        this.sip_domain = sip_domain;
    }

    public String getSip_number() {
        return sip_number;
    }

    public void setSip_number(String sip_number) {
        this.sip_number = sip_number;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public static H300sVoipSettings createFromJson(String jsonString) throws IllegalArgumentException, JSONException {
        if(jsonString == null || jsonString.trim().equals("")){
            throw new IllegalArgumentException("JsonString Should not be empty");
        }

        JSONArray settingsJson = new JSONArray(jsonString);
        H300sVoipSettings settings = new H300sVoipSettings();

        for (int i = 0; i < settingsJson.length(); i++) {
            JSONObject item = settingsJson.getJSONObject(i);
            if(item.getString("type").equals("provider")){
                settings.setPrimary_registar(item.getString("primary_registrar"));
                settings.setPrimary_registar_port(item.getString("primary_registrar_port"));
                settings.setPrimary_proxy(item.getString("primary_proxy"));
                settings.setPrimary_proxy_port(item.getString("primary_proxy_port"));
                settings.setSip_domain(item.getString("sip_domain"));
                String secondary_proxy = item.getString("secondary_proxy");
                if(secondary_proxy != null && !secondary_proxy.trim().equals("")){
                    settings.setSecondary_proxy(secondary_proxy.trim());
                }
                settings.setSecondary_proxy_port(item.getString("secondary_proxy_port"));
                settings.setSecondary_registar(item.getString("secondary_registrar"));
                settings.setSecondary_registar_port(item.getString("secondary_registrar_port"));
            } else if(item.getString("type").equals("number")){
                settings.setSip_number(item.getString("sip_number"));
                settings.setUsername(item.getString("username"));
                settings.setPassword(item.getString("password"));
            }
        }

        return settings;
    }

    public boolean equals(H300sVoipSettings other){

        boolean truth =  other.getPassword().equals(this.getPassword()) &&
               other.getUsername().equals(this.getUsername()) &&
               other.getSip_number().equals(this.getSip_number()) &&
               other.getSip_domain().equals(this.getSip_domain()) &&
               other.getPrimary_proxy().equals(this.getPrimary_proxy()) &&
               other.getPrimary_proxy_port().equals(this.getPrimary_proxy_port()) &&
               other.getPrimary_registar().equals(this.getPrimary_registar()) &&
               other.getPrimary_registar_port().equals(this.getPrimary_registar_port()) &&
               other.getSecondary_proxy_port().equals(this.getSecondary_proxy_port()) &&
               other.getSecondary_registar_port().equals(this.getSecondary_registar_port());


        truth = truth && ((other.getSecondary_proxy() == null && this.getSecondary_proxy() == null) || (other.getSecondary_proxy().equals(this.getSecondary_proxy())));
        truth = truth &&
                (
                        (other.getSecondary_registar() == null && this.getSecondary_registar() == null) ||
                                (
                                        other.getSecondary_registar().equals(this.getSecondary_registar())
                                )
                );
        return truth;
    }

    public String toString()
    {
        StringBuilder txt = new StringBuilder();

            txt.append("Phone Number: ");
            txt.append(this.getSip_number());
            txt.append("\n");

            txt.append("Username: ");
            txt.append(this.getUsername());
            txt.append("\n");

            txt.append("Password: ");
            txt.append(this.getPassword());
            txt.append("\n");

            txt.append("Sip Domain: ");
            txt.append(this.getSip_domain());
            txt.append("\n");

            txt.append("Primary proxy: ");
            txt.append(this.getPrimary_proxy());
            txt.append(" Port: ");
            txt.append(this.getPrimary_proxy_port());
            txt.append("\n");

            txt.append("Secondary proxy: ");
            String secondary_proxy = this.getSecondary_proxy();
            secondary_proxy = (secondary_proxy == null || !secondary_proxy.trim().equals(""))?"N/A":secondary_proxy;
            txt.append(secondary_proxy);
            txt.append(" Port: ");
            String secondaryProxyPort = this.getSecondary_proxy_port();
            secondaryProxyPort=(secondaryProxyPort == null || !secondaryProxyPort.trim().equals(""))?"N/A":secondaryProxyPort;
            txt.append(secondaryProxyPort);
            txt.append("\n");

            txt.append("Primary Registar: ");
            String primaryRegistar = this.getPrimary_registar();
            txt.append(primaryRegistar);
            txt.append(" Port: ");
            String primaryRegistarPort = this.getPrimary_registar_port();
            txt.append(primaryRegistarPort);
            txt.append("\n");

            txt.append("Secondary Registar: ");
            String secondary_registar = this.getSecondary_registar();
            secondary_registar = (secondary_registar == null || !secondary_registar.trim().equals(""))?"N/A":secondary_registar;
            txt.append(secondary_registar);
            txt.append(" Port: ");
            String secondaryRegistarPort = this.getSecondary_registar();
            secondaryRegistarPort=(secondaryRegistarPort == null || !secondaryRegistarPort.trim().equals(""))?"N/A":secondaryRegistarPort;
            txt.append(secondaryRegistarPort);
            txt.append("\n");

        return txt.toString();
    }

    public void save(File file) throws IOException {
        PrintWriter out = new PrintWriter(new FileWriter(file));
        out.println("********");
        out.print("Exported Date: ");
        out.println(new Date().toString());
        out.println("********");
        out.print(this.toString());
        out.close();
    }
}

并用于数据序列化。该应用程序在 Android 清单中具有以下内容:

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

此外,我查看了这些答案,但没有任何结果:

那么你知道我如何访问 donwloads 文件夹吗?

尽管拥有权限:

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

您还应该提示用户也接受这些请求。为了实现这一点,onCLick 函数应该是:

public void onClick(View v) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            Log.d("H300s","Permission Accepted");
            saveFile();
        } else {
            requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE );
        }
    }

其中 requestPermissionLauncher 被初始化为 onCreate 是这样的:

        requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
            Log.d("H300s","Permissions Callback");

            if (isGranted) {
                Log.d("H300s","Permission Accepted 2");
                saveFile();
            } else {
                permissionSaveDenied();
            }
        });

此外,请确保在 build.gradle 处放置以下内容:

    implementation 'androidx.activity:activity-ktx:1.2.0'
    implementation 'androidx.fragment:fragment:1.3.0'

为了 ActivityResultContracts 正常工作。

奖金提示

您可以跳过声明需要此权限:

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

因为应用程序仍然会要求用户提供它。