美文网首页Android专题Java
Android基础(30)动态权限、安全

Android基础(30)动态权限、安全

作者: perry_Fan | 来源:发表于2019-03-04 21:10 被阅读325次

    1)动态权限适配方案,权限组的概念

    2)权限管理系统(底层的权限是如何进行 grant 的)?
    http://www.cnblogs.com/rossoneri/p/10266189.html
    3)谈谈你对安卓签名的理解。
    4)App 是如何沙箱化,为什么要这么做?
    https://blog.csdn.net/ljheee/article/details/53191397


    5)请解释安卓为啥要加签名机制?
    一. 简介

    从Android6.0 棉花糖,Google进行了应用的权限申请方案。调整之后的用户授权,不仅仅体现在用户安装应用之时,更为重要的是,对于权限层级为危险的用户权限,需要在应用运行时向其授权,简称,动态授权

    动态授权方案不仅可以简化应用安装过程,用户在安装或更新过程中可以不需要授予权限,而且更为重要的是可以实现用户对应用功能进行更多的控制,从而达到用户增强隐私的保护。

    动态授权方案将系统权限进行了分级。分为危险权限和普通权限。危险权限需要在使用的地方,通过官方调用的API主动申请。

    // 涉及读写联系人,访问账户
    group:android.permission-group.CONTACTS
      permission:android.permission.WRITE_CONTACTS
      permission:android.permission.GET_ACCOUNTS
      permission:android.permission.READ_CONTACTS
    // 涉及电话操作
    group: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_VOICEMAIL
    // 涉及日历信息的操作(用户日程安排)
    group:android.permission-group.CALENDAR
      permission:android.permission.READ_CALENDAR
      permission:android.permission.WRITE_CALENDAR
    // 涉及相机操作
    group:android.permission-group.CAMERA
      permission:android.permission.CAMERA
    // 涉及使用手机传感器操作
    group:android.permission-group.SENSORS
      permission:android.permission.BODY_SENSORS
    // 涉及用户地理位置信息的操作
    group:android.permission-group.LOCATION
      permission:android.permission.ACCESS_FINE_LOCATION
      permission:android.permission.ACCESS_COARSE_LOCATION
    // 涉及存储卡的读写操作
    group:android.permission-group.STORAGE
      permission:android.permission.READ_EXTERNAL_STORAGE
      permission:android.permission.WRITE_EXTERNAL_STORAGE
    // 涉及多媒体信息的操作哦
    group:android.permission-group.MICROPHONE
      permission:android.permission.RECORD_AUDIO
    // 涉及SMS卡的操作
    group: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_BROADCASTS
    
    ungrouped:
      permission:com.xiaomi.xmsf.permission.PAYMENT // 小米 下载而不显示通知
      permission:miui.permission.ACCESS_BLE_SETTINGS  // 小米 涉及到用户设置的操作
    
    
    二. 权限分类:
    1.检查权限:ContextCompat.checkSelfPermission();

    如果您的应用中需要危险权限,则每次执行需要这一权限的操作时,都必须检查是否具有该权限,用户始终可以自由的调用次权限,因此,即使应用昨天使用了并响应,他不能假设自己今天仍然具有该权限。

    // Assume thisActivity is the current activity
    int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
            Manifest.permission.WRITE_CALENDAR);
    
    2.申请权限:ContextCompat.requestPermissions();

    如果应用需要Manifest清单中的危险权限,那么他必须要求用户授予该权限。并且在某些情况下,我们需要让用户了解应用为什么需要某些权限。最简单的例子是,当我们需要相机拍照的时候需要相机权限,这是很正常的需求,但是当我们需要保存照片信息的时候,需要用户的地理位置信息,这个权限对于用户来说就显得很不理解。针对这种现状,Google也为我们提供了很实用的Api ,ContextCompat.shouldShowRequestPermissionRationab-如果应用之前请求过次权限但用户拒绝了,则该方法返回true。如果用户不仅拒绝上次的请求权限,而且勾选了“不再提示”,则该方法返回false。

    
    // Here, thisActivity is the current activity
    if (ContextCompat.checkSelfPermission(thisActivity,
                    Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
     
        // Should we show an explanation?
        if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
                Manifest.permission.READ_CONTACTS)) {
     
            // Show an expanation to the user *asynchronously* -- don't block
            // this thread waiting for the user's response! After the user
            // sees the explanation, try again to request the permission.
     
        } else {
     
            // No explanation needed, we can request the permission.
     
            ActivityCompat.requestPermissions(thisActivity,
                    new String[]{Manifest.permission.READ_CONTACTS},
                    MY_PERMISSIONS_REQUEST_READ_CONTACTS);
     
            // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
            // app-defined int constant. The callback method gets the
            // result of the request.
        }
    
    3.处理申请结果:onRequestPermissionResult();

    当应用请求权限时候,系统将向用户显示一个对话框。当用户相应的时候,系统将调用onRequstPermissionsResult() 方法。向其传递用户的相应。我们需要在我们自己Activity中复写该方法。并且对用户操作的反馈做处理。

    @Override
    public void onRequestPermissionsResult(int requestCode,
            String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.
     
                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
                return;
            }
     
            // other 'case' lines to check for other
            // permissions this app might request
        }
    }
    
    三. 实现

    1.定义权限回调

        // 这是权限回调接口;
        public interface OnPermissionCallback {
            // 授权;
            void onGranted();
     
            // 拒绝;
            void onDenied();
        }
    

    2.请求权限,这个是供外界调用的方法。参数为需要申请的权限,和权限回调。如果申请的权限已经被用户授权,则直接走授权回调,否则去申请权限。

     /**
       * 要求授权;
       */
        public void requestPermissions(@NonNull String[] permissions, @NonNull OnPermissionCallback
                callback) {
            if (permissions.length < 1) {
                return;
            }
            mPermissions = permissions;
            mPermissionCallback = callback;
            if (checkPermissions(permissions)) {
                // 有权限
                if (mPermissionCallback != null) {
                    mPermissionCallback.onGranted();
                }
            } else {
                // 无权限;
                ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
            }
        }
    

    3.申请权限后,程序会走OnRequestPermissionsResult()的回调。在回调中继续我们的逻辑;

    @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                               @NonNull int[] grantResults) {
            if (requestCode == PERMISSION_REQUEST_CODE) {
                if (checkPermissionResult(grantResults)) {
                    // 全部授权
                    if (mPermissionCallback != null) {
                        mPermissionCallback.onGranted();
                    }
                } else {
                    if (shouldShowRequestPermissionsRationale(permissions)) {
                        // 用户选择了拒绝;
                        ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
                    } else {
                        // 用户选择了不再提示;
                        showPermissionRationale(null, null);
                    }
                    // 没有全部授权;
                    if (mPermissionCallback != null) {
                        mPermissionCallback.onDenied();
                    }
                }
     
            } else {
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    

    在onRequestPermissionResult方法中,如果申请的所有权限都已经授权,则走授权回调。没有的话,再通过shouldRequestPermissionRationale()方法继续分析用户的针对我们的上次授权的操作。需要说明的一点是shouldRequestPermissonsRationale()这个方法的返回值,如果用户针对我们的上次请求权限拒绝的话,会返回true,如果用户针对我们的上次请求权限不仅做了拒绝的操作,而且还点击了“不再提醒”(就是以后不会再跳出授权框)的选择框,则该方法返回false。
    针对这个情况,如果用户只是单纯的拒绝授权,我们在这里进行第二次的请求权限。如果用户选择“不再提醒”,我们就弹出一个自定的Dialog,引导用户去设置界面授权。

    这里的Dialog,我直接用了SweetDialog的普通样式。

        // 跳出 提示界面;
        private void showPermissionRationale(@Nullable String title, @NonNull String content) {
            SweetAlertDialog dialog = new SweetAlertDialog(this, SweetAlertDialog.NORMAL_TYPE);
            dialog.setCancelable(false);
            dialog.setTitleText(TextUtils.isEmpty(title) ? getString(R.string.permission_title) : title)
                    .setContentText(TextUtils.isEmpty(content) ? getString(R.string
                            .permission_content) : content)
                    .setConfirmButton(R.string.dialog_ok, new SweetAlertDialog.OnSweetClickListener() {
                        @Override
                        public void onClick(SweetAlertDialog sweetAlertDialog) {
                            sweetAlertDialog.dismissWithAnimation();
                            gotoSetting();
                        }
                    })
                    .setCancelButton(R.string.permission_cancle, new SweetAlertDialog
                            .OnSweetClickListener() {
                        @Override
                        public void onClick(SweetAlertDialog sweetAlertDialog) {
                            sweetAlertDialog.dismissWithAnimation();
                            if(mPermissionCallback!=null){
                                mPermissionCallback.onDenied();
                            }
                        }
                    })
                    .show();
        }
    

    在Dialog中如果用户选择取消的话,则表示这次授权失败了,直接走失败的回调。如果用户选择"ok”的话,则引导用户到设置详情界面去手动授权。

      // 跳转到设置界面;
        private void gotoSetting() {
            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            intent.setData(Uri.fromParts("package", getPackageName(), null));
            startActivityForResult(intent, PERMISSION_REQUEST_CODE);
        }
    

    需要注意的是这里引导用户到设置界面的方式是通过startActivityForResult()的方式。用户在从设置界面回到应用的时候,会走onActivityResult()方法,在这里我们需要再一次的对权限做判断。

      @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == PERMISSION_REQUEST_CODE) {
                if (mPermissions != null && checkPermissions(mPermissions)) {
                    if (mPermissionCallback != null) {
                        mPermissionCallback.onGranted();
                    } else {
                        mPermissionCallback.onDenied();
                    }
                }
            } else {
                super.onActivityResult(requestCode, resultCode, data);
            }
     
        }
    

    以上的这些都是在我的Activity的基类中完成的。我的所有activity继承这个基类。在需要动态权限的地方只需要一句代码就可以完成动态权限的申请了。

            requestPermissions(new String[]{Manifest.permission.CAMERA}, new OnPermissionCallback() {
                @Override
                public void onGranted() {
                    Log.d("Permission","onGranted");
                }
     
                @Override
                public void onDenied() {
                    Log.d("Permission","onDenied");
                }
            });
    
    为什么需要android签名呢?
    1. 发送者的身份认证
      由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换

    2. 保证信息传输的完整性
      签名对于包中的每个文件进行处理,以此确保包中内容不被替换

    3. 防止交易中的抵赖发生, Market 对软件的要求

    给apk签名可以带来以下好处:
    1. 应用程序升级:如果你希望用户无缝升级到新的版本,那么你必须用同一个证书进行签名。这是由于只有以同一个证书签名,系统才会允许安装升级的应用程序。如果你采用了不同的证书,那么系统会要求你的应用程序采用不同的包名称,在这种情况下相当于安装了一个全新的应用程序。如果想升级应用程序,签名证书要相同,包名称要相同!

    2. 应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序,此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块

    3. 代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。以同一个证书对多个应用程序进行签名,利用基于签名的权限检查,你就可以在应用程序间以安全的方式共享代码和数据了。
      不同的应用程序之间,想共享数据,或者共享代码,那么要让他们运行在同一个进程中,而且要让他们用相同的证书签名。

    怎样签名呢?用java自带的keytool和jarsigner工具。

    1. 通过以下命令可以获取证书和私钥:keytool -genkey -v -keystore doumiw.keystore -alias doumiw -keyalg RSA -validity 10000
    2. 通过以下命令可以对apk签名:jarsigner -verbose -keystore doumiw.keystore doumiw.apk doumiw

    相关文章

      网友评论

        本文标题:Android基础(30)动态权限、安全

        本文链接:https://www.haomeiwen.com/subject/sdymeqtx.html