前段时间在写着玩时用到了一个定位的库,但却总没能成功获取到地理位置,反而换了部手机却又可以了。想到两部手机的版本定位不了的是6.0的,而另一部是4.4的,所以应该是权限没有。查看日志后也确是如此,为此就记录一下关于6.0(api 23)之后的运行时权限的获取问题。虽然这个很早就有了,但是仍有部分应用其实还是没有对此做处理的。本篇是以一个开源库PermissionsDispatcher
的使用来写这篇学习笔记。
Github链接:hotchemi/PermissionsDispatcher
用户: 应用安装时会罗列出全部需要的权限,确定安装则表示同意应用获取这些权限,如果因为某个权限用户不想授予,则唯一的方法就是取消安装,最后结果是可能因为一小部分功能需要这个权限使得用户没能安装使用上该应用,这对于双方都是不愿看到的。
开发者: 在AndroidManifest
文件中声明所需要的权限,请求授权简单,需要什么权限加个uses-permission
就可以了。
用户: 用户可以在手机的设置中或者一些有权限管理的应用中对我们的应用权限进行操作,禁止他们不愿意授予的权限。结果是他们仍然可以安装使用应用而且还能,而且使用时也更放心。
开发者: 需要在需要权限的地方添加代码动态申请权限的授予,在等到用户同意时才能进入到该需要权限的功能模块。相对之前而言请求授权是复杂了些,但是对于用户确是更为友好。
系统的权限大致可分为正常权限、危险权限、特殊权限
所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:
如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。
权限组 | 权限 |
---|---|
CALENDAR (日历) |
READ_CALENDAR WRITE_CALENDAR |
CAMERA (相机) |
CAMERA |
CONTACTS (通讯录) |
READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS |
LOCATION (地理位置) |
ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION |
MICROPHONE (麦克风) |
RECORD_AUDIO |
PHONE (电话) |
READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS |
SENSORS (传感器) |
BODY_SENSORS |
SMS (短信) |
SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
STORAGE (存储) |
READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE |
在GitHub上他们已经有一些介绍,看的懂得可以直接看上面的README文档,此处只说明我这边的实现步骤,并且以一个简单的拨打电话demo来举例,或许写法有些不一样但目的都是能最终实现权限的授予。
效果图:
既然我们的项目针对的用户有6.0及其以上的系统,那么就需要将我们项目中的targetSdkVersion
改为23或者以上
在Project的build.gradle文件中添加一下代码
buildscript {
repositories {
jcenter()
...
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
...
}
}
在Module的build.gradle文件中添加
apply plugin: 'android-apt'
dependencies {
...
/*后面的版本查看GitHub上*/
compile 'com.github.hotchemi:permissionsdispatcher:2.4.0'
apt 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
}
首先根据我们的例子是拨打电话,那么需要的权限是CALL_PHONE
,此时我们仍然需要在AndroidManifest
文件中添加权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
我们照常写我们的逻辑代码,写完之后就是对需要权限的地方做下申请操作。先给出我的完整代码,基本上依葫芦画瓢就可以了,使用起来很简单。具体的在代码之后再说明一下。
@RuntimePermissions
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button mBtnCallPhone;
private TextView mTvTelNum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mBtnCallPhone = (Button) findViewById(R.id.btn_callPhone);
mBtnCallPhone.setOnClickListener(this);
mTvTelNum = (TextView) findViewById(R.id.tv_telNum);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_callPhone:
//发起权限申请,名字为“该Activity名+PermissionsDispatcher.调用方法名+WithCheck”
MainActivityPermissionsDispatcher.callPhoneWithCheck(MainActivity.this);
callPhone();
break;
default:
break;
}
}
/**
* 需要权限的方法
*/
@NeedsPermission(Manifest.permission.CALL_PHONE)
void callPhone(){
String telNum = mTvTelNum.getText().toString().trim();
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+telNum));
try {
startActivity(intent);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 唤出权限时的提示
* @param request 所要申请的权限
*/
@OnShowRationale(Manifest.permission.CALL_PHONE)
void showRationaleForCallPhone(PermissionRequest request) {
showRationaleDialog("使用此功能需要打开拨打电话的权限", request);
}
/**
* 被用户拒绝
*/
@OnPermissionDenied(Manifest.permission.CALL_PHONE)
void onCallPhoneDenied() {
Toast.makeText(this,"权限未授予,功能无法使用",Toast.LENGTH_SHORT).show();
}
/**
* 被拒绝并勾选不在提醒授权时,应用需提示用户未获取权限,需用户自己去设置中打开
*/
@OnNeverAskAgain(Manifest.permission.CALL_PHONE)
void onCallPhoneNeverAskAgain() {
AskForPermission();
}
/**
* 告知用户具体需要权限的原因
* @param messageResId
* @param request
*/
private void showRationaleDialog(String messageResId, final PermissionRequest request) {
new AlertDialog.Builder(this)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();//请求权限
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage(messageResId)
.show();
}
/**
* 被拒绝并且不再提醒,提示用户去设置界面重新打开权限
*/
private void AskForPermission() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("当前应用缺少拨打电话权限,请去设置界面打开");
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName())); // 根据包名打开对应的设置界面
startActivity(intent);
}
});
builder.create().show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 将权限处理采用PermissionsDispatcher的处理方式
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
}
该开源库有五种注释,在上面例子中就可以看到这五种注释的意义。
@RuntimePermissions
(必须)标记需要运行时权限判断的Activity或者Fragment类,标注在该类上。 @NeedsPermission
(必须)标记需要权限的方法。即该方法中有需要权限授予,并是在授予后才执行。 @OnShowRationale
请求权限时的回调,在里面一般用于说明为何需要此权限。 @OnPermissionDenied
标记用户没有授予该权限或者获取权限被拒绝时执行的方法。 @OnNeverAskAgain
在有该标记的方法中执行如果权限请求被用户禁止且不再提示时的处理。 在此我们再回到上面的权限分类上,危险权限写法就可以像上面例子那样,但特殊权限Manifest.permission.SYSTEM_ALERT_WINDOW
和Manifest.permission.WRITE_SETTINGS
就有点改动,但改动不大,只是把onRequestPermissionsResult
中权限回调的写在onActivityResult
中。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
MainActivityPermissionsDispatcher.onActivityResult(this, requestCode);
}
再回来就是说下采用该库时调用方法的规则是你(@RuntimePermissions
标注的类名)+PermissionsDispatcher.(@NeedsPermission
标注的方法名)+WithCheck(@RuntimePermissions
标注的对象)。如我这里是:
MainActivityPermissionsDispatcher.callPhoneWithCheck(MainActivity.this);
注意:
此处需要注意的一点是除了第一个@RuntimePermissions
是在类上标注的,其余四个在方法上做标注的方法不能设为private
由于各个手机厂商的定制问题可能有着各自的权限管理方式,因此此处的“设置”部分大家可以自行考虑取舍,或者捕获一下异常,避免出现错误。
好了,到这里就可以在我们的6.0及以上的系统版本上运行我们的程序查验一下。基本上是可以了,也会发现这个库使用起来确实简单,就是在调用时如MainActivityPermissionsDispatcher
之类的名字写法麻烦,而且会报错,但基本上rebuild一下就可以了。可以尝试一下,当然还有很多运行时权限获取的开源库,只是我觉得就这个最简单。萝卜青菜各有所爱,适合自己的就行。
例子demo
参考:
Android6.0运行时权限解决方案:http://www.jianshu.com/p/d4a9855e92d3
权限最佳做法
系统权限