Android13 授予特殊应用权限代码

Android13 授予特殊应用权限代码

一、前言

Android 权限类别有啥,网上很多是说有普通权限和危险权限,其实是不准确的。

Android 权限分为普通权限,动态权限,特殊权限,私有权限。

几种权限的简单说明:

权限的使用都要在AndroidManifest.xml中声明就行,即使是系统应用也是要声明后才能获取到。

1、普通权限: 不用申请直接能获取到;
    比如:INTERNET 网络权限
2、动态权限:普通应用需要动态申请,系统应用直接能获取到;
    比如:WRITE_EXTERNAL_STORAGE 读写权限
3、私有权限:也叫特殊权限,声明包名和权限(系统应用才能使用),如果不声明系统无法正确启动
    在 frameworks\base\data\etc\privapp-permissions-platform.xml 
    比如:RECEIVE_WIFI_CREDENTIAL_CHANGE wifi凭证更改监听 和CHANGE_OVERLAY_PACKAGES overlay配置权限
4、特殊应用权限:普通应用需要向系统申请(和动态申请不一样),系统应用直接能获取到;
    比如:SYSTEM_ALERT_WINDOW 悬浮框权限 和 FINE_LOCATION 定位权限

私有权限和特殊应用权限比较容易弄混,
私有权限影响比较大会导致系统一直重启,特殊应用权限最多导致应用崩溃。

原生设置Settings上只有动态权限和特殊应用权限的管理显示。

二、授予特殊权限代码

Android中最常用的特殊应用权限就是悬浮框权限;

1、系统签名应用代码中设置某个应用获取悬浮框权限


import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;


    private void initOppPermission(Context context) {

        try {
            setPackageAppOpsPermission(context, "com.android.bluetooth", AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //设置特殊权限通过
    private void setPackageAppOpsPermission(Context context, String packageName, String opsString) {
        boolean isNeedSetPermission = SystemProperties.getBoolean("persist.sys.debug.need_ops_permission", true);
        DebugLog.debug("isNeedSetPermission = " + isNeedSetPermission);
        if (!isNeedSetPermission) {
            return;
        }
        AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        PackageManager manager = context.getPackageManager();
        int uid = 1;
        try {
            ApplicationInfo packageInfo = manager.getApplicationInfo(packageName, 0);
            uid = packageInfo.uid;
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        DebugLog.debug("uid = " + uid);
        mAppOps.setUidMode(opsString, uid, AppOpsManager.MODE_ALLOWED);
    }

上面是设置蓝牙应用获取悬浮框权限;
Android13 之后,蓝牙签名应用不再是系统应用,
标识是android.uid.bluetooth,不拥有系统签名权限。
开发的方案中系统是去除通知栏的,想要自己接收到蓝牙文件,就要自己弹框接收处理文件。
所以需要悬浮框权限必须手动申请通过系统应用授予权限或者系统代码给它权限。

这个给与权限的代码是从Android13的Settings中研究后抽取出来的,
Settings中权限管理的代码是比较复杂的,特别是特殊应用权限的管理更加复杂;
Android14 的Settings权限管理相关的很多代码都是用kt文件写的,现在估计都看不太明白了。

2、普通应用申请悬浮框权限

Android11 之后不一定行了。

import android.content.Context;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
 
public class PermissionHelper {
 
    // 悬浮窗权限的请求码
    private static final int REQUEST_FLOAT_WINDOW_PERMISSION = 1;
 
    // 检查悬浮窗权限是否被授予
    public static boolean checkFloatWindowPermission(Context context) {
        return ActivityCompat.checkSelfPermission(context,
                android.Manifest.permission.SYSTEM_ALERT_WINDOW) == PackageManager.PERMISSION_GRANTED;
    }
 
    // 请求悬浮窗权限
    public static void requestFloatWindowPermission(Activity activity) {
        if (!checkFloatWindowPermission(activity)) {
            // 如果系统小于6.0,直接返回false
            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
                // 此处处理低于6.0版本的系统
                return;
            }
            // 请求权限
            ActivityCompat.requestPermissions(activity,
                    new String[]{android.Manifest.permission.SYSTEM_ALERT_WINDOW},
                    REQUEST_FLOAT_WINDOW_PERMISSION);
        }
    }
}

你可以在需要的地方调用requestFloatWindowPermission(Activity activity)方法,例如在Activity的onCreate方法中:


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    PermissionHelper.requestFloatWindowPermission(this);
}

在Activity中重写onRequestPermissionsResult方法来处理权限被授予的情况:


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == PermissionHelper.REQUEST_FLOAT_WINDOW_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限被授予,可以创建悬浮窗
            Toast.makeText(this,"权限被授予",Toast.LENGTH_LONG).show();
        } else {
            // 权限被拒绝,不能创建悬浮窗
             Toast.makeText(this,"权限被拒绝",Toast.LENGTH_LONG).show();
        }
    }
}

上面代码看起来就是动态权限身体代码的处理!好像没啥用!

上面这些代码都是搜索得到的,测试了一下Android11 和Android13 aml方案的代码,都是权限授予失败的,直接提示 “权限被拒绝”.

这里的授予权限其实就是通过系统监听到权限申请后给与的权限。

估计是系统或者原生Settings未处理这个请求导致。

再搜索发现还有其他方式:


    // 悬浮窗权限的请求码
    private static final int REQUEST_FLOAT_WINDOW_PERMISSION = 1;

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

          setContentView(R.layout.activity_main);
        requestFloatWindowPermission(this);
        if (!Settings.canDrawOverlays(this)) {
            Toast.makeText(this, "向Settings申请权限",Toast.LENGTH_LONG).show();
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_FLOAT_WINDOW_PERMISSION);
        } else {
            Toast.makeText(this, "以获取到权限",Toast.LENGTH_LONG).show();

        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_FLOAT_WINDOW_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 权限被授予,可以创建悬浮窗
                Toast.makeText(this,"权限被授予",Toast.LENGTH_LONG).show();
            } else {
                // 权限被拒绝,不能创建悬浮窗
                Toast.makeText(this,"权限被拒绝",Toast.LENGTH_LONG).show();
            }
        }
    }

上面的代码在界面启动后是会跳转到悬浮框权限管理界面。
但是我发现Android Studio的应用声明了悬浮框权限,在Settings设置中还是没有看到可以授予悬浮框权限的选项。

    <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
        tools:ignore="ReservedSystemPermission" />

dumpsys命令查看应用的权限信息:

console:/ # dumpsys package com.demo.listdemo| grep -i permission
    installPermissionsFixed=true
    requested permissions:
      android.permission.POST_NOTIFICATIONS
      runtime permissions:
        android.permission.POST_NOTIFICATIONS: granted=true, flags=[ USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
      com.android.permissioncontroller:
console:/ # 
console:/ #dumpsys package com.demo.listdemo| grep -i SYSTEM_ALERT_WINDOW
console:/ #

从过滤的信息查看是没有悬浮框权限声明的!

其他系统签名应用,申请后,是查看到正常权限授予的。
比如:

console:/ # dumpsys package com.skg.filemanager | grep SYSTEM_ALERT_WINDOW
      android.permission.SYSTEM_ALERT_WINDOW
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
console:/ # 

第一行是应用申请的权限,后面是显示是否授予的情况。

难道要系统应用或系统签名应用才能申请到悬浮框权限了??
后面有验证测试情况说明。

三、其他

1、Android13 授予特殊权限代码小结:

系统签名android.uid.system应用,声明了特殊权限就可以获取到。
其他应用,就要手动授予;或者系统流程中授予,或者系统类用代码授予。

系统代码或者系统应用赋予某个apk 特殊权限的代码:

// opsString 是特殊权限在AppOpsManager 里面定义的对应的字符串
// uid 应用的uid值
AppOpsManager.setUidMode(opsString, uid, AppOpsManager.MODE_ALLOWED);


//在系统源码中,有的特殊权限是用 setMode 方法设置的
mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid,
                                        packageName, AppOpsManager.MODE_ALLOWED);

有的方案遇到使用 setMode 未生效,使用 setUidMode 才生效的情况。
所以这两个方法的第一个参数是不同的,setMode第一个参数是一个int值;
setMode 方法需要设置packageName。

2、有些应用授予了权限但是dumpsys还是显示授予情况为 false

Android14 的蓝牙应用在原生Settings上显示已获取到悬浮框权限,但是dumpsy未查询到

console:/ # dumpsys package com.android.bluetooth | grep -i SYSTEM_ALERT_WINDO 
      android.permission.SYSTEM_ALERT_WINDOW
      android.permission.SYSTEM_ALERT_WINDOW: granted=false
      android.permission.SYSTEM_ALERT_WINDOW: granted=false
console:/ # 
console:/ # dumpsys package com.android.bluetooth | grep -i appid
    appId=1002
    appId=1002
console:/ #

蓝牙应用已经被我在系统代码中授予了悬浮框权限的!

真奇怪,Android13 上是显示授予 true的,Android14中却显示为false。
蓝牙应用虽然是系统应用,但是不是系统签名应用,是蓝牙签名应用。
所以蓝牙应用权限并不是很大,但是应该比普通系统应用权限大,
但是没有系统签名应用的权限大,蓝牙签名权限具体有什么什么差别还未研究过。

Android 蓝牙应用签名区别:

//Android 的蓝牙应用是蓝牙签名
android:sharedUserId="android.uid.bluetooth"

//Android 系统签名应用
android:sharedUserId="android.uid.system"

如果强制把蓝牙应用的蓝牙签名改成系统签名,
有些需要EDLA(谷歌需求)认证的项目就会有问题。

源码和生成apk 的目前区别:

//Android11 的蓝牙应用 :
    源码目录在: packages/apps/Bluetooth
    生成的apk在:system/app/Bluetooth
//Android14 的蓝牙应用 
    源码目录在: packages/modules/Bluetooth
    生成的apk在:/apex/com.android.btservices/app/Bluetooth@UP1A.231105.001.B2/Bluetooth.apk

3、普通、系统应用和系统签名应用声明悬浮框权限的区别

(1)Android14 上:

普通应用上面提到了,声明了也没法给到悬浮框权限。

adb查看系统应用的悬浮框权限情况:

130|console:/ # //(1)查看当前应用的权限情况
130|console:/ # dumpsys package com.demo.listdemo | grep -i permission
    installPermissionsFixed=false
    declared permissions:
      android.permission.SYSTEM_ALERT_WINDOW: prot=normal
    requested permissions:
      android.permission.POST_NOTIFICATIONS
      runtime permissions:
        android.permission.POST_NOTIFICATIONS: granted=true, flags=[ SYSTEM_FIXED|GRANTED_BY_DEFAULT|USER_SENSITIVE_WHEN_GRANTED|USER_SENSITIVE_WHEN_DENIED]
      com.android.permissioncontroller:
console:/ # 
console:/ # //(2)apk路径
console:/ # dumpsys package com.demo.listdemo | grep -i path 
    codePath=/system/priv-app/Demo
    resourcePath=/system/priv-app/Demo
    path: /system/priv-app/Demo/Demo.apk
console:/ # //(3)应用的appid,可以查看到是否是系统签名,1000是系统签名
console:/ # dumpsys package com.demo.listdemo | grep -i id
      android.intent.action.MAIN:
          Action: "android.intent.action.MAIN"
          Category: "android.intent.category.LAUNCHER"
    appId=10042
console:/ # 
console:/ # 

已经把apk放到 系统目录:priv-app/Demo,成为了系统应用;
但是发现只有声明那里有悬浮框权限,授予情况是没有显示的;
代码中弹框也是无法授予悬浮框权限的。

给这个应用加上系统签名(android.uid.system)试试。

系统签名应用的权限情况:
和上面同一个应用,只是添加了system签名权限。


//(1)查看该应用的appId,1000 表示系统签名应用
console:/ # dumpsys package com.demo.listdemo | grep -i appid
    appId=1000
    appId=1000
console:/ #
console:/ #// (2)查看是否获取到悬浮框权限
console:/ #dumpsys package com.demo.listdemo | grep -i SYSTEM_ALERT_WINDOW
      android.permission.SYSTEM_ALERT_WINDOW: prot=normal
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
console:/ #
console:/ # //(3)查看该应用的所有权限
console:/ # dumpsys package com.demo.listdemo | grep -i permission
    installPermissionsFixed=true
    declared permissions:
      android.permission.SYSTEM_ALERT_WINDOW: prot=normal
    requested permissions:
      android.permission.POST_NOTIFICATIONS
    install permissions:
      android.permission.REAL_GET_TASKS: granted=true
      android.permission.REMOTE_AUDIO_PLAYBACK: granted=true
      android.permission.DOWNLOAD_WITHOUT_NOTIFICATION: granted=true
      android.permission.ACCESS_GPU_SERVICE: granted=true
      android.permission.REGISTER_WINDOW_MANAGER_LISTENERS: granted=true
      android.permission.WRITE_SETTINGS: granted=true
      android.permission.CONTROL_KEYGUARD: granted=true
      android.permission.SYSTEM_ALERT_WINDOW: granted=true // SYSTEM_ALERT_WINDOW 被授予
...
console:/ #

可以看到给这个应用赋予了系统签名后,就可以获取到悬浮框权限和其他很多权限。
主界面代码也是吐司提示已获取到悬浮框权限的。

我这边不能100% 确定:只有某些系统应用或者系统签名应用才可以申请到悬浮框权限;
因为目前开发的系统对权限的控制是有一定的适配的;
所以不太确定是Android14系统原生会这样还是适配后导致的。

(2)Android11 上:

Android11 上应用uid查看的是userId,而不是 appId。

普通应用:

console:/ # 
console:/ # dumpsys package com.demo.listdemo | grep path
    path: /data/app/~~ZCTL3S1kYt7BgSa9-x1M9g==/com.demo.listdemo-0vU4jx3t6Vwv55tTtGuOgg==/base.apk
console:/ # dumpsys package com.demo.listdemo | grep -i permission
    installPermissionsFixed=true
      runtime permissions:
      com.android.permissioncontroller:
console:/ # dumpsys package com.demo.listdemo | grep -i userid
    userId=10052
console:/ # 

从上面信息可以看到是普通的安装目录,没看到什么权限信息,userId也是普通应用的uid。

一样的未显示悬浮框权限,原生设置权限管理也看不到这个应用的悬浮框权限,真奇怪,我还以为可以申请的!

系统应用:

apk试了几次,push到 /system/priv-app/Demo/下面
但是重启后,系统未安装这个应用!无法查看具体情况。
估计也是没有默认给权限的。
我记得之前的一些非系统签名应用是可以授予悬浮框权限的。

系统签名应用:

console:/ # 
130|console:/ # dumpsys package com.demo.listdemo | grep path
    path: /data/app/~~ODurfPp13MvbmaDOrNMu5A==/com.demo.listdemo-iNQy6MjPURB4V3F1clRDig==/base.apk
console:/ # 
console:/ # dumpsys package com.demo.listdemo | grep -i userid
    userId=1000
    userId=1000
console:/ # dumpsys package com.demo.listdemo | grep -i SYSTEM_ALERT_WINDOW
      android.permission.SYSTEM_ALERT_WINDOW: prot=normal
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
      android.permission.SYSTEM_ALERT_WINDOW: granted=true
console:/ # 
console:/ # 

系统签名应用,不用放到system/priv-app下,安装上去也是可以获取系统签名权限的。
从上面信息看,确实是系统签名应用,并且获取到了悬浮框权限的。

所以有可能是要系统签名才能获取到悬浮框权限了。

上面的信息提供做一个参考吧。
在不同的Android版本和系统环境下可能会出现不同的情况。

4、Android 应用添加系统签名权限的几种方式实现介绍

Android 应用添加系统签名就能获取到系统权限调用一些系统接口,

添加系统签名的方式主要包括:

在Android Studio中配置签名文件生成apk 和 在源码目录编译添加系统签名生成apk。

下面介绍的都是一些基础的签名知识,后续延伸介绍相关权限内容。

https://blog.csdn.net/wenzhi20102321/article/details/136024007

03-21 23:30:13.628 1000 2675 2719 I ActivityManager: Start proc 28941:system/u14s1000 for broadcast {com.android.server.telecom/com.android.server.telecom.components.AppUninstallBroadcastReceiver} 03-21 23:30:13.630 1000 3393 3393 D AolSceneNotifer: updateScreenState Screen INTERNAL on 03-21 23:30:13.631 1410212 28828 28828 W ContextImpl: Failed to ensure /data/user_de/0/com.google.android.permissioncontroller/files: mkdir failed: ENOENT (No such file or directory) 03-21 23:30:13.631 1000 3393 3393 D AolSceneNotifer: updateRotationState Screen INTERNAL USB_TOWARD_DOWN 03-21 23:30:13.632 1410212 28828 28828 W ContextImpl: Failed to ensure /data/user_de/0/com.google.android.permissioncontroller/files: mkdir failed: ENOENT (No such file or directory) 03-21 23:30:13.632 1410212 28828 28828 W FileUtils: Failed to chmod(/data/user_de/0/com.google.android.permissioncontroller/files): android.system.ErrnoException: chmod failed: ENOENT (No such file or directory) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: Could not write packages_already_notified_location_access 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: java.io.FileNotFoundException: /data/user_de/0/com.google.android.permissioncontroller/files/packages_already_notified_location_access: open failed: ENOENT (No such file or directory) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: at libcore.io.IoBridge.open(IoBridge.java:574) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: at java.io.FileOutputStream.<init>(FileOutputStream.java:259) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: at android.app.ContextImpl.openFileOutput(ContextImpl.java:795) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: at com.android.permissioncontroller.permission.service.LocationAccessCheck.persistAlreadyNotifiedPackagesLocked(LocationAccessCheck.java:322) 03-21 23:30:13.633 1410212 28828 28828 E LocationAccessCheck: at com.android.permission
最新发布
03-22
<think>好的,我现在需要解决用户在Android系统中遇到的ENOENT错误,具体是“mkdir failed: no such file or directory”。首先,我得理解这个错误的原因。根据用户提供的引用内容,ENOENT错误通常是因为文件或目录不存在,或者路径中的父目录缺失。比如引用[5]提到,创建目录时如果父目录不存在,就会报错,建议使用递归创建目录的方法。类似的情况可能在Android中也适用。 接下来,我需要考虑Android系统的文件权限问题。用户提到的引用[1]和[2]都是关于在sdcard或特定路径下创建文件时出现的ENOENT错误。这可能涉及到Android的存储权限,特别是从Android 10(API 29)开始,作用域存储(Scoped Storage)的限制,应用不能随意访问外部存储的任意位置。用户的应用可能没有正确的权限,或者试图访问不允许的目录。 另外,路径是否正确也是一个关键因素。用户可能在代码中硬编码了路径,例如/storage/emulated/0/...,但不同设备可能有不同的路径表示方式。应该使用Environment.getExternalStorageDirectory()或Context.getExternalFilesDir()等方法来获取正确的路径,避免硬编码。引用[2]中的错误路径可能就是硬编码导致的。 还要检查目标目录是否存在。如果父目录不存在,直接创建子目录就会失败,这时候需要递归创建所有需要的父目录。引用[5]中的解决方案提到使用fs.mkdir的recursive选项,这在Android开发中可能需要使用Java的File.mkdirs()方法,它也会自动创建父目录。 另外,用户提到的错误信息中有“locationaccesscheck”和“persistalreadynotifiedpackageslocked”,这可能涉及到某些特定的权限或系统服务的问题,但暂时不太清楚具体关联。可能需要检查是否有相关的权限声明,或者系统服务是否正常。 综合以上分析,解决步骤可能包括: 1. 检查并请求正确的存储权限。 2. 使用正确的API获取外部存储路径,避免硬编码。 3. 确保目标目录的父目录存在,或使用递归创建。 4. 检查应用是否在正确的上下文中操作文件,比如使用应用专属目录。 同时,需要参考用户提供的引用内容,特别是关于路径设置和递归创建目录的建议。例如引用[4]提到修改.npmrc文件中的路径设置,虽然这是Node.js的例子,但原理类似,即确保路径配置正确。在Android中,可能需要检查路径是否正确获取,或者是否存在拼写错误。 最后,可能需要编写示例代码,展示如何正确请求权限、获取路径和创建目录,并处理可能的异常情况,帮助用户一步步解决问题。</think>要解决Android系统中因文件路径不存在导致的`ENOENT`错误(如`mkdir failed: no such file or directory`),可以按照以下步骤排查和修复: --- ### 1. **检查存储权限** Android 10及以上版本引入了**作用域存储(Scoped Storage)**,应用默认只能访问自身专属目录(如`Context.getExternalFilesDir()`)。若需访问公共目录(如下载、文档等),需: - 在`AndroidManifest.xml`中添加权限声明: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` - **动态请求权限**(针对Android 6.0及以上): ```java ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); ``` --- ### 2. **使用正确的路径获取方式** 避免硬编码路径(如`/storage/emulated/0/...`),应通过系统API获取路径: - **应用专属目录**(无需权限): ```java File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); // 路径如:/storage/emulated/0/Android/data/<包名>/files/Documents ``` - **公共目录**(需权限): ```java File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // 如:/storage/emulated/0/Download ``` --- ### 3. **递归创建目录** 若目标路径的父目录不存在,直接创建会失败。需使用`File.mkdirs()`代替`File.mkdir()`: ```java File targetDir = new File("/path/to/your/directory"); if (!targetDir.exists()) { boolean success = targetDir.mkdirs(); // 自动创建所有缺失的父目录 if (!success) { Log.e("TAG", "目录创建失败"); } } ``` --- ### 4. **检查路径合法性** - **空格或特殊字符**:路径中包含空格或特殊符号(如`#`, `%`)可能导致解析失败。 - **设备兼容性**:某些设备可能没有SD卡插槽,需先检查外部存储是否可用: ```java if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // 外部存储可用 } ``` --- ### 5. **错误处理示例** 结合日志和异常捕获定位问题: ```java try { File file = new File(context.getExternalFilesDir(null), "test.txt"); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); // 确保父目录存在 } FileOutputStream fos = new FileOutputStream(file); fos.write("test".getBytes()); fos.close(); } catch (IOException e) { Log.e("TAG", "错误:" + e.getMessage()); e.printStackTrace(); } ``` --- ### 常见错误场景 1. **未动态申请权限**:仅声明权限不够,需在运行时请求用户授权[^1][^2]。 2. **路径拼写错误**:如`/storage/emulated/0/Document`应改为`/storage/emulated/0/Documents`[^2]。 3. **作用域存储限制**:Android 10+默认禁止直接访问外部存储根目录,应改用`MediaStore`或应用专属目录[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值