3

This related with my previous question after I changed readFile and make it read from URI for devices running in android 11 and above I got ANR error while I tried to read file

gif showing the error

this my full code

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_DOC = 1;

    private static final String TAG = "MainActivity";

    private ActivityMainBinding activityMainBinding = null;

    private File file;
    private Uri selectedFileURI;
    BufferedReader bufferedReader;
    InputStream inputStream;
    FileReader fileReader;

    @Override
    protected void onDestroy() {
        super.onDestroy();
        activityMainBinding = null;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(activityMainBinding.getRoot());


    }

    @Override
    protected void onStart() {
        super.onStart();
        activityMainBinding.textView.setMovementMethod(new ScrollingMovementMethod());
        activityMainBinding.browseButton.setOnClickListener(view -> {


            browseDocuments();
        });

        activityMainBinding.read.setOnClickListener(view -> {
            if (TextUtils.isEmpty(activityMainBinding.editTextPath.getText())) {
                activityMainBinding.editTextPath.setError("The file path cannot be empty");
            } else {
                readFile();

            }
        });

        activityMainBinding.clear.setOnClickListener(view -> activityMainBinding.textView.setText(null));
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_DOC && resultCode == Activity.RESULT_OK) {

            try {

                if (data != null) {

                    selectedFileURI = data.getData();
                    file = new File(selectedFileURI.getPath());
                    activityMainBinding.editTextPath.setText(file.getAbsolutePath());
                    Log.d(TAG, "onActivityResult: " + file.getAbsolutePath());

                } else {
                    Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
                }

                String mimeType = getContentResolver().getType(selectedFileURI);
                Log.i("Type of file", mimeType + "");
            } catch (Exception exception) {

                if (exception.getMessage() != null) {

                    Log.e("test Exception", exception.getMessage());

                } else if (exception.getCause() != null) {
                    Log.e("test Exception", Objects.requireNonNull(exception.getCause()).toString());
                }


            }
        }

    }

    public String getPath(Uri uri) {
        String[] projection = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
        if (cursor == null) return null;
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String s = cursor.getString(column_index);
        cursor.close();
        return s;
    }


    private void readFile() {
        try {

            StringBuilder sb = new StringBuilder();
            String line;

            if (SDK_INT >= Build.VERSION_CODES.R) {

                inputStream = getContentResolver().openInputStream(selectedFileURI);
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

            } else {
                fileReader = new FileReader(file);
                bufferedReader = new BufferedReader(fileReader);
            }
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line).append("\n");
            }

            activityMainBinding.textView.setText(sb.toString());

            if(inputStream != null) {
                inputStream.close();
            }else if(bufferedReader != null) {
                bufferedReader.close();
            }else if(fileReader != null) {
            fileReader.close();
            }

        } catch (IOException e) {
            Log.e("IOException", e.getMessage());
            Log.e("IOException2", e.getCause() + "");
            Log.e("IOException3", "exception", e);
            Toast.makeText(MainActivity.this, "Cannot read this file", Toast.LENGTH_LONG).show();

        }

    }


    private boolean checkPermission() {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            return Environment.isExternalStorageManager();
        } else {
            int result = ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE);
            int result1 = ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE);
            return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
        }
    }

    private void requestPermission() {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            try {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.addCategory("android.intent.category.DEFAULT");
                intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
                startActivityForResult(intent, 1);
            } catch (Exception e) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivityForResult(intent, 1);
            }
        } else {

            ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE,
                    WRITE_EXTERNAL_STORAGE}, 1);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE_DOC:
                if (grantResults.length > 0) {
                    boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                    if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
                        readFile();


                    } else {
                        Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
        }
    }

    private void browseDocuments() {

        if (!checkPermission()) {
            requestPermission();
        } else {


            String[] mimeTypes =
                    {"text/plain", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                            "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                            "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                            "textView/plain",
                            "application/pdf"};

            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            setResult(Activity.RESULT_OK);

            intent.setType("*/*");
            intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);


            startActivityForResult(Intent.createChooser(intent, "ChooseFile"), REQUEST_CODE_DOC);
        }
    }

}
Reginald D
  • 43
  • 5
  • Why are you using `SDK_INT >= Build.VERSION_CODES.R`? And to help us understand the problem, what version of Android are you testing in? – Pedro Oliveira Oct 10 '22 at 11:08
  • `file = new File(selectedFileURI.getPath())` That is nonsense as getPath() does not deliver a file system path. That wrong impossible path is visible in your picture. – blackapps Oct 10 '22 at 14:13
  • @pedro-oliveira **Why are you using SDK_INT >= Build.VERSION_CODES.R?** this because the changes that are happened in storage starting from android 11 please check this [answer](https://stackoverflow.com/a/65514216/20184459) and [this](https://stackoverflow.com/a/60917774/20184459) I testing on android 11 and above – Reginald D Oct 10 '22 at 21:43
  • @blackapps this is useful if running this app on android <= 10 , the second line will get the absolute path, anyway I didn't read files from this path this is just set in textView – Reginald D Oct 10 '22 at 21:46
  • I have the same problem even when I used rxjava – Nerd Girl Oct 12 '22 at 10:50

3 Answers3

3

The major problem is caused by blocking the main thread. You are reading from memory, which is usually taking some time. You are doing it on the main thread, which is used for all UI operations (layout, detecting inputs etc.). While you are occupying it, no rendering or input detection can happen. That is why you are seeing the ANR warning.

To solve your problem, you need to put your work to a background thread. There are several ways to do so. This is a good starting point. Also that one, if you are running tasks frequently.

Now to give you a quick solution to your problem, you can create a class to do your work:

public class ReadFile extends Worker
{
    private File file;
    private BufferedReader bufferedReader;
    private InputStream inputStream;
    private FileReader fileReader;
    public ReadFile(@NonNull Context context, @NonNull WorkerParameters workerParams)
    {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork()
    {
        try
        {
            StringBuilder sb = new StringBuilder();
            String line;

            if (SDK_INT >= Build.VERSION_CODES.R)
            {
                Uri uri = Uri.parse(getInputData().getString("URI"));
                inputStream = getApplicationContext().getContentResolver().openInputStream(uri);
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            }
            else
            {
                fileReader = new FileReader(file);
                bufferedReader = new BufferedReader(fileReader);
            }
            while ((line = bufferedReader.readLine()) != null)
            {
                sb.append(line).append("\n");
            }

            if (inputStream != null)
            {
                inputStream.close();
            }
            else if (bufferedReader != null)
            {
                bufferedReader.close();
            }
            else if (fileReader != null)
            {
                fileReader.close();
            }
            Data myData = new Data.Builder()
                    .putString("text", sb.toString())
                    .build();
            return Result.success(myData);
        }
        catch (IOException e)
        {
            Log.e("IOException", e.getMessage());
            Log.e("IOException2", e.getCause() + "");
            Log.e("IOException3", "exception", e);
            return Result.failure();
        }
    }
}

If you are using an inner class of your activity, just make sure you are not using any fields in the UI thread, that you are using inside the worker as well. If you do so, you need to synchronize them. That's why I used input and output data in the example.

Then finally in your activity call:

Data myData = new Data.Builder()
        .putString("URI", selectedFileURI.toString())
        .build();
OneTimeWorkRequest readFile = new OneTimeWorkRequest.Builder(ReadFile.class).setInputData(myData).build();
WorkManager.getInstance(getContext()).enqueue(readFile);
WorkManager.getInstance(getContext()).getWorkInfoByIdLiveData(readFile.getId()).observe(this, info ->
{
    String myResult = info.getOutputData().getString("text");
    activityMainBinding.textView.setText(myResult);
});
Aorlinn
  • 718
  • 5
  • 16
3

That's because you reading files on the Main UI thread which blocks it and causes ANR until is finished, you need to do this work on a background thread, I suggest to take look at the answers on this question even it's old "since 10 years approximately" but it's a good place to start trying, you will find some methods to do the process in background threads like AsyncTask, Callbacks and Executors all this ways can help you to do the fix the issue, but I'll focus in my answer on the newest and recommended way is using "RX Java" I suggest to take look at this RxJava Tutorial you will learn more about it.

let's start to fix this

  1. Add Rx Java dependencies to your project in build.gradle(app)
  implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    // Because RxAndroid releases are few and far between, it is recommended you also
    // explicitly depend on RxJava's latest version for bug fixes and new features.
    // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
    implementation 'io.reactivex.rxjava3:rxjava:3.0.3'
  1. in read button onClick create a new Observable and call method readFile on it, the subscribeOn it's defines the thread that the observable will work on it I choosed Schedulers.computation() because you will not be able to determine the size of the doc/text file or How long this process will take, but you can choose other threads like Schedulers.io(), in observeOn you added the thread that observer will work on it, in your case, it's the main thread, and finally call subscribe to connect observable with the observer, I also suggest to add progressBar on your layout to show it while reading the file and hide it when finished the process
activityMainBinding.read.setOnClickListener(view -> {
            if (TextUtils.isEmpty(activityMainBinding.editTextPath.getText())) {
                activityMainBinding.editTextPath.setError("The file path cannot be empty");
            } else {
                

                Observable.fromCallable(this::readFile)
                        .subscribeOn(Schedulers.computation())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Observer<Object>() {
                            @Override public void onSubscribe(Disposable d) {
                                activityMainBinding.progressBar.setVisibility(View.VISIBLE);
                            }

                            @Override public void onNext(Object o) {
                                if(o instanceof StringBuilder){
                                    activityMainBinding.textView.setText((StringBuilder) o);
                                }
                            }

                            @Override public void onError(Throwable e) {

                            }

                            @Override public void onComplete() {
                                activityMainBinding.progressBar.setVisibility(View.INVISIBLE);
                            }
                        });

            }
        });

also, I would suggest you if the file is PDF use AndroidPdfViewer library it makes a lot easier for you and you will not need all these permissions to read PDF files, you can check these this article to learn more about it.

Dr Mido
  • 2,414
  • 4
  • 32
  • 72
0
  • this is because you are using the main thread to handle a lengthy operations
  • (ANR) error stands for Application Not Responding and this is happening because the main thread handle the application UI and other important staff , but you try to do a lengthy operation by get the image so this makes the main thread unable to manage the UI and the main app.
  • that's why this message appear to make you chose between waiting for the main thread to be ready for the UI or to close the app.
  • a good solution is to make this lengthy operation (get the image) handled by another thread , so you need to create a thread that will work with the operation and when the mission completed you need to switch back to the main thread so you can apply the changes on the UI.
  • Note that you can not touch or change the UI from any thread but the Main Thread because the main thread is the responsible for the application UI , that's why you need to switch back to the main thread after the mission complete.
Ahmad Ellamey
  • 331
  • 2
  • 7