1、问题描述
如下一段代码,在Android 5.0设备运行时,可以得到正确结果,但在Android 6.0以上设备运行时,提示没有读写权限。但AndroidManifest文件中已经设置过权限了。
错误提示信息是这样的:
Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/audio/media from pid=3508, uid=10078 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
Activity代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
do {
String title = cursor.getString(cursor.getColumnIndex("title"));
Log.e("TITLE",title);
}while (cursor.moveToNext());
}
}
AndroidManifest代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lenovo.permissiontest">
<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/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
2、问题诊断
众所周知,我们在做Android开发时,经常会用到一些系统的权限,比如读写外部存储、读写联系人信息、调用电话短信、访问网络等。在Android 6.0之前,对于这些权限调用的声明,只需要在AndroidManifest文件中声明即可,就像下面这样:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
用户在安装应用时,会提示该程序总共用到哪些权限,但用户没有选择权,只有知情权,那么安全问题就随之而来。所以在android 6.0 棉花糖版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。这就是所谓的“权限动态授权”。
那么哪些权限是危险权限呢?下表所示即为危险权限,这些权限都必须在应用内部经用户动态授权才能使用。
3、问题解决
动态权限分配,通过代码如何实现呢?请看下面代码。
Activity代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//检查当前权限(若没有该权限,值为-1;若有该权限,值为0)
int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(getApplication(),Manifest.permission.READ_EXTERNAL_STORAGE);
Log.e("PERMISION_CODE",hasReadExternalStoragePermission+"***");
if(hasReadExternalStoragePermission== PackageManager.PERMISSION_GRANTED){
obtainMediaInfo();
}else{
//若没有授权,会弹出一个对话框(这个对话框是系统的,开发者不能自己定制),用户选择是否授权应用使用系统权限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
}
}
//用户选择是否同意授权后,会回调这个方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode==1){
if(permissions[0].equals(Manifest.permission.READ_EXTERNAL_STORAGE)&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
//用户同意授权,执行读取文件的代码
obtainMediaInfo();
}else{
//若用户不同意授权,直接暴力退出应用。
// 当然,这里也可以有比较温柔的操作。
finish();
}
}
}
//将之前获取音乐信息的代码单独封装到了一个方法里面
private void obtainMediaInfo() {
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
cursor.moveToFirst();
do {
String title = cursor.getString(cursor.getColumnIndex("title"));
Log.e("TITLE",title);
}while (cursor.moveToNext());
}
}
总结
这里主要用到两个方法
- ActivityCompat.requestPermissions()
- onRequestPermissionsResult()
ActivityCompat.requestPermissions()方法有3个参数:
第1个参数,当前上下文环境;
第2个参数,需要授权的字符串数组;
第3个参数,请求码requestCode,在回调方法中会用到。
onRequestPermissionsResult()是个回调方法,也就是说在用户点击“允许”或“拒绝”按钮后,才会调用。这个方法也有3个参数:
第1个参数,请求码,对应上述方法的第3个参数;
第2个参数,请求权限数组;
第3个参数,请求权限的结果数组。
详细使用方法请看上方代码,注释较为详细。
至此,我们已经完成了单个权限授权的代码。但是,距离完美还差那么一点点,比如:
- 如果我们想要一次性进行多次授权呢?
- 如果用户选择了不再弹出之后呢?
请继续往下看
4、更优雅的解决方案
Android6.0提出了如此周到、复杂的权限处理方式,对用户而言是好事,对程序员而言是极大的繁琐。
幸运的是我们有github,早已有人开发了第三方插件。
build.gradle中编译
compile ‘com.master.android:permissionhelper:1.3’
github链接里已经写了详细的使用步骤,我也写了个例子,亲测非常好用。代码附上。
public class HelloActivity extends AppCompatActivity {
private String TAG = "HelloActivity";
private PermissionHelper permissionHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello);
permissionHelper = new PermissionHelper(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
permissionHelper.request(new PermissionHelper.PermissionCallback() {
@Override
public void onPermissionGranted() {
//全都授权
Log.e(TAG,"onPermissionGranted...");
}
@Override
public void onIndividualPermissionGranted(String[] grantedPermission) {
//某个授权
Log.e(TAG,"onIndividualPermissionGranted() called with: grantedPermission = [" + TextUtils.join(",",grantedPermission) + "]");
}
@Override
public void onPermissionDenied() {
//某个拒绝
Log.e(TAG,"onPermissionDenied...");
}
@Override
public void onPermissionDeniedBySystem() {
//用户选择了"不再询问"后,点击"拒绝按钮",执行此方法
Log.e(TAG,"onPermissionDeniedBySystem...");
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (permissionHelper != null) {
permissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}