Android 11+读取文本/Doc文件时出现ANR无响应问题求助
Android 11+ 文件读取ANR问题解决
问题背景
为适配Android 11及以上设备,修改readFile方法为通过URI读取文件,但触发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); } } }
问题分析
- ANR核心原因:文件读取操作在主线程执行,当文件体积较大时,主线程被阻塞超过系统阈值(通常5秒),触发ANR。
- URI处理错误:Android 11+通过
ACTION_GET_CONTENT获取的URI并非真实文件路径,直接转File对象无效,旧版本逻辑也存在兼容性问题。 - 资源泄漏风险:流的关闭逻辑用
else if分支,会导致部分流未被关闭;未使用自动资源管理机制。 - 权限冗余:
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
相关产品推荐
相关产品推荐

