Android--权限管理

来源:互联网 发布:都怪本泽马什么梗 知乎 编辑:程序博客网 时间:2024/06/07 02:52

权限机制

对于6.0以下的权限在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要忍受其一些不必要的权限。

Android 6.0推出了新的权限机制,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝。当然我们也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。

Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等,新的权限机制更好的保护了用户的隐私。

Normal Permission:

ACCESS_LOCATION_EXTRA_COMMANDSACCESS_NETWORK_STATEACCESS_NOTIFICATION_POLICYACCESS_WIFI_STATEBLUETOOTHBLUETOOTH_ADMINBROADCAST_STICKYCHANGE_NETWORK_STATECHANGE_WIFI_MULTICAST_STATECHANGE_WIFI_STATEDISABLE_KEYGUARDEXPAND_STATUS_BARGET_PACKAGE_SIZEINSTALL_SHORTCUTINTERNETKILL_BACKGROUND_PROCESSESMODIFY_AUDIO_SETTINGSNFCREAD_SYNC_SETTINGSREAD_SYNC_STATSRECEIVE_BOOT_COMPLETEDREORDER_TASKSREQUEST_INSTALL_PACKAGESSET_ALARMSET_TIME_ZONESET_WALLPAPERSET_WALLPAPER_HINTSTRANSMIT_IRUNINSTALL_SHORTCUTUSE_FINGERPRINTVIBRATEWAKE_LOCKWRITE_SYNC_SETTINGS

我们可以用 adb 命令查看危险的权限,Dangerous Permission:

adb shell pm list permissions -d -gDangerous Permissions:group:android.permission-group.CONTACTS  permission:android.permission.WRITE_CONTACTS  permission:android.permission.GET_ACCOUNTS  permission:android.permission.READ_CONTACTSgroup: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_VOICEgroup:android.permission-group.CALENDAR  permission:android.permission.READ_CALENDAR  permission:android.permission.WRITE_CALENDARgroup:android.permission-group.CAMERA  permission:android.permission.CAMERAgroup:android.permission-group.SENSORS  permission:android.permission.BODY_SENSORSgroup:android.permission-group.SUPERUSER  permission:android.permission.ACCESS_SUPERUSERgroup:android.permission-group.LOCATION  permission:android.permission.ACCESS_FINE_LOCATION  permission:android.permission.ACCESS_COARSE_LOCATIONgroup:android.permission-group.STORAGE  permission:android.permission.READ_EXTERNAL_STORAGE  permission:android.permission.WRITE_EXTERNAL_STORAGEgroup:android.permission-group.MICROPHONE  permission:android.permission.RECORD_AUDIOgroup: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_BROADCASTSungrouped:

看到上面的dangerous permissions,我们会发现危险权限都是以组来分的。那么分组会有什么影响。

如果app运行在Android 6.0 或以上的机器上,对于授权机制是这样的。

如果你申请某个危险的权限,假设你的app早已被用户授权了同一组的某个危险权限,那么系统会立即授权,而不需要用户去点击授权。比如你的app对READ_CONTACTS已经授权了,当你的app申请WRITE_CONTACTS时,系统会直接授权通过。

此外,对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是单个权限(这个dialog是不能进行定制的)。

不过需要注意的是,不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。

API处理

新增的 API 有如下几个:

ContextCompat.checkSelfPermissionActivityCompat.requestPermissions()onRequestPermissionsResult()ActivityCompat.shouldShowRequestPermissionRationale

checkSelfPermission 是检查我们是否已申请了某个权限,如果没有申请就调用 requestPermissions() 来进行权限申请。另外因为 ActivityCompat 继承了 ContextCompat,所以我们都用 ActivityCompat 也是可以的。

因为申请权限是个异步的过程,所以我们要在一个回调里去处理结果,就是 onRequestPermissionsResult(),我们在这里拿到用户是否通过了授权,通过就去做通过的处理,失败就做提示处理。

shouldShowRequestPermissionRationale 是告诉用户这个权限是干什么的,只有拒绝授予权限之后再弹出权限框才会出现,你需要给用户一个解释。

使用流程:

  1. 在AndroidManifest文件中添加需要的权限。

    这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。

  2. 检查权限

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)      != PackageManager.PERMISSION_GRANTED) {}else{  //}

    checkSelfPermission 方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。

  3. 权限申请

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)      != PackageManager.PERMISSION_GRANTED) {      ActivityCompat.requestPermissions(this,            new String[]{Manifest.permission.READ_CONTACTS},            MY_PERMISSIONS_REQUEST_READ_CONTACTS);}else{  //}

    该方法是异步的,第一个参数是Context;第二个参数是需要申请的权限的字符串数组;第三个参数为requestCode,主要用于回调的时候检测。可以从方法名requestPermissions以及第二个参数看出,是支持一次性申请多个权限的,系统会通过对话框逐一询问用户是否授权。

  4. 权限申请回调

    @Overridepublic void onRequestPermissionsResult(int requestCode,    String permissions[], int[] grantResults) {    switch (requestCode) {    case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {        // 如果请求被拒绝了,这个结果数组是空的        if (grantResults.length > 0            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {            // 同意了,做你需要做的与该权限相关的任务。        } else {            //许可拒绝,禁用依赖于此权限的功能。        }        return;    }}}

    对于权限的申请结果,首先验证 requestCode 定位到我们的申请,然后验证 grantResults 对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。如果我们同时申请两个权限,那么 grantResults 的 length 就为2,分别记录我们两个权限的申请结果。如果申请成功,就可以做我们的事情了。

步骤就是这样了,为了用户体验,我们还要把 shouldShowRequestPermissionRationale 向用户解释加进去。

if (ContextCompat.checkSelfPermission(this,                Manifest.permission.READ_CONTACTS)        != PackageManager.PERMISSION_GRANTED) {    // 我们是否应该显示解释    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,            Manifest.permission.READ_CONTACTS)) {        // 给个解释给用户,不异步阻塞这个线程等待用户的响应!用户看到说明后,再试一次请求权限。    } else {        // 不需要解释,我们可以请求许可。        ActivityCompat.requestPermissions(thisActivity,                new String[]{Manifest.permission.READ_CONTACTS},                MY_PERMISSIONS_REQUEST_READ_CONTACTS);        // MY_PERMISSIONS_REQUEST_READ_CONTACTS 是一个我们定义的int常数。回调方法获取请求的结果,我们可以给它赋值为0为1都可以。    }}

封装

虽然权限申请并不复杂,但是重复的代码太多,我们对权限的操作并不是这个我们要开发 app 的要点,如果把它混杂在我们的 MainActivity 里会影响代码的可读性。所以在对权限的操作比较多的时候,我们最好对其进行封装。

原本我们的申请代码是这样的:

if (ContextCompat.checkSelfPermission(this,                Manifest.permission.CALL_PHONE)                != PackageManager.PERMISSION_GRANTED){    ActivityCompat.requestPermissions(this,            new String[]{Manifest.permission.CALL_PHONE},            MY_PERMISSIONS_REQUEST_CALL_PHONE);} else{    //}

可以看到不同权限的逻辑是相同的,不同的只是参数。通过上面的代码,我们可以知道需要的是这三个参数:

  • Activity | Fragment
  • permissions(权限字符串数组)
  • requestCode(int型申请码)

所以我们只要写个方法来接收这三个参数即可,所有权限逻辑都相同。通常我们都会把公共的方法都写在一个 BaseActivity 里,然后让我们的 MainActivity 去继承它,在这里我也这样去做。

BaseActivity.java:

public class BaseActivity extends AppCompatActivity {    protected void isPermissionGranted(String permission, int requestCode) {        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){            return ;        }        //判断是否需要请求允许权限        if (hasPermission(new String[]{permission})) {            requestPermissions(new String[]{permission}, requestCode);        }    }    protected void isPermissionAllGranted(String[] permissions, int requestCode) {        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){            return ;        }        //获得批量请求但被禁止的权限列表        List<String> deniedPerms = new ArrayList<String>();        for(int i = 0; permissions != null && i < permissions.length;i++){            if (!hasPermission(new String[]{permissions[i]})) {                deniedPerms.add(permissions[i]);            }        }        //进行批量请求        int denyPermNum = deniedPerms.size();        if(denyPermNum != 0){            requestPermissions(deniedPerms.toArray(new String[denyPermNum]), requestCode);        }    }    /**     * 为子类提供一个权限检查方法     * @param permissions     * @return     */    public boolean hasPermission(String[] permissions) {        for (String permission: permissions) {            if (ContextCompat.checkSelfPermission(this, permission)                    != PackageManager.PERMISSION_GRANTED) {                return false;            }        }        return true;    }    /**     * 为子类提供一个权限请求方法     * @param requestCode     * @param permissions     */    public void requestPermission(int requestCode, String[] permissions) {        ActivityCompat.requestPermissions(this, permissions, requestCode);    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        if(grantResults.length == 0){            return;        }        switch (requestCode) {            case 1 :                if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {                    Toast.makeText(this, "未授予拨打电话权限", Toast.LENGTH_SHORT).show();                }else{                    doCallPhone();                }                break;            case 2 :                if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {                    Toast.makeText(this, "未授予读取SD卡权限", Toast.LENGTH_SHORT).show();                }else{                    Toast.makeText(this, "用户已经授予读取SD卡权限", Toast.LENGTH_SHORT).show();                }                break;            case 3 :                for (int i = 0; i < grantResults.length; i++) {                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {                        Toast.makeText(this, "有权限未授予,部分功能不能使用", Toast.LENGTH_SHORT).show();                        break;                    } else if (i == grantResults.length - 1){                        Toast.makeText(this, "用户已经授予全部权限", Toast.LENGTH_SHORT).show();                    }                }                break;        }        super.onRequestPermissionsResult(requestCode, permissions, grantResults);    }    private void doCallPhone() {        Intent intent = new Intent(Intent.ACTION_CALL);        Uri data = Uri.parse("tel:" + "10086");        intent.setData(data);        startActivity(intent);    }}

MainActivity.java:

public class MainActivity extends BaseActivity {    private Button mButton;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        isPermissionAllGranted(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 3);        mButton = (Button) findViewById(R.id.setting);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //前往应用详情界面                try {                    Uri packUri = Uri.parse("package:"+getPackageName());                    Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,packUri);                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                    startActivity(intent);                } catch (Exception e) {                }            }        });    }}

这里我做了一个小例子,申请了读取SD卡和拨打电话两个权限,可做批量申请,并且回调方法可进入拨号界面,如果 API < 23就不做申请,因为是6.0以下。因为这是举例,我没有创建常量类,拨打电话的 requestCode 是1,SD卡是2,批量是3。代码很简单,稍注意的是在批量申请时有 List 集合记录未申请的权限。

界面上有一个 Button,点击可进入系统的设置,可手动更改权限。

如果是 Fragment,我们只要用getActivity()方法获得 Activity 对象,通过这个对象调用checkSelfPermission相关方法就可以啦。

   public Activity getActivity(Object object) {        if (object instanceof Activity) {            return (Activity) object;        } else if (object instanceof Fragment) {            return ((Fragment) object).getActivity();        }        return null;    }

结束语:本文仅用来学习记录,参考查阅。

4 0
原创粉丝点击