Android 6(M)

运行时权限

Android 的权限机制从系统的第一个版本开始就已经存在了,只不过之前的权限机制在保护用户安全隐私等方面起到的作用比较有限。为此,Android 6.0 系统中引入了运行时权限这个功能。

在加入了权限声明后,用户主要在两个方面得到了保护。

  • 如果用户在低于 6.0 系统的设备上安装该程序,会在安装界面给出提示,知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。
  • 用户可随时在应用程序管理界面查看任意一个程序的权限申请情况,保证程序不会出现各种滥用权限的情况。

在 Android 6.0 系统中加入了运行时权限功能,这样,用户不需要在安装软件时一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。

Android 将常用权限大致归为两类:普通权限、危险权限。(其实还有一些特殊权限,但使用较少)

  • 普通权限:指那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限,系统会自动帮我们进行授权,不需要用户手动操作。
  • 危险权限:指那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息,定位设备地理位置等,对于这部分权限申请,必须由用户手动授权才行,否则程序就无法使用相应的功能。

到 Android 10 系统为止所有的危险权限

权限组名(group) 权限名(permission)
CALENDAR READ_CALENDAR
WRITE_CALENDAR
CALL_LOG READ_CALL_LOG
WRITE_CALL_LOG
PROCESS_OUTGOING_CALLS
CAMERA CAMERA
CONTACTS WRITE_CONTACTS
READ_CONTACTS(读取系统联系人)
GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_BACKGROUND_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
READ_PHONE_NUMBERS
CALL_PHONE
ANSWER_PHONE_CALLS
ADD_VOICEMAIL
USE_SIP
ACCEPT_HANDOVER
SENSORS BODY_SENSORS
ACTIVITY_RECOGNITION ACTIVITY_RECOGNITION
SMS SEND_SMS
READ_SMS
RECEIVE_SMS
RECEIVE_MMS
RECEIVE_WAP_PUSH
STORAGE READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
ACCESS_MEDIA_LOCATION

说明:

1
2
所需权限无论是否是危险权限 均需要在清单文件声明
在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限,App才能被安装,App一旦安装后后授权不可取消。Android 6.0 的已授权运行时权限可在手机设置内取消权限。

查看权限目录:

1
2
通过 adb shell 命令
查看危险权限 adb shell pm list permissions -d -g

注意:原则上,用户一旦同意了某个权限申请后,同组的其它权限也会被系统自动授权。但是,不要基于此规则来实现任何功能逻辑,因为 Android 系统随时有可能调整权限的分组。


示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MainActivity extends BaseActivity{

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

requestPermiss();
}

/**
* 请求权限
*/
private void requestPermiss() {
//危险权限
request(new OnPermissionsResult() {
@Override
public void OnSuccess() {

}

@Override
public void OnFail(List<String> noPermissions) {
LogUtils.d("noPermissions:" + noPermissions.toString());
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
public class BaseActivity extends AppCompatActivity {

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

//申请运行时权限的Code
private static final int PERMISSION_REQUEST_CODE = 1000;
//申请窗口权限的Code
public static final int PERMISSION_WINDOW_REQUEST_CODE = 1001;

//申明所需权限
private String[] mStrPermission = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CALL_PHONE,
Manifest.permission.ACCESS_FINE_LOCATION

};

//保存没有同意的权限
private List<String> mPerList = new ArrayList<>();
//保存没有同意的失败权限
private List<String> mPerNoList = new ArrayList<>();

private OnPermissionsResult permissionsResult;

/**
* 一个方法请求权限
*
* @param permissionsResult
*/
protected void request(OnPermissionsResult permissionsResult) {
if (!checkPermissionsAll()) {
requestPermissionAll(permissionsResult);
}
}

/**
* 判断单个权限
*
* @param permissions
* @return
*/
protected boolean checkPermissions(String permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = checkSelfPermission(permissions);
return check == PackageManager.PERMISSION_GRANTED;
}
return false;
}

/**
* 判断是否需要申请权限
*
* @return
*/
protected boolean checkPermissionsAll() {
mPerList.clear();
for (int i = 0; i < mStrPermission.length; i++) {
boolean check = checkPermissions(mStrPermission[i]);
//如果不同意则请求
if (!check) {
mPerList.add(mStrPermission[i]);
}
}
return mPerList.size() > 0 ? false : true;
}

/**
* 请求权限
*
* @param mPermissions
*/
protected void requestPermission(String[] mPermissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(mPermissions, PERMISSION_REQUEST_CODE);
}
}

/**
* 申请所有权限
*
* @param permissionsResult
*/
protected void requestPermissionAll(OnPermissionsResult permissionsResult) {
this.permissionsResult = permissionsResult;
requestPermission((String[]) mPerList.toArray(new String[mPerList.size()]));
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
mPerNoList.clear();
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
//你有失败的权限
mPerNoList.add(permissions[i]);
}
}
if (permissionsResult != null) {
if (mPerNoList.size() == 0) {
permissionsResult.OnSuccess();
} else {
permissionsResult.OnFail(mPerNoList);
}
}
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

protected interface OnPermissionsResult {
void OnSuccess();

void OnFail(List<String> noPermissions);
}

/**
* 判断窗口权限
*
* @return
*/
protected boolean checkWindowPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(this);
}
return true;
}

/**
* 请求窗口权限
*/
protected void requestWindowPermissions() {
Toast.makeText(this, "申请窗口权限,暂时没做UI交互", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION
, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, PERMISSION_WINDOW_REQUEST_CODE);
}
}
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.framework">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>

</manifest>

##示例(Kotlin)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 运行时权限的核心,就是在程序运行过程中,由用户授权我们去执行某些危险操作。
*/
class PermissionActivity :BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_permission)

// 申请运行时权限
btnCall.setOnClickListener{
// 先判断用户是否已经给过授权了,借助的是 ContextCompat.checkSelfPermission(),接收两个参数:
// 第一个参数是 Context。
// 第二个参数是具体的权限名,比如打电话的权限名是 Manifest.permission.CALL_PHONE。
// 然后使用方法的返回值和 PackageManager.PERMISSION_GRANTED 比较,相等就说明用户已经授权。
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE)!=PackageManager.PERMISSION_GRANTED){
// 如果不相等,说明未授权,则需要调用 ActivityCompat.requestPermissions() 向用户申请授权,它接收三个参数:
// 第一个参数要求是 Activity 的实例
// 第二个参数是一个 String 数组,把要申请的权限名放在数组中即可。
// 第三个参数是请求码,只要是唯一值就可以。
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CALL_PHONE),1)
}else {
call()
}
}
}

/**
* 调用完 requestPermissions() 后,系统会弹出一个申请权限的对话框,不论是否同意,最终都会回调到这个方法中。
* 授权的结果会封装在 grantResults 参数中。
*/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode){
// 匹配请求码
1 -> {
// 判断授权结果
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
call()
}else{
Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show()
}
}
}
}

private fun call(){
// 打开拨号界面,不需要声明权限.
// val intent = Intent(Intent.ACTION_DIAL)

// 直接拨打电话,危险权限.清单文件声明和动态申请权限,都需要.
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:1008611")
startActivity(intent)
}catch (e: SecurityException){
e.printStackTrace()
}
}

companion object{
fun actionStart(context: Context){
val intent = Intent(context, PermissionActivity::class.java)
context.startActivity(intent)
}
}
}

备注

参考资料:

第一行代码(第3版)