在Android开发的过程中我们少不了要与权限打交道,在这过程中我们自然会想:为什么有些操作需要权限,有些操作不需要呢?系统是怎么配置操作所需要的权限呢?

带着这些问题,我们来分析一个常见的操作——打电话。

事例代码如下:

CallActivity.java:

public class CallActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.call).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                call();
            }
        });
    }

    private void call() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.CALL_PHONE}, 1);
        } else {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults != null && grantResults.length > 0) {
            switch (requestCode) {
                case 1:
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        call();
                    }
                    break;

                default:
                    break;
            }
        }
    }
}

Activity对应的布局很简单,只有一个button,就不贴出来了。

然后还需要在AndroidManifest.xml中声明打电话的权限:

<uses-permission android:name="android.permission.CALL_PHONE"/>

如果不在AndroidManifest中声明相应的权限以及在6.0或以上系统中不通过运行时权限机制动态获取权限的话,调用打电话的方法就会报错,错误类似于:

01-01 08:12:24.974  7299  7299 E AndroidRuntime: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{941f69d 7299:com.android.mikechenmj.custompermission/u0a104} (pid=7299, uid=10104) with revoked permission android.permission.CALL_PHONE

那么系统是怎么给打电话这一操作配置权限的呢?我们通过系统源码来进行简单分析:

接收Intent.ACTION_CALL这一个Action的Activity在AndroidManifest中的定义:

<activity 
                android:name=".components.UserCallActivity"
                android:label="@string/userCallActivityLabel"
                android:theme="@style/Theme.Telecomm.Transparent"
                android:configChanges="orientation|screenSize|keyboardHidden|mcc|mnc"
                android:permission="android.permission.CALL_PHONE"
                android:excludeFromRecents="true"
                android:process=":ui">
            <!-- CALL action intent filters for the various ways of initiating an outgoing call. -->
            <intent-filter>
                <action android:name="android.intent.action.CALL" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="tel" />
            </intent-filter>
            <!-- Specify an icon for SIP calls so that quick contacts widget shows a special SIP
                 icon for calls to SIP addresses. -->
            <intent-filter android:icon="@drawable/ic_launcher_sip_call">
                <action android:name="android.intent.action.CALL" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="sip" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.CALL" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="voicemail" />
            </intent-filter>
            <!-- Omit default category below so that all Intents sent to this filter must be
                 explicit. -->
            <intent-filter>
                <action android:name="android.intent.action.CALL" />
                <data android:mimeType="vnd.android.cursor.item/phone" />
                <data android:mimeType="vnd.android.cursor.item/phone_v2" />
                <data android:mimeType="vnd.android.cursor.item/person" />
            </intent-filter>
        </activity>

上面的代码在第6行上通过android:permission标记给Activity配置了所需的权限:
android:permission="android.permission.CALL_PHONE"
正是因为这句话,我们调用打电话功能的之前必须先去获取权限。

然后我们再去看下CALL_PHONE这个权限是如何被定义的,我们查看frameworks\base\core\res\AndroidManifest.xml这个文件,在这个文件中有如下代码:

<!-- Allows an application to initiate a phone call without going through
        the Dialer user interface for the user to confirm the call.
        <p>Protection level: dangerous
    -->
    <permission android:name="android.permission.CALL_PHONE"
        android:permissionGroup="android.permission-group.PHONE"
        android:permissionFlags="costsMoney"
        android:label="@string/permlab_callPhone"
        android:description="@string/permdesc_callPhone"
        android:protectionLevel="dangerous" />

在这里面定义了CALL_PHONE这个权限的一系列属性,包括:权限的名称、权限所属的权限组,权限的标记,标题、描述和权限的等级。

接下来对这几个属性进行简单的说明:

android:permissionGroup

这个属性用于指定权限所属的权限组,在上例中的android.permission-group.PHONE权限组的定义如下:

<permission-group android:name="android.permission-group.PHONE"
        android:icon="@drawable/perm_group_phone_calls"
        android:label="@string/permgrouplab_phone"
        android:description="@string/permgroupdesc_phone"
        android:priority="500" />

在此处也类型地定义了一部分属性,且这部分属性会应用到该权限组的权限当中。对于权限组我们需要注意的一点是:如果我们已经成功获取了权限组中的其中一个权限,那么系统会允许我们获取该权限组的其它保护等级相同或者更低的权限。

比如我们在6.0以上系统通过运行时权限获取到了android.permission.CALL_PHONE这个权限,那么同属于同一组的权限android.permission.READ_PHONE_STATE 就不需要再通过运行时权限机制获取了。

permissionFlags:

给该权限设置对应的标志,在此处的标志是costsMoney,代表获取该权限有可能会造成费用。

label:

给该权限设置标题。

description:

给该权限设置描述。

priority:

指定了该权限的优先等级。

icon:

给该权限设置图标。

protectionLevel:

指定了该权限的保护等级。保护等级主要有:normal、dangerous、signature、signatureOrSystem四种。

normal是等级最低的,代表该权限没什么危险,只要申请了就能获取。

dangerous代表该权限是有危险的,用户需要谨慎获取。这个等级与normal最明显的区别在于——dangerous的权限在6.0及以上的系统是需要通过运行时权限动态获取的。

signature代表只有带有同样的数字签名的APK才能获取,一般用于公司内部不同APK的交互调用。

signatureOrSystem在signature的基础上允许了系统应用对该权限的获取。


大致分析完毕,那么我们也来试着动手自定义权限吧,为了演示方便,我就只在6.0系统上运行。

我们先新建一个Module,叫做PermissionTarget,在这里定义几个需要获取了自定义权限之后才能启动的Activity。

在AndroidManifest中:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.android.mikechenmj.premissiontarget"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <permission
        android:name="mikechenmj.permission.DANGEROUS_TEST"
        android:description="@string/dangerous_description"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/dangerous_label"
        android:permissionFlags="costsMoney"
        android:protectionLevel="dangerous"/>

    <permission
        android:name="mikechenmj.permission.NORMAL_TEST"
        android:description="@string/normal_description"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/normal_label"
        android:protectionLevel="normal"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".DangerousActivity"
                  android:permission="mikechenmj.permission.DANGEROUS_TEST">
            <intent-filter>
                <action android:name="mikechenmj.intent.action.DANGEROUS_TEST"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity android:name=".NormalActivity"
                  android:permission="mikechenmj.permission.NORMAL_TEST">
            <intent-filter>
                <action android:name="mikechenmj.intent.action.NORMAL_TEST"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

在这第5行和第13行分别定义了两个自定义权限,并各种配了相应的属性。其中mikechenmj.permission.DANGEROUS_TEST 的protectionLevel是dangerous的,
mikechenmj.permission.NORMAL_TEST 的protectionLevel是normal。

然后在28行和41行通过标签android:permission给两个不同的Activity指定启动所需的权限。

接下来我们再创建一个Module,名叫PermissionRequest,我们要在这个Module中获取权限并启动PermissionTarget的Activity。

首先在AndroidManifest中声明要用到的权限:

<uses-permission android:name="mikechenmj.permission.DANGEROUS_TEST"/>
    <uses-permission android:name="mikechenmj.permission.NORMAL_TEST"/>

这两个权限是在PermissionTarget的AndroidManifest中定义的。

然后在PermissionRequest的主活动中定义3个按钮,分别用于启动需要Normal级别权限的NormalActivity、在不申请运行时权限的情况下启动需要Dangerous级别权限的DangerousActivity以及在申请了运行时权限后启动DangerousActivity。然后还添加了一个TextView用于打印错误信息。

代码如下

public class MainActivity extends AppCompatActivity {

    private static final String DANGEROUS_ACTION = "mikechenmj.intent.action.DANGEROUS_TEST";

    private static final String NORMAL_ACTION = "mikechenmj.intent.action.NORMAL_TEST";

    private static final String DANGEROUS_PERMISSION = "mikechenmj.permission.DANGEROUS_TEST";

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.message);
    }

    public void onClick(View v) {
        switch (v.getId()) {

            case R.id.normal:
                try {
                    startActivity(new Intent(NORMAL_ACTION));
                } catch (Exception e) {
                    e.printStackTrace();
                    mTextView.setText(e.getMessage());
                }
                break;

            case R.id.dangerous:
                try {
                    startActivity(new Intent(DANGEROUS_ACTION));
                } catch (Exception e) {
                    e.printStackTrace();
                    mTextView.setText(e.getMessage());
                }
                break;

            case R.id.dangerous_with_permission:
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if (ActivityCompat.checkSelfPermission(this,DANGEROUS_PERMISSION)
                                != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(this,new String[]
                                    {DANGEROUS_PERMISSION},1);
                        }else {
                            startActivity(new Intent(DANGEROUS_ACTION));
                        }
                    }else {
                        startActivity(new Intent(DANGEROUS_ACTION));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    mTextView.setText(e.getMessage());
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults != null && grantResults.length >=0) {
            switch (requestCode) {
                case 1:
                    if (grantResults[0]==PackageManager.PERMISSION_GRANTED) {
                        startActivity(new Intent(DANGEROUS_ACTION));
                    }
            }
        }
    }
}

当点击id为R.id.normal按钮的时候会正常启动NormalActivity,这是因为我们已经在PermissionRequest的AndroidManifest中定义了启动所需的权限,并且该权限只是normal级别的。

当点击id为R.id.dangerous按钮的时候正常情况下APP会直接报错,这是因为启动DangerousActivity所需的权限的级别是Dangerous的,在6.0上还需要用户手动给予,即通过运行时权限获取手动在设置中授予。在事例代码中为了方便演示,我用一个try catch语句把报错给catch住了,并把报错内容输出到一个TextView上。

当点击id为R.id.dangerous_with_permission时,程序会先去申请运行时权限,在获取到权限后将正常启动DangerousActivity。

Android adb设备串口名称 android串口怎么给权限_permission

我们可以注意到,运行时权限框的icon就是权限中配置的权限的icon,而“获取危险权限测试功能”这段文字则是权限的description。

流程总结起来:

  1. 先在需要自定义权限的应用的AndroidManifest中定义所需的权限,然后在想要设置权限的activity中通过标签android:permission配置。
  2. 在需要申请权限的应用的AndroidManifest中通过<use-permission 标签声明所需的权限,然后根据该权限的保护等级作出相应的操作,比如通过运行时权限机制获取。

与Activity类似的,自定义权限也能用于广播与服务上,方式也类似,在这里就不展开了。