Проблема записи файла на некоторых устройствах через openOutputStream(mUri)

85
21 февраля 2022, 00:50

Я пишу написал приложение, которое открывает текстовый файл через Uri: (getContentResolver().openInputStream(mUri))

дальше после изменения текста в EditText по нажатию кнопки я пытаюсь сохранить содержимое файла по полученному Uri: (getContentResolver().openOutputStream(mUri);)

проблема в том, что на эмуляторах весь код отрабатывает, а вот когда отлаживаю на реальном устройстве (Asus ZE553KL Android 8.0.0), то при сохранении выдает ошибку: "java.io.IOException: write failed: EBADF (Bad file descriptor)"

Помогите пожалуйста правильно реализовать код. Спасибо! Ниже весь код MainActivity.

package com.aginf.notetohelp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class MainActivity extends AppCompatActivity {
    private EditText mEditText;
    private Uri mUri;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditText = (EditText) findViewById(R.id.editText);
        //////////////////////////////////////
        // Получаю Uri только при SCHEME_CONTENT
        // и сразу открываю файл и помещаю его в EditText
        Intent intent = getIntent();
        String action = intent.getAction();
        if (action.compareTo(Intent.ACTION_VIEW) == 0) {
            String scheme = intent.getScheme();
            if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) {
                mUri = intent.getData();
                OpenFromUri();
            }
        }
    }
    ///////////////////////////////////
    // Процедура получения содержимого файла по Uri
    private void OpenFromUri() {
        StringBuilder stringBuilder = new StringBuilder();
        Boolean res;
        try{
            InputStream inputStream = getContentResolver().openInputStream(mUri);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line + "\n");
            }
            mEditText.setText(stringBuilder.toString());
        }catch(Throwable t){
            Toast.makeText(getApplicationContext(),"ERROR (open from Uri): "+t.toString(), Toast.LENGTH_LONG).show();
        }
    }
    ///////////////////////////////////
    // ПСохранение содержимого в файл по Uri
    public void onSaveClick(View view) {
        try{
            OutputStream outputStream = getContentResolver().openOutputStream(mUri);
            OutputStreamWriter osw = new OutputStreamWriter(outputStream);
            osw.write(mEditText.getText().toString());
            osw.close();
            outputStream.close();
            Toast.makeText(MainActivity.this, "Saved", Toast.LENGTH_SHORT).show();
        }catch(Throwable t){
            Toast.makeText(getApplicationContext(),"ERROR (save from Uri):"+t.toString(), Toast.LENGTH_LONG).show();
        }
    }
}

Манифест:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.aginf.notetohelp">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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>
            <intent-filter android:icon="@mipmap/ic_launcher_round" android:label="Note to help" android:priority="0">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="content" />
                <data android:mimeType="text/plain" />
                <data android:pathPattern=".*\\.txt" />
                <data android:pathPattern=".*\\..*\\.txt" />
                <data android:pathPattern=".*\\..*\\..*\\.txt" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\.txt"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
Answer 1

вы забыли закрыть stream, в методе OpenFromUri()

    private void OpenFromUri() {
        StringBuilder stringBuilder = new StringBuilder();
        Boolean res;
        try{
            InputStream inputStream = getContentResolver().openInputStream(mUri);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line + "\n");
            }
            mEditText.setText(stringBuilder.toString());
    inputStream .close(); // ДОБАВИЛ строку тут
}catch(Throwable t)
{ 
Toast.makeText(getApplicationContext(),"ERROR (open from Uri): "+t.toString(), Toast.LENGTH_LONG).show(); } }
Answer 2

По итогам дискуссии в комментариях вижу следующий диагноз. Сначала заглянем под капот:

Ваш use-case подразумевает, что юзер через стоковый файл-менеджер (какой бы он ни был) выбирает текстовый файл и "открывает" его. При этом в зависимости от локальной ситуации конкретных настроек девайса юзер либо выбирает с помощью чего он будет открывать текстовый файл, либо открывает приложением по умолчанию - в любом случае ваш use case подразумевает, что юзер откроет его с помощью вашего приложения.

На стороне файл-менеджера происходит при этом следующее:

1) На сцену выходит ContentProvider, в задачу которого входит формирование Uri для выдачи его внешнему Intent'у - в данном случае вашему приложению. Поскольку речь идет об Android 8 то прямые ссылки типа file:// запрещены, поэтому Uri должен быть вида content://

2) В зависимости от файлового менеджера провайдер может иметь вид FileProvider или DocumentsProvider и т.д.

3) В любом случае в провайдере должны быть вариации с наличием/отсутствием разрешения на редактирование, типа:

<provider android:authorities="list"
      android:grantUriPermissions=["true" | "false"]
      android:permission="string"
      android:readPermission="string"
      android:writePermission="string" >
</provider>

4) В вашем случае, очевидно, что провайдер не предоставляет разрешения на запись.

Это либо баг конкретного провайдера файлового менеджера либо фича :) То есть надо писать в поддержку/форум поставщика девайса

Другой вариант, при "вылете" Exception - интеллигентно сообщить юзеру - так мол и так, файл увы не поддается редактированию.

Есть еще вариант поиграться с разными видами ACTION_OPEN_DOCUMENT, ACTION_EDIT и т.д. - надежды конечно мало.

Как то так.

Update

Вам нужен доступ к файлам через Storage Access Framework, вам надо получать доступ к файлам именно через него, а он активируется применением ACTION_OPEN_DOCUMENT, а не ACTION_VIEW, только это гарантирует доступ к файлу в режиме редактирования.

READ ALSO
Можно ли на одном AnchorPane менять местами Labl(ы)? [закрыт]

Можно ли на одном AnchorPane менять местами Labl(ы)? [закрыт]

Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском

182
Hibernate, @OneToMany и выборка из базы данных

Hibernate, @OneToMany и выборка из базы данных

Имеются два класса, связанные между собой:

171
Несколько public классов в одном .class-файле

Несколько public классов в одном .class-файле

По какой причине нельзя создать несколько public-классов в одномjava-файле? Почему можно создавать без модификатора доступа?

93
Оптимизация кода (Android Studio, Java, Codestyle)

Оптимизация кода (Android Studio, Java, Codestyle)

Вот такой код у меня получился для воспроизведения радио через Exoplayer:

90