Android外部存储权限异常求助:重装后无法写入文件
Android 外部存储权限问题:卸载重装后文件保存失败
已在Manifest中添加存储权限,首次安装时应用可正常请求权限并保存Assets文件夹中的文件,但卸载重装(代码无改动)后,文件保存功能失效,调试时触发权限拒绝错误。需要实现运行时权限申请解决该问题。
现有Manifest配置
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.hcghelp"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.MANAGE_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/Theme.HCGHelp"> <activity android:name=".NP_all"></activity> <activity android:name=".GKL18_pre" /> <activity android:name=".GKL17_in" /> <activity android:name=".GKL17_pre" /> <activity android:name=".GKL16_in" /> <activity android:name=".GKL14_in" /> <activity android:name=".GKL10_in" /> <activity android:name=".GKL16_pre" /> <activity android:name=".GKL14_pre" /> <activity android:name=".GKL10_pre" /> <activity android:name=".GKL07_in" /> <activity android:name=".GKL07_pre" /> <activity android:name=".GKL02_in" /> <activity android:name=".GKL02_pre" /> <activity android:name=".GKL01_pre" /> <activity android:name=".GKL01_in" /> <activity android:name=".GKL_all" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
现有Activity代码
public class GKL10_pre extends AppCompatActivity { private Button button1; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gkl10_pre); button1 = (Button) findViewById(R.id.B_GKL10_KEIMENO); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { opengkl10_pk(); } }); button = (Button) findViewById(R.id.B_GKL10_FILES); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { copyAsset("file1.pdf"); copyAsset("file2.pdf"); copyAsset("file3.pdf"); copyAsset("file4.pdf"); } }); } private void copyAsset(String filename){ String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download"; File dir = new File(dirPath); if(!dir.exists()) { dir.mkdirs(); } AssetManager assetManager = getAssets(); InputStream in = null; OutputStream out = null; try { in = assetManager.open(filename); File outFile = new File(dirPath, filename); out = new FileOutputStream(outFile); copyFile(in, out); Toast.makeText(this, "Saved!", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "Failed!", Toast.LENGTH_SHORT).show(); } finally { if(in != null){ try { in.close(); } catch (IOException e){ e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (IOException e){ e.printStackTrace(); } } } } private void copyFile(InputStream in, OutputStream out) throws IOException{ byte[] buffer = new byte[1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } } }
错误日志
W/System.err: at libcore.io.IoBridge.open(IoBridge.java:492) W/System.err: at java.io.FileOutputStream.<init>(FileOutputStream.java:236) W/System.err: at java.io.FileOutputStream.<init>(FileOutputStream.java:186) W/System.err: at com.example.hcghelp.GKL10_pre.copyAsset(GKL10_pre.java:84) W/System.err: at com.example.hcghelp.GKL10_pre.access$000(GKL10_pre.java:28) W/System.err: at com.example.hcghelp.GKL10_pre$2.onClick(GKL10_pre.java:56) W/System.err: at android.view.View.performClick(View.java:7448) W/System.err: at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) W/System.err: at android.view.View.performClickInternal(View.java:7425) W/System.err: at android.view.View.access$3600(View.java:810) W/System.err: at android.view.View$PerformClick.run(View.java:28305) W/System.err: at android.os.Handler.handleCallback(Handler.java:938) W/System.err: at android.os.Handler.dispatchMessage(Handler.java:99) W/System.err: at android.os.Looper.loop(Looper.java:223) W/System.err: at android.app.ActivityThread.main(ActivityThread.java:7656) W/System.err: at java.lang.reflect.Method.invoke(Native Method) W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied) W/System.err: at libcore.io.Linux.open(Native Method) W/System.err: at libcore.io.ForwardingOs.open(ForwardingOs.java:166) W/System.err: at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254) W/System.err: at libcore.io.ForwardingOs.open(ForwardingOs.java:166) W/System.err: at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7542) W/System.err: at libcore.io.IoBridge.open(IoBridge.java:478) W/System.err: ... 17 more D/CompatibilityChangeReporter: Compat change id reported: 147798919; UID 10157; state: ENABLED
问题原因
卸载重装后,系统会保留应用之前的权限状态记忆,而代码未实现运行时权限申请逻辑,直接操作外部存储时触发权限拒绝。同时不同Android版本的存储权限机制存在差异,需要针对性适配。
解决方案
1. 适配Manifest配置
- 针对Android 10(API 29),在
application标签添加兼容旧存储模式的属性:android:requestLegacyExternalStorage="true" MANAGE_EXTERNAL_STORAGE仅适用于Android 11+且需要全存储访问的场景,若只是保存到下载目录,可暂时保留但无需主动申请(除非有特殊需求)。
2. 实现运行时权限申请逻辑
修改GKL10_preActivity代码,添加权限检查和申请流程:
步骤1:定义常量
在Activity类中添加:
private static final int REQUEST_STORAGE_PERMISSION = 100; private static final String[] STORAGE_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };
步骤2:修改按钮点击事件
替换原按钮点击逻辑,先检查权限再执行复制:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 检查写入权限是否已授予 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { copyAssets(); } else { // 申请权限 requestPermissions(STORAGE_PERMISSIONS, REQUEST_STORAGE_PERMISSION); } } }); // 封装复制方法 private void copyAssets() { copyAsset("file1.pdf"); copyAsset("file2.pdf"); copyAsset("file3.pdf"); copyAsset("file4.pdf"); }
步骤3:处理权限申请结果
重写onRequestPermissionsResult方法:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_STORAGE_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限通过,执行文件复制 copyAssets(); } else { // 权限被拒绝,提示用户 Toast.makeText(this, "需要存储权限才能保存文件", Toast.LENGTH_LONG).show(); } } }
3. 优化存储路径(可选)
针对Android 10+,推荐使用系统提供的公开下载目录路径,替代手动拼接:
// 替换copyAsset方法中的dirPath定义 String dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
如果不需要文件在应用卸载后保留,可使用应用专属外部存储(无需权限):
String dirPath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
4. 测试验证
卸载重装应用后,点击保存按钮会弹出权限申请弹窗,用户同意后即可正常保存Assets中的文件。
内容的提问来源于stack exchange,提问作者W8ForMe247
相关产品推荐
相关产品推荐

