Runtime Permissions(郭霖CSDN公开课)

运行时权限

Api23开始,Android权限机制更改,有一部分权限不再是简单的在AndroidManifest.xml中声明即可。而是需要在运行时让用户去选择是否允许该项权限的操作。

那么哪些权限需要在运行时申请呢?危险权限需要这么做,而普通权限仍然和以前一样。具体的分类可以看之前的文章Runtime Permissions

普通权限一半时不会威胁安全、隐私;而危险权限一般涉及用户隐私,设备安全问题。

AndroidManifest声明权限

无论是危险权限还是普通权限都必须在AndroidManifest.xml中声明,这一步的作用是什么?主要有两方面:

  1. 程序安装时告知用户需要的权限,由用户决定是否安装。
  2. 在设置的应用选项中,点击应用查看信息可以看到应用获取的权限。由用户决定是否保留应用。

在build.gradle(app)中targetSdkVersion的值低于23时,应用运行在Android6.0及以上系统时,会默认打开在AndroidManifest.xml声明的权限。

如果升级应用,修改了targetSdkVersion为23及以上,再次运行时,依旧默认允许所有AndroidManifest.xml中所用声明的权限。

危险权限导致Crash

如果在Android6.0及以上运行程序执行危险权限相关操作,没有运行时检查权限获取情况,如果没有获取会导致程序crash。例如Console输出:Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{cae02cf 30814:android.example.com.permissionusage/u0a154} (pid=30814, uid=10154) with revoked permission android.permission.CALL_PHONE

有时候需要对执行危险权限操作进行封装,例如打电话操作:

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel://1234567890"));
startActivity(intent);

直接写成一个方法编译器会报错,即便不去处理也可以运行。可以通过捕获上面Console输出的异常来解决编译器报错:

    private void makeCall() {
        try{
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel://1234567890"));
            startActivity(intent);
        }catch (SecurityException e) {
            e.printStackTrace();
        }
    }

运行时权限基础写法

单个运行时权限申请

    /**
     * 单个权限授权
     * @param view
     */
    public void btnClick(View view) {
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    this, new String[]{Manifest.permission.CALL_PHONE}, CALL_REQUEST);
        }else {
            makeCall();
        }
    }

权限申请回调

public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch(requestCode) {
            case CALL_REQUEST:
                if(grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    makeCall();
                }else {
                    Snackbar.make(mContainer, "权限被拒绝了", Snackbar.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

其实当grantResults数组长度为0时,程序某个地方一定出现问题。

多个运行时权限申请

    /**
     * 多个权限同时授权
     * @param v
     */
    public void btnMorePermissions(View v) {
        List permissions = new ArrayList<>();
        //安全权限,无需运行时检查
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE)
                != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.ACCESS_NETWORK_STATE);
        }
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.CALL_PHONE);
        }
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if(!permissions.isEmpty()) {
            ActivityCompat.requestPermissions(
                    this,
                    permissions.toArray(new String[permissions.size()]),
                    MORE_PERMISSIONS_REQUEST);
        }else {
            doSomething();
        }
    }

权限申请回调

public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch(requestCode) {
            case MORE_PERMISSIONS_REQUEST:
                if(grantResults.length > 0) {
                    for(int i : grantResults) {
                        if(i != PackageManager.PERMISSION_GRANTED) {
                            Snackbar.make(mContainer, "某个权限没有授权", Snackbar.LENGTH_SHORT).show();
                            return;
                        }
                    }
                    doSomething();
                }else {

                }
            default:
                break;
        }
    }

为什么读写外部存储属于危险权限

在Android6.0以前,读写外部存储只需要在AndroidManifest.xml中声明即可,但是由于应用的强制行为,导致用户的外部存储中文件杂乱,权限被滥用。所以将其制定为危险权限。

在外部存储目录Android/data/packgae_name属于应用私有的目录,不需要运行时申请读写外部存储危险权限,甚至不需要在AndroidManifest.xml中声明就可以读写。应用可以随意支配自身的文件存储(cache目录经常会被清理软件清理,主要文件放入files目录下)。

这样既保证了外部存储目录的整洁,又不会给应用开发者带来不必要的麻烦。具体操作可以看文章Android数据存储之File总结。而且应用卸载后,相应包名的目录也会删除。

封装

由于运行时权限申请的繁琐,所以封装饰必须的。但是申请权限的操作必须建立在Activity之上。只有在Activity中才可以弹出申请权限对话框。而且申请回调函数属于Activity的方法,所以申请权限的操作和Activity藕合度非常高。可以通过以下办法:

  1. 自定义一个PermissionActivity,专门用于处理申请运行时权限操作。该Activity背景透明,用户无法察觉。执行完后finish掉。
  2. 参照RxPermissions第三方库的实现。
  3. 创建一个BaseActivity去实现运行时权限申请方法,然后所有Activity继承BaseActivity,需要时调用方法即可。BaseActivity对于一个项目可以提高Activity类的扩展性,在里面实现自己的方法供子类使用。

BaseActivity

public class BaseActivity extends AppCompatActivity{

    private static final int REQUEST_CODE = 1;
    private PermissionListener mListener;

    public void requestRuntimePermissions(String[] permissions, PermissionListener listener) {
        mListener = listener;
        List permissionList = new ArrayList<>();
        for(String permission : permissions) {
            if(ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(permission);
            }
        }
        if(!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(
                    this,
                    permissionList.toArray(new String[permissionList.size()]),
                    REQUEST_CODE);
        }else {
            mListener.onGranted();
        }
    }


    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE:
                if(grantResults.length > 0) {
                    List deniedPermission = new ArrayList<>();
                    for(int i = 0; i < grantResults.length; i++) {
                        int grantResult = grantResults[i];
                        if(grantResult == PackageManager.PERMISSION_DENIED) {
                            deniedPermission.add(permissions[i]);
                        }
                    }
                    if(deniedPermission.isEmpty()) {
                        mListener.onGranted();
                    }else {
                        mListener.onDenied(deniedPermission);
                    }
                }
               break;
            default:
                break;
        }
    }

}

PermissionListener用于将申请结果返回给调用的Activity。让Activity去实现权限申请结果相应的操作。

public interface PermissionListener {
    void onGranted();
    void onDenied(List deniedPermissions);
}

最后在Activity中使用:

public class SecondActivity extends BaseActivity{
    private LinearLayout mContainer;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initView();
    }

    private void initView() {
        mContainer = (LinearLayout) findViewById(R.id.id_second_container);
    }

    public void btnRuntimePermission(View view) {
        requestRuntimePermissions(new String[]{
                Manifest.permission.CALL_PHONE,
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionListener() {
            @Override
            public void onGranted() {
                Snackbar.make(mContainer, "All Permissions Granted!", Snackbar.LENGTH_SHORT).show();
            }

            @Override
            public void onDenied(List deniedPermissions) {
                StringBuilder builder = new StringBuilder(32);
                int deniedCount = deniedPermissions.size();
                for(int i = 0; i < deniedCount; i++) {
                    String[] strArray = deniedPermissions.get(i).split("\\.");
                    builder.append(strArray[strArray.length - 1]);
                    if(i == (deniedCount - 1)) {
                        builder.append(".");
                    }else {
                        builder.append(",");
                    }
                }
                Snackbar.make(
                        mContainer,
                        "Denied Permissions:" + builder.toString(),
                        Snackbar.LENGTH_SHORT
                ).show();
            }
        });
    }
}

参考

Android 6.0运行时权限讲解

运行效果

RuntimePermission.gif

你可能感兴趣的:(Runtime Permissions(郭霖CSDN公开课))