一、概述
从Android 6.0(API 23)开始,系统权限做了很大的改变。在之前,6.0以下的权限及在安装时,会根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装。也就是说当我们想要使用某个app时,就要默默忍受一些不必要的权限(比如访问通讯录和短信等)。而在Android 6.0(API 23)以后,我们可以直接安装app。app的权限授予是在app运行时授予的,而不是在app安装时授予。这样既简化app安装过程,当app需要我们授予不恰当的权限的时候,我们也可以选择拒绝授予这些权限。已授予过的权限,我们还可以去app设置页面去关闭授权。这站在用户的角度上来说提高了安全性,可以防止一些应用恶意访问用户数据。但是对于开发者来说,也增加了不少工作量。当app要访问敏感用户数据(如联系人和短信)以及某些系统功能(如相机和互联网的权限)就必须先动态申请权限。根据功能的不同,系统可能会自动授予权限,或者可能会提示用户批准请求。
和往常一样,主要是想总结一下对Android 6.0动态权限申请的学习过程以及一些需要注意的地方。
详细请查看谷歌官方文档:https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-runtime-permissions
二、权限和权限组
新的权限机制更好的保护了用户的隐私,Google将权限分为两类:正常权限(Normal Permissions)和危险权限(Dangerous Permission)
2.1、正常权限
这类权限一般不涉及用户隐私,是不需要用户进行授权的,只要app在其清单中列出了正常权限,系统将自动授予该权限,比如设置时区,手机震动和访问网络等。
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
2.2、危险权限
这类权限一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录和短信等。
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
我们可以通过adb shell pm list permissions -d -g
进行查看
在这里我们会发现危险权限都是分组存在的,其分组的权限机制为:当你申请某个危险的权限时,假设你的app早已被用户授权了同一权限组中的某个危险权限时,那么系统会立即授权,不需要用户再授权。比如你的app之前已经请求并被授予了READ_CONTACTS权限,然后当它请求WRITE_CONTACTS权限时,系统会立即授予该权限,而不会向用户显示权限对话框。
三、代码的实现
按照惯例,我们先来看看效果图
从效果图中可以看到,当页面启动时,向用户显示要请求的权限对话框,分别是读取外置存储的权限、访问摄像头、读取通讯录和读取短信。当授权完成后,在onRequestPermissionsResult方法回调中,检查如果没有全部授予权限时,并提示目前缺少权限,要不要到其app设置页面授权等。在这里当app需要我们授予不恰当的权限的时候,我们也可以选择拒绝授予这些权限。已授予过的权限,我们也可以到app设置页面去关闭授权。
好了,下面我们来看看具体的实现吧
首先,必须在应用程序清单中通过
然后就是完整的代码实现啦,代码都有注释,在这里就不一一解释啦
package per.lijuan.permissiondome
import android.Manifest
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import java.util.*
class MainActivity : AppCompatActivity() {
companion object {
private const val KEY_PERMISSIONS_REQUEST_COUNT = "KEY_PERMISSIONS_REQUEST_COUNT"
private const val MAX_NUMBER_REQUEST_PERMISSIONS = 1 //请求权限的最多次数
private const val REQUEST_CODE_PERMISSIONS = 101
}
private var mPermissionRequestCount: Int = 0
// 应用程序需要的权限列表
private val sPermissions = object : ArrayList() {
init {
add(Manifest.permission.READ_EXTERNAL_STORAGE)//读取外置存储的权限
add(Manifest.permission.CAMERA)//访问摄像头
add(Manifest.permission.READ_CONTACTS)//读取通讯录
add(Manifest.permission.READ_SMS)//读取短信
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
mPermissionRequestCount = savedInstanceState.getInt(KEY_PERMISSIONS_REQUEST_COUNT, 0)
}
requestPermissionsIfNecessary()
}
/**
* 检查是否授予权限
* 如果全部获取, 则直接过.
* 如果权限缺失, 则提示Dialog
*
* @param requestCode 请求码
* @param permissions 权限
* @param grantResults 结果
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS && !checkAllPermissions())
showMissingPermissionDialog()
}
/**
* 检查目前应用是否有所有我们需要的权限
* 如果全部获取, 则直接过.
* 如果权限缺失, 就判断当前请求权限的次数是否已达到最大值,如果没有则再次请求权限,否则就提示Dialog
*/
private fun requestPermissionsIfNecessary() {
if (!checkAllPermissions()) {
if (mPermissionRequestCount < MAX_NUMBER_REQUEST_PERMISSIONS) {
mPermissionRequestCount += 1
ActivityCompat.requestPermissions(
this,
sPermissions.toTypedArray(),
REQUEST_CODE_PERMISSIONS)
} else {
Toast.makeText(this,"缺失权限",Toast.LENGTH_LONG).show()
}
}
}
/**
* 检查应用目前是否有这些权限
*/
private fun checkAllPermissions(): Boolean {
var hasPermissions = true
for (permission in sPermissions) {
hasPermissions = hasPermissions and (ContextCompat.checkSelfPermission(
this, permission) == PackageManager.PERMISSION_GRANTED)
}
return hasPermissions
}
/**
* 显示缺失权限提示
*/
private fun showMissingPermissionDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle("帮助")
builder.setMessage("当前应用缺少必要权限。\n \n 请点击 \"设置\"-\"权限\"-打开所需权限。")
builder.setNegativeButton("取消", DialogInterface.OnClickListener(){ _, _ ->
})
builder.setPositiveButton("设置", DialogInterface.OnClickListener { _, _ ->
// 跳转到当前应用对应的设置页面
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:" + this.packageName)
startActivity(intent)
})
builder.setCancelable(false)
builder.show()
}
}
这代码还有待进一步完善,因为在的权限申请中,我们看到它依赖于Activity或者Fragment的回调方法。但我觉得代码有待封装,可以采用单独的Activity来申请权限,通过回调的方式通知业务层,这样会方便点。
本篇文章大概就这么多,有什么疑问的,请在下面留言,有不足之处还望指导,感谢各位_