javaandroidinputstreamfilereaderandroid-anr-dialog

Android not responding while trying to read from text or doc file


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);
        }
    }

}

Solution

  • 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.