【转】Android权限管理

我今天写一个程序的时候发现Android6.0以上对权限的编写进行修改,我一直都不知道为什么我ImageView的setImageBitmap方法不能成功载入图片,我检查了多遍权限的声明以及对与图像的载入的代码,但是找不到错误在哪,我就是百度了一下导致这个问题的原因,然后发现Android6.0以上对权限的编写进行修改,不能再单纯的只在AndroidManifest.xml上进行添加权限了,要在代码中配置一下,然后我看了下我室友的博客找到了下面这篇博客,我感觉讲的还是蛮不错的,推荐给大家,可以方便大家对这方面的了解以及运用。

权限机制

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

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

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

Normal Permission:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

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

adb shell pm list permissions -d -g

Dangerous Permissions:

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_VOICE

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.SUPERUSER
  permission:android.permission.ACCESS_SUPERUSER

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

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:

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

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

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

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

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

API处理

新增的 API 有如下几个:

ContextCompat.checkSelfPermission

ActivityCompat.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. 权限申请回调

    @Override
    public 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;
    }

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

以上转自http://blog.csdn.net/hardworkingant/article/details/70945889

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值