You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 11+读取文本/Doc文件时出现ANR无响应问题求助

Android 11+ 文件读取ANR问题解决

问题背景

为适配Android 11及以上设备,修改readFile方法为通过URI读取文件,但触发ANR(应用无响应)错误。

错误演示:
ANR错误演示

原始完整代码

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

}

问题分析

  1. ANR核心原因:文件读取操作在主线程执行,当文件体积较大时,主线程被阻塞超过系统阈值(通常5秒),触发ANR。
  2. URI处理错误:Android 11+通过ACTION_GET_CONTENT获取的URI并非真实文件路径,直接转File对象无效,旧版本逻辑也存在兼容性问题。
  3. 资源泄漏风险:流的关闭逻辑用else if分支,会导致部分流未被关闭;未使用自动资源管理机制。
  4. 权限冗余:ACTION_GET_CONTENT通过系统文件选择器授权,Android 11+不需要MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,旧版本仅需READ_EXTERNAL_STORAGE权限。

修复方案

1. 子线程执行文件读取(解决ANR)

将文件读取操作移至子线程,主线程仅负责更新UI。以下是基于AsyncTask的实现:

private void readFile() {
    if (selectedFileURI == null) {
        Toast.makeText(this, "请先选择文件", Toast.LENGTH_SHORT).show();
        return;
    }

    new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... voids) {
            StringBuilder sb = new StringBuilder();
            // 使用try-with-resources自动管理流,避免资源泄漏
            try (InputStream inputStream = getContentResolver().openInputStream(selectedFileURI);
                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                return sb.toString();
            } catch (IOException e) {
                Log.e("FileReadError", "读取失败", e);
                return null;
            }
        }

        @Override
        protected void onPostExecute(String content) {
            super.onPostExecute(content);
            if (content != null) {
                activityMainBinding.textView.setText(content);
            } else {
                Toast.makeText(MainActivity.this, "无法读取文件", Toast.LENGTH_LONG).show();
            }
        }
    }.execute();
}

2. 修正URI与权限逻辑

权限检查与请求优化

// 新增权限请求码
private static final int REQUEST_CODE_PERMISSION = 2;

private boolean checkPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android 11+ 使用ACTION_GET_CONTENT无需特殊权限
        return true;
    } else {
        // 旧版本仅需读取权限
        int result = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
        return result == PackageManager.PERMISSION_GRANTED;
    }
}

private void requestPermission() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
        ActivityCompat.requestPermissions(this, 
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 
            REQUEST_CODE_PERMISSION);
    }
}

// 修正权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            browseDocuments();
        } else {
            Toast.makeText(this, "请允许存储访问权限!", Toast.LENGTH_SHORT).show();
        }
    }
}

URI处理优化(显示文件名而非无效路径)

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_DOC && resultCode == Activity.RESULT_OK) {
        if (data != null) {
            selectedFileURI = data.getData();
            // 获取并显示文件名
            String fileName = getFileName(selectedFileURI);
            activityMainBinding.editTextPath.setText(fileName);
            Log.d(TAG, "选中文件URI: " + selectedFileURI.toString());
        } else {
            Toast.makeText(this, "请允许存储访问权限!", Toast.LENGTH_SHORT).show();
        }
    }
}

// 工具方法:从URI获取文件名
private String getFileName(Uri uri) {
    String result = null;
    if (uri.getScheme().equals("content")) {
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        try {
            if (cursor != null && cursor.moveToFirst()) {
                result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
    if (result == null) {
        result = uri.getPath();
        int cut = result.lastIndexOf('/');
        if (cut != -1) {
            result = result.substring(cut + 1);
        }
    }
    return result;
}

内容的提问来源于stack exchange,提问作者Reginald D

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:20:33