目录
1.概述
1.1 Android 6.0之前版本权限
Android 6.0(API23)之前应用的权限在安装程序时就全部授予,运行时应用不再需要询问用户。
1.2 Android 6.0及更高版本权限
在Android 6.0和更高版本对权限进行了分类,对某些涉及到用户隐私的权限需要在运行时根据用户的需要进行动态的授予(对话框咨询用户是否授予权限),这样用户就不需要再安装时被迫同意某些权限。
2.权限分类
Android系统权限分为几个保护级别,需要了解的两个最重要的保护级别是正常权限和危险权限。
2.1 正常权限
指的是不会对直接威胁到用户的安全和隐私,或其他应用操作风险很小的权限。这些权限在应用安装时授予,运行时不再询问用户。例如:网络访问,WIFI状态,音量设置等。
2.2 危险权限
涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户储存的数据或其他应用的操作产生影响的区域。例如:读取通讯录联系人信息,读写储存器数据,获取用户地理位置等。如果应用需要这些危险的权限,则必须在运行时明确的告诉用户,让用户手动的进行是否授予权限,如果不授权,则程序无法使用相应的功能。
3.权限组
3.1 权限组概念
Android系统对所有的危险权限进行了分组,称之为权限组。属于同一组的危险权限将自动合并授予,用户授予应用某个权限组的权限,则应用将获得该权限组下的所有权限。
3.2 权限组和危险权限列表
权限组和危险权限列表如下:
CALENDAR READ_CALENDAR (read_calendar)
WRITE_CALENDAR (write_calendar)
CAMERA CAMERA (camera) CONTACTS READ_CONTACTS (read_contacts)
WRITE_CONTACTS (write_contacts)
GET_ACCOUNTS (get_accounts)
LOCATION ACCESS_FINE_LOCATION (access_fine_location)
ACCESS_COARSE_LOCATION (access_coarse_loaction)
MICROPHONE RECORD_AUTO (record_aut0) PHONE READ_PHONE_STATE (read_phone_state)
CALL_PHONE (call_phone)
READ_CALL_LOG (read_call_log)
WRITE_CALL_LOG (write_call_log)
ADD_VOICEMAIL (add_voicemail)
USE_SIP (use_sip)
PROCESS_OUTGOING_CALLS (process_outgoing_calls)SENSORS BODY_SENDORS (body_sendors) SMS SEND_SMS (send_sms)
RECEIVE_SMS (receive_sms)
READ_SMS (read_sms)
RECEIVE_WAP_PUSH (receive_wap_push)
RECEIVE_MMS (receive_mms)
STORAGE READ_EXTERNAL_STORAGE (read_external_storage)
WRITE_EXTERNAL_STORAGE (write_external_storage)
3.3 权限组和权限的表示
权限组和权限在Android代码中以字符串常量来表示,分别定义在以下两个静态内部类的字段中:
android.Manifest.permission(权限): Manifest.permission.READ_CALENDAR Manifest.permission.READ_EXTERNAL_STORAGE
4.在运行时请求权限
4.1 原理
设备系统是Android 6.0(API 23)或更高版本,并且应用的targetSdkVersion是23或更高版本,则针对在AndroidManifest中声明的危险权限,在运行时还需要动态请求用户进行授权。动态权限请求相关操作的API封装在android:support.v4包中,发起请求权限的Activity需要直接或间接继承android.support.v4.app.FragmentActivity,代码步骤中主要包含如下几个方法:
4.2 检查权限
方法如下所示:
ContextCompat.checkSelfPermission(Context context,Permission permission) 返回检查指定权限是否拥有的结果 返回的值常量为:
有权限 PackageManager.PERMISSION_GRANTED 无权限 PackageManager.PERMISSION_DENIED 当应用需要到危险权限时,在执行权限相关代码前,使用该方法判断是否已经拥有指定的权限。如果有权限,则继续执行设计需要权限的代码;无权限,则向用户请求授予指定的权限。
4.3 解释权限
方法如下所示:
ActivityCompat.shouldShowRequestPermissionRationale(Activity activity,String permission) 返回值为boolean 该方法解释如下所示:
1.此方法用于判断是否有必要向用户解释为什么要这项权限 2.如果应用第一次请求过此权限,但是被用户拒绝了,则之后调用该方法将会返回true,此时就有必要向用户详细说明需要此权限的原因。 3.如果应用第一次请求此权限被用户拒绝了,第二次再次请求此权限时,用户勾选了权限请求对话框的“不再询问”复选框,则此方法返回false。 4.如果设备规范禁止应用拥有该权限,则此方法也返回false。 4.4 请求权限
方法如下所示:
ActivityCompat.requestPermission(Activity activity,String[] permissions,int requestCode) 动态配置包含指定数组里面的权限 该方法解释如下所示:
1.当检测到应用没有指定的权限时,调用此方法向用户请求权限,将弹出权限请求对话框询问用户,“允许”或“拒绝”指定的权限。 2.权限参数传入的是数组,可以调用该方法传入一次请求多个权限。传入的权限数组参数以单个具体权限为单位,但是当对话框询问用户授权时,属于同一个权限组的将自动合并询问权限一次。 3.请求的权限必须事先在AndroidManifest.xml中有声明,否则调用该方法请求时,将不弹出对话框,而是直接返回“拒绝”的结果。 4.第一次请求权限时,用户点击了“拒绝”,第二次再请求该权限时,对话框将出现“不再询问”的复选框,如果用户勾选了“不再询问”,并点击了“拒绝”,则之后再请求此权限组时将不在弹出对话框,而是直接返回“拒绝”的结果。 4.5 处理结果
请求权限的结果返回和接收一个Activity的返回类似,重写FragmentActivity或Fragment中的onRequestPermissionResult(...)方法,如下所示:
@Override public void onRequestPermissionResult(int requestCode,String[] permissions,int[] grantResults){ //处理结果 }
5.案例实现
这里以打电话为案例,需要用户动态授予权限:打电话的权限为 CALL_PHONE
布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="申请打电话" android:layout_centerInParent="true" /> </RelativeLayout>
AndroidManifest,xml :添加权限
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.luckyliuqs.permission_getphonecontacts"> <uses-permission android:name="android.permission.CALL_PHONE" /> <application ...... </application> </manifest>
java代码:MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = findViewById(R.id.button); button.setOnClickListener(this); } //执行打电话 public void call(){ try{ //Intent.ACTION_CALL系统内置的打电话动作 Intent intent = new Intent(Intent.ACTION_CALL); //指定协议为tel intent.setData(Uri.parse("tel:10086")); startActivity(intent); }catch (SecurityException e){ e.printStackTrace(); } } //处理请求结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case 1: //请求成功 if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ call(); }else{ Toast.makeText(this,"你拒绝了权限",Toast.LENGTH_SHORT).show(); } break; default: break; } } @Override public void onClick(View v) { //检查打电话权限是否已经被授予了 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { //向用户请求权限,指定请求码 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE},1); }else{ //已经授予了则开始打电话 call(); } }
效果图: