因为Android M权限问题导致的"Permission Denial: reading com.android.providers.media.MediaProvider"解决办法

本文详细解析了在Android API级别23及以上时遇到的权限访问问题,特别是针对危险权限如READ_EXTERNAL_STORAGE的具体解决方案,包括使用PermissionsDispatcher库来实现运行时权限请求。

程序出错报告

在模拟器上调试程序,出错代码如下:

Cursor cur = context.getContentResolver().query(        
    MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
    new String[] { MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.ARTIST,
                        MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA }, null, null, null);

AndroidManifest.xml已经添加了如下权限.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

但是有如下错误:

java.lang.RuntimeException: Unable to start activity ComponentInfo{io.github.oncealong.yplayer/io.github.oncealong.yplayer.MainActivity}: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/audio/media from pid=8520, uid=10058 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()

Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/audio/media from pid=8520, uid=10058 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()

程序出错原因

最后查明是因为API过高权限访问有修改, 在API级别>=23时, 权限访问被分为三个级别, 分别为”PROTECTION_NORMAL, PROTECTION_DANGEROUS, 和PROTECTION_SIGNATURE(还有两个标志可以和SIGNATURE联合使用才有意义)”. PROTECTION_NORMAL是普通权限, 通过manifest文件在安装时被授予. PROTECTION_SIGNATURE是签名权限, 通过”检查manifest和app签名是否匹配app中声明的权限”在安装时授予. 对于 PROTECTION_DANGEROUS, 不仅需要在manifest中声明, 还需要在运行时通过requestPermissions获得, 也就是弹出来一个个对话框, 让用户确认是否授予app这些权限.
这些是常见PROTECTION_DANGEROUS权限, 如果你在程序中使用了, 那么在API>=23, 很可能会不正常工作.
ACCESS_COARSE_LOCATION
ACCESS_FINE_LOCATION
ADD_VOICEMAIL
BODY_SENSORS
CALL_PHONE
CAMERA
GET_ACCOUNTS
PROCESS_OUTGOING_CALLS
READ_CALENDAR
READ_CALL_LOG
READ_CELL_BROADCASTS
READ_CONTACTS
READ_EXTERNAL_STORAGE
READ_PHONE_STATE
READ_SMS
RECEIVE_MMS
RECEIVE_SMS
RECEIVE_WAP_PUSH
RECORD_AUDIO
SEND_SMS
USE_SIP
WRITE_CALENDAR
WRITE_CALL_LOG WRITE_CONTACTS
WRITE_EXTERNAL_STORAGE

一篇讲解Android M最新的运行时权限的文章.
[http://jijiaxin89.com/2015/08/30/Android-s-Runtime-Permission/]

错误解决办法

最后解决办法: 使用PermissionsDispatcher库,这里封装了常用操作.github地址: https://github.com/hotchemi/PermissionsDispatcher.

简易使用方法:
1. 在build.gradle(Project)中的buildscript.dependecies添加:

 dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }

2. 在build.gradle(Module)中紧挨着

apply plugin: 'com.android.application'

添加

apply plugin: 'android-apt'

然后在 dependencies中添加如下内容,其中2.1.2代表版本.

compile "com.github.hotchemi:permissionsdispatcher:2.1.2"
apt "com.github.hotchemi:permissionsdispatcher-processor:2.1.2"

整体看起来就像这样,带有**的表示需要添加的:

apply plugin: 'com.android.application'
**apply plugin: 'android-apt'**

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "io.github.oncealong.yplayer"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:+'
    **compile "com.github.hotchemi:permissionsdispatcher:2.1.2"** 
    **apt "com.github.hotchemi:permissionsdispatcher-processor:2.1.2"**
}

3. 在需要权限的类上加上@RuntimePermissions注解, 在需要权限的方法上加上@NeedsPermission, 需要权限的方法不能是private, PermissionsDispatcher不是使用的反射, 而是使用的委托的方式. 这两个注解是必须要有的,如下:

@RuntimePermissions
public class MainActivity extends AppCompatActivity {
    ...
    @NeedsPermission(Manifest.permission.CAMERA)
    void showCamera() {
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
                .addToBackStack("camera")
                .commitAllowingStateLoss();
    }
    ...
}

4. build一下, 会自动生成一个叫MainActivityPermissionsDispatcher的类, 所有需要权限的操作都会委托给这个类. 然后在activity的onRequestPermissionsResult方法中将操作委托给MainActivityPermissionsDispatcher.onRequestPermissionsResult方法.

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    // NOTE: delegate the permission handling to generated method
    MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}

搞定收工, 如果你感觉这种简单还在接受范围内. 可以去github主页看下.

### 解决Java.Lang.SecurityException Android.Permission.READ_CONTACTS 权限拒绝问题 当应用程序尝试访问联系人数据而未获得 `READ_CONTACTS` 权限时,会抛出 `SecurityException`。以下是针对此问题的解决方案: #### 1. **确认清单文件中的权限声明** 确保在应用的 `AndroidManifest.xml` 文件中已正确定义了所需的权限。对于读取联系人的操作,需添加以下权限声明[^1]: ```xml <uses-permission android:name="android.permission.READ_CONTACTS"/> ``` 如果目标 API 级别为 23 或更高,则还需要动态请求运行时权限。 --- #### 2. **实现运行时权限请求逻辑** 自 Android 6.0 (API Level 23) 起,某些敏感权限(如 `READ_CONTACTS`)需要在运行时显式请求。可以通过以下方式处理: ##### 请求权限的方法 创建一个方法来检查并请求权限: ```java private static final int REQUEST_CODE_READ_CONTACTS = 1; public void requestReadContactsPermission() { if (!hasReadContactsPermission()) { // 检查是否有权限 ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_READ_CONTACTS); } } ``` ##### 检查权限状态 定义辅助函数用于检测当前是否拥有该权限: ```java private boolean hasReadContactsPermission() { return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED; } ``` ##### 处理权限回调 重写 `onRequestPermissionsResult` 方法以响应用户的授权决定: ```java @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_CODE_READ_CONTACTS && grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 用户授予权限后的逻辑 accessContactsData(); } else { // 如果用户拒绝权限,提示他们手动开启 showPermissionDeniedDialog(); } } } ``` --- #### 3. **适配权限被永久拒绝的情况** 如果用户选择了“不再询问”,则需要引导其进入设置页面手动授予权限。可以提供跳转到应用设置的功能: ```java private void openAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } ``` 调用此功能前应先判断是否存在这种情况: ```java if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { // 用户可能已经勾选了“不再显示” showGoToSettingsDialog(); // 提示用户前往设置页 } else { requestReadContactsPermission(); // 正常请求权限 } ``` --- #### 4. **测试与验证** 完成以上修改后,在不同设备和模拟器上进行全面测试,特别是关注以下场景: - 应用首次启动时自动弹窗请求权限。 - 当用户拒绝权限时的行为表现。 - 验证权限被禁用后再启用的效果。 通过这些措施能够有效解决由缺失 `READ_CONTACTS` 导致的安全异常问题[^2]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值