Android的权限机制并不是什么新鲜事物,从系统的第一个版本开始就已经存在了。但其实之前Android的权限机制在保护用户安全和隐私等方面起到的作用比较有限,尤其是一些大家都离不开的常用软件,非常容易“店大欺客”。为此,Android 开发团队在Android6.0系统中引用了运行时权限这个功能,从而更好地保护了用户的安全和隐私,那么本节我们就来详细学习一下这个6.0系统中引入的新特性。
8.2.1 Android 权限机制详解
首先来回顾一下过去Android的权限机制是什么样的。我们在第6章写BroadcastTest项目的时候第一次接触了Android权限相关的内容,当时为了要访问系统的网络状态以及监听开机广播,于是在AndroidManifest.xml文件中添加了这样两句权限声明:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
...
</manifest>
因为监听开机广播设计了用户设备的安全,因此必须在AndroidMainfest.xml 中加入权限声明,否则我们的程序就会崩溃。
那么现在问题来了,加入了这两句权限声明后,对于用户来说到底有什么影响呢?为什么这样就可以保护用户设备的安全性了呢?
其实用户主要在两个方面得到了保护。一方面,如果用户在低于Android6.0 系统的设备上安装该程序,会在安装界面给出需要用到的权限提示列表。这样用户就可以清楚地知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。
另一方面,用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况。这样该程序申请的所有权限就尽收眼底,什么都瞒不过用户的眼睛,以此保证应用程序不会出现各种滥用权限的情况。这种权限机制的设计思路其实非常简单,就是用户如果认可你所申请的权限,那么就会安装你的程序,如果不认可你所申请的权限,那么拒绝安装就可以了。
但是理想是美好的,现实却很残酷,因为很多我们所离不开的常用软件普遍存在着滥用权限的情况,不管到底用不用得到,反正先把权限申请了再说。比如微信为什么要读取我手机的短信和彩信?但是我不认可又能怎样,难道我拒绝安装微信?没错,这种例子比比皆是,当一些软件已经让我们产生依赖的时候就会容易“店大欺客”,反正这个权限我就是要了,你自己看着办吧!
Android 开发团队当然也意识到了这个问题,于是在6.0系统中加入了运行时权限功能。也就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。比如说一款相机应用在运行时申请了地理位置定位权限,就算我拒绝了这个权限,但是我应该仍然可以使用这个应用的其他功能,而不是像之前那样直接无法安装它。
当然,并不是所有权限都需要在运行时申请,对于用户来说,不停地授权也很烦琐。Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了,比如在BroadcastTest项目中申请的两个权限就是普通权限。危险权限则表示那些可能会触及用户隐私,或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
但是Android中有一共有上百种权限,我们怎么从中区分哪些是普通权限,哪些是危险权限呢?其实并没有那么难,因为危险权限总共就那么几个,除了危险权限之外,剩余的就都是普通权限了。下表列出了Android中所有的危险权限,一共11组30个权限。
这张表格你看起来可能并不会那么轻松,因为里面的权限全都是你没使用过的。不过没有关系,你并不需要了解表格中每个权限的作用,只要把它当成一个参照表来查看就行了。每当要使用一个权限时,可以先到这张表中来查一下,如果是属于这张表中的权限,那么就需要进行运行时权限处理,如果不在这张表中,那么只需要在AndroidManifest.xml文件中添加一下权限声明就可以了。
另外注意,表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限所对应的权限组中所有的其他权限也会同时被授权。
访问http://developer.android.com/reference/android/Manifest.permission.html 可以查看Android系统中完整的权限列表。
8.2.2 在程序运行时申请权限
首先新建一个RuntimePermissionTest项目,我们就在这个项目的基础上来学习运行时权限的使用方法。在开始动手之前还需要考虑一下到底要申请什么权限,其实刚才表中列出的所有权限都是可以申请的,这里简单起见我们就使用CALLPHONE这个权限来作为本小节中的示例吧。
CALL PHONE 这个权限是编写拨打电话功能的时候需要声明的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限。在Android6.0系统出现之前,拨打电话功能的实现其实非常简单,修改activity main.xml布局文件,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
// 为了不让程序崩溃,我们抓一下错误
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
}catch (e:Exception){
e.printStackTrace()
}
}
}
}
之前我们做过一次拨号的Intent 调用,但是那次是打开拨号界面的Action,本次的是直接拨打电话,所以在上面的列表中,拨打电话属于危险权限操作,必须要声明权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.runtimepermissiontest">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
这样拨打电话功能就可以成功实现了,并且在低于Android 6.0 系统的手机上都是可以正常运行的。但是,如果我们在Android 6.0 或更高版本系统的手机上运行,点击 “Make Call”按钮就没有任何效果了,因为我们抓了报错所以程序不会崩溃,我们可以看日志会发现提示的错误信息:
“Permission Denial”,由于权限被禁止导致的,因为Android6.0 及以上系统在使用危险权限时必须进行运行时权限处理。
接下来我们来修复这个问题,修改MainActivity 的代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) !=
PackageManager.PERMISSION_GRANTED){
// 申请权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE),1)
}else{
call()
}
}
}
// 权限申请回调 ActivityCompat.requestPermissions 这个方法调用后的回调方法。
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_LONG).show()
}
}
}
}
private fun call(){
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
}
}
上面的代码将运行时权限的完整流程都覆盖了,下面我们来具体解析一下。说白了,运行时权限的核心就是在程序运行过程中由用户授权我们去执行某些危险操作,程序是不可以擅自做主去执行这些危险操作的。因此,第一步就是要先判断用户是不是已经给过我们授权了,借助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接收两个参数,第一个参数是Context,这个没什么好说的,第二个参数是具体的权限名,比如打电话的权限名就是Manifest.permission.CALL_PHONE,然后我们使用方法的返回值和PackageManager.PERMISSION GRANTED做比较,相等就说明用户已经授权,不等就表示用户没有授权。
如果已经授权的话就简单了,直接去执行拨打电话的逻辑操作就可以了,这里我们把拨打电话的逻辑封装到了call()方法当中。如果没有授权的话,则需要调用ActivityCompat.
requestPermissions()方法来向用户申请授权,requestPermissions()方法接收3个参数,第一个参数要求是Activity的实例,第二个参数是一个String数组,我们把要申请的权限名放在数组中即可,第三个参数是请求码,只要是唯一值就可以了,这里传入了1。
调用完了requestPermissions()方法之后,系统会弹出一个权限申请的对话框,然后用户可以选择同意或拒绝我们的权限申请,不论是哪种结果,最终都会回调到onRequest-
PermissionsResult()方法中,而授权的结果则会封装在grantResults参数当中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就调用call()方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并且弹出一条失败提示。
现在重新运行一下程序,并点击Make Call按钮:
由于用户还没有授权过我们拨打电话权限,因此第一次运行会弹出这样一个权限申请的对话框,用户可以选择同意或者拒绝,比如说这里点击了DENY,就会弹出 You denied the permission ,点击Allow 就会直接拨打电话。
可以看到,这次我们就成功进入到拨打电话界面了,并且由于用户已经完成了授权操作,之后再点击Make Call按钮就不会再弹出权限申请对话框了,而是可以直接拨打电话。那可能你会担心,万一以后我又后悔了怎么办?没有关系,用户随时都可以将授予程序的危险权限进行关闭,商进入Settings→Apps→RuntimePermission Test→Permissions。