文章目录
- Android运行时权限详解
- Android权限机制详解
- 在程序运行时申请权限
Android运行时权限详解
Android的权限机制并不是什么新鲜事物,从系统的第一个版本开始就已经存在了。但其实之前Android的权限机制在保护用户安全和隐私等方面起到的作用比较有限,尤其是一些大家都离不开的常用软件,非常容易“店大欺客”。为此,Android开发团队在Android 6.0系统中引用了运行时权限这个功能,从而更好地保护了用户的安全和隐私,那么本节我们就来详细学习一下这个6.0系统中引入的新特性。
Android权限机制详解
首先来回顾一下过去Android的权限机制是什么样的。我们在第5章写BroadcastTest项目的时候第一次接触了Android权限相关的内容,当时为了要访问系统的网络状态以及监听开机广播,于是在AndroidManifest.xml文件中添加了这样两句权限声明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
</manifest>
因为访问系统的网络状态以及监听开机广播涉及了用户设备的安全性,因此必须在AndroidManifest.xml中加入权限声明,否则我们的程序就会崩溃。
那么现在问题来了,加入了这两句权限声明后,对于用户来说到底有什么影响呢?为什么这样就可以保护用户设备的安全性了呢?
其实用户主要在以下两个方面得到了保护,一方面,如果用户在低于6.0系统的设备上安装该程序,会在安装界面给出提醒。这样用户就可以清楚地知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。如图所示:
另一方面,用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况。这样该程序申请的所有权限就尽收眼底,什么都瞒不过用户的眼睛,以此保证应用程序不会出现各种滥用权限的情况。如图所示:
这种权限机制的设计思路其实非常简单,就是用户如果认可你所申请的权限,那么就会安装你的程序,如果不认可你所申请的权限,那么拒绝安装就可以了。
但是理想是美好的,现实却很残酷,因为很多我们所离不开的常用软件普遍存在着滥用权限的情况,不管到底用不用得到,反正先把权限申请了再说。比如说微信所申请的权限列表如图所示:
这只是微信所申请的一半左右的权限,因为权限太多一屏截不下来。其中有一些权限我并不认可,比如微信为什么要读取我手机的短信和彩信?但是我不认可又能怎样,难道我拒绝安装微信?没错,这种例子比比皆是,当一些软件已经让我们产生依赖的时候就会容易 “店大欺客”,
反正这个权限我就是要了,你自己看着办吧!
Android开发团队当然也意识到了这个问题,于是在6.0系统中加入了运行时权限功能。也就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。比如说一款相机应用在运行时申请了地理位置定位权限,就算我拒绝了这个权限,但是我应该仍然可以使用这个应用的其他功能,而不是像之前那样直接无法安装它。
当然,并不是所有权限都需要在运行时申请,对于用户来说,不停地授权也很烦琐。Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。准确地讲,其实还有第三类特殊权限,不过这种权限使用得很少,因此不在本书的讨论范围之内。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了,比如在BroadcastTest项目中申请的两个权限就是普通权限。危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。但是Android中有一共有上百种权限,我们怎么从中区分哪些是普通权限,哪些是危险权限呢?
其实并没有那么难,因为危险权限总共就那么几个,除了危险权限之外,剩余的就都是普通权限了。下表列出了Android中所有的危险权限,一共是9组24个权限。
这张表格你看起来可能并不会那么轻松,因为里面的权限全都是你没使用过的。不过没有关系,你并不需要了解表格中每个权限的作用,只要把它当成一个参照表来查看就行了。每当要使用一个权限时,可以先到这张表中来查一下,如果是属于这张表中的权限,那么就需要进行运行时权限处理,如果不在这张表中,那么只需要在AndroidManifest.xml文件中添加一下权限声明就可以了。
另外注意一下,表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限所对应的权限组中所有的其他权限也会同时被授权。
在程序运行时申请权限
首先新建一个RuntimePermissionTest项目,我们就在这个项目的基础上来学习运行时权限的使用方法。在开始动手之前还需要考虑一下到底要申请什么权限,其实刚才表中列出的所有权限都是可以申请的,这里简单起见我们就使用CALL_PHONE这个权限来作为本小节中的示例吧。
CALL_PHONE这个权限是编写拨打电话功能的时候需要声明的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限。在Android 6.0系统出现之前,拨打电话功能的实现其实非常简单,修改activity_main.xml布局文件,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/make_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Make Call" />
</LinearLayout>
我们在布局文件中只是定义了一个按钮,当点击按钮时就去触发拨打电话的逻辑。接着修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
});
}
}
可以看到,在按钮的点击事件中,我们构建了一个隐式Intent,Intent的action指定为Intent.ACTION_CALL,这是一个系统内置的打电话的动作,然后在data部分指定了协议是tel,号码是10086。其实这部分代码我们在2.3.3小节中就已经见过了,只不过当时指定的action是Intent.ACTION_DIAL,表示打开拨号界面,这个是不需要声明权限的,
而Intent.ACTION_CALL则可以直接拨打电话,因此必须声明权限。另外为了防止程序崩溃,我们将所有操作都放在了异常捕获代码块当中。
那么接下来修改AndroidManifest.xml文件,在其中声明如下权限:
<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:supportsRtl="true"
android:theme="@style/AppTheme">
...
</application>
</manifest>
这样我们就将拨打电话的功能成功实现了,并且在低于Android 6.0系统的手机上都是可以正常运行的,但是如果我们在6.0或者更高版本系统的手机上运行,点击Make Call按钮就没有任何效果,这时观察logcat中的打印日志,你会看到如图所示的错误信息:
错误信息中提醒我们“Permission Denial”,可以看出,是由于权限被禁止所导致的,因为6.0及以上系统在使用危险权限时都必须进行运行时权限处理。
那么下面我们就来尝试修复这个问题,修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@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();
}
}
});
}
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.
PERMISSION_GRANTED) {
call();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_
SHORT).show();
}
break;
default:
}
}
}
上面的代码将运行时权限的完整流程都覆盖了,下面我们来具体解析一下。说白了,运行时权限的核心就是在程序运行过程中由用户授权我们去执行某些危险操作,程序是不可以擅自做主去执行这些危险操作的。因此,第一步就是要先判断用户是不是已经给过我们授权了,借助的是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 → RuntimePermissionTest → Permissions,界面如图所示:
在这里我们就可以对任何授予过的危险权限进行关闭了。好了,关于运行时权限的内容就讲到这里,现在你已经有能力处理Android上各种关于权限的问题了。
如果对你有帮助,就一键三连呗(点赞+收藏+关注),我会持续更新更多干货~~