运行时权限
之前Android 的权限机制在保护用户安全和隐私等方面起到的作用比较有限,为此,Android 开发团队在Android 6.0 系统中引入了运行时权限这个功能,从而更好地保护了用户的安全和隐私。
1 Android 权限机制详解
加入了权限声明后,用户主要在两个方面得到了保护。**一方面,如果用户在低于Android 6.0 系统的设备上安装该程序,会在安装界面给出如图所示的提醒。**这样用户就可以清楚地知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。
另一方面,用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况,如图所示:
这种权限机制的设计思路其实非常简单,就是用户如果认可你所申请的权限,就会安装你的程序,如果不认可你所申请的权限,那么拒绝安装就可以了。
但是很多我们离不开的常用软件普遍存在着滥用权限的情况, 不管到底用不用得到,反正先把权限申请了再说。比如微信所申请的权限列表如图所示:
**Android 开发团队也意识到了这个问题,于是在Android 6.0 系统中加入了运行时权限功能。也就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。**比如一款相机应用在运行时申请了地理位置定位权 限,就算我拒绝了这个权限,也应该可以使用这个应用的其他功能,而不是像之前那样直接无法安装它。
但是,并不是所有权限都需要在运行时申请,对于用户来说,不停地授权也很烦琐。**Android 现在将常用的权限大致归成了两类,一类是普通权限,一类是危险权限。**准确地讲,其实还有一些特殊权限,不过这些权限使用得相对较少,不在此处讨论。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,不需要用户手动操作。危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须由用户手动授权才可以,否则程序就无法使用相应的功能。
但是Android 中一共有上百种权限,以下列出了到Android 10 系统为止所有的危险权限,一共是11 组30 个权限:
如果要使用的权限在这张表中,就需要进行运行时权限处理,否则,只需要在AndroidManifest.xml 文件中添加一下权限声明就可以了
另外注意,表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名。原则上,用户一旦同意了某个权限申请之后,同组的其他权限也会被系统自动授权。但是请谨记,不要基于此规则来实现任何功能逻辑,因为Android 系统随时有可能调整权限的分组。
2 在程序运行时申请权限
首先新建一个RuntimePermissionTest 项目,在这个项目的基础上学习运行时权限的使用方法。这里使用CALL_PHONE 这个权限来作为示例。
CALL_PHONE 这个权限是编写拨打电话功能的时候需要声明的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限。在Android 6.0 系统出现之前,拨打电话功能的实现其实非常简单,修改activity_main.xml 布局文件,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/makeCall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Make Call" />
</LinearLayout>
在布局文件中只是定义了一个按钮,点击按钮就去触发拨打电话的逻辑。接着修改MainActivity 中的代码,如下所示:
class MainActivity : AppCompatActivity() {
private lateinit var makeCall: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall = findViewById(R.id.makeCall)
makeCall.setOnClickListener {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
}
在按钮的点击事件中,构建了一个隐式Intent ,Intent 的action 指定为Intent.ACTION_CALL ,这是一个系统内置的打电话的动作,然后在data 部分指定了协议是tel ,号码是10086 ,Intent.ACTION_CALL 则表示直接拨打电话,因此必须声明权限。另外,为了防止程序崩溃, 将所有操作都放在了异常捕获代码块当中。
接下来修改AndroidManifest.xml 文件,在其中声明如下权限:
<uses-permission android:name="android.permission.CALL_PHONE" />
这样拨打电话的功能成功实现了,并且在低于Android 6.0 系统的手机上都是可以正常运行的。但是,如果我们在Android 6.0 或者更高版本系统的手机上运行,点击Make Call 按钮就没有任何效果了,还会看到如图所示的错误信息:
错误信息中提醒我们Permission Denial ,可以看出,这是由于权限被禁止所导致的,因为Android 6.0 及以上系统在使用危险权限时必须进行运行时权限处理。修改MainActivity 中的代码,如下所示:
class MainActivity : AppCompatActivity() {
private lateinit var makeCall: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall = findViewById(R.id.makeCall)
makeCall.setOnClickListener {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CALL_PHONE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
} else {
call()
}
}
}
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() {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
上面的代码覆盖了运行时权限的完整流程。运行时权限的核心就是在程序运行过程中由用户授权我们去执行某些危险操作,程序是不可以擅自做主去执行这些危险操作的。因此,第一步就是要先判断用户是不是已经给过我们授权了,借助的是ContextCompat.checkSelfPermission() 方法。checkSelfPermission() 方法接收两个参数:
- 第一个参数是
Context - 第二个参数是具体的权限名,比如打电话的权限名就是
Manifest.permission.CALL_PHONE 。然后使用方法的返回值和 PackageManager.PERMISSION_GRANTED 做比较,相等就说明用户已经授权,不等就表示用户没有授权。
如果已经授权的话就简单了,直接执行拨打电话的逻辑操作就可以了,这里我们把拨打电话的逻辑封装到了call() 方法当中。如果没有授权的话,则需要调用ActivityCompat.requestPermissions() 方法向用户申请授权。 requestPermissions() 方法接收3 个参数:
- 第一个参数要求是
Activity 的实例 - 第二个参数 是一个
String 数组,把要申请的权限名放在数组中即可 - 第三个参数是请求码,只要是唯 一值就可以了,这里传入了
1
调用完requestPermissions() 方法之后,系统会弹出一个权限申请的对话框,用户可以选 择同意或拒绝我们的权限申请。不论是哪种结果,最终都会回调到onRequestPermissionsResult() 方法中,而授权的结果则会封装在grantResults 参数当中。这里我们只需要判断一下最后的授权结果:如果用户同意的话,就调用call() 方法拨打电话;如果用户拒绝的话,我们只能放弃操作,并且弹出一条失败提示。
现在重新运行一下程序,并点击Make Call 按钮,效果如图所示:
由于用户还没有授权过我们拨打电话权限,因此第一次运行会弹出这样一个权限申请的对话 框,用户可以选择同意或者拒绝,比如说这里点击了Deny ,结果如图所示:
由于用户没有同意授权,我们只能弹出一个操作失败的提示。下面我们再次点击Make Call 按 钮,仍然会弹出权限申请的对话框,这次点击Allow ,结果如图所示:
可以看到,成功进入拨打电话界面了。并且由于用户已经完成了授权操作,之后再点击Make Call 按钮不会再次弹出权限申请对话框,而是可以直接拨打电话。另外,用户随时都可以将授予程序的危险权限进行关闭,进入Settings → Apps & notifications → 应用名 → Permissions , 界面如图所示:
在这里我们可以通过点击相应的权限来对授权过的危险权限进行关闭。
|