android6.0以后动态获取权限

原文:
http://www.2cto.com/kf/201512/455888.html
http://blog.csdn.net/yangqingqo/article/details/48371123
http://inthecheesefactory.com/blog/things-you-need-to-know-about-Android-m-permission-developer-edition/en

一、Marshmallow版本权限简介

android的权限一直是首要的安全概念,因此这些权限只是在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西,而且一般用户安装的时候很少会仔细去看权限列表,更不会深入去了解这些权限可能带来的相关的危害。所以在android 6.0 Marshmallow版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。
这里写图片描述

二、旧版本app兼容问题

那么问题来了,是不是所有以前发布的app都会出现问题呢?答案是否定的,只有那些targetSdkVersion设置为23和23以上的应用才会出现异常,在使用危险权限的时候必须要获得用户的同意才能使用,要不然应用就会崩溃,出现类似
java.lang.SecurityException:Permission Denial:reading com.android.providers.media.MediaProvider的崩溃日志。所以targetSdkVersion如果没有设置23版本或者以上,系统还是会使用旧规则:在安装的时候赋予app所申请的所有权限。所以app当然可以和以前一样正常使用了,但是还有一点需要注意的是6.0的系统里面,用户可以手动将该app的权限关闭,如下图:
这里写图片描述
那么问题由来了,如果以前的老应用申请的权限被用户手动关闭了怎么办,应用会崩溃么?我们来试一试。

好吧,可以庆幸了一下,不会抛出异常,不会崩溃,只不过调用那些被用户禁止权限的api接口返回值都为null或者0,所以我们只需要做一下判断空操作就可以了,不判断空当然还是会崩溃的喽。
这里写图片描述

三、普通权限和危险权限列表

现在对于新版本的权限变更应该有了基本的认识,那么,是不是所有权限都需要去进行特殊处理呢?当然不是,只是有哪些危险级别的权限才需要。

PROTECTION_NORMAL类权限

当用户安装或更新应用时,系统将授予应用所请求的属于PROTECTION_NORMAL的所有权限(安装时授权的一类基本权限)。这类权限包括:
android.permission.ACCESS LOCATIONEXTRA_COMMANDS

android.permission.ACCESS NETWORKSTATE

android.permission.ACCESS NOTIFICATIONPOLICY

android.permission.ACCESS WIFISTATE

android.permission.ACCESS WIMAXSTATE

android.permission.BLUETOOTH

android.permission.BLUETOOTH_ADMIN

android.permission.BROADCAST_STICKY

android.permission.CHANGE NETWORKSTATE

android.permission.CHANGE WIFIMULTICAST_STATE

android.permission.CHANGE WIFISTATE

android.permission.CHANGE WIMAXSTATE

android.permission.DISABLE_KEYGUARD

android.permission.EXPAND STATUSBAR

android.permission.FLASHLIGHT

android.permission.GET_ACCOUNTS

android.permission.GET PACKAGESIZE

android.permission.INTERNET

android.permission.KILL BACKGROUNDPROCESSES

android.permission.MODIFY AUDIOSETTINGS

android.permission.NFC

android.permission.READ SYNCSETTINGS

android.permission.READ SYNCSTATS

android.permission.RECEIVE BOOTCOMPLETED

android.permission.REORDER_TASKS

android.permission.REQUEST INSTALLPACKAGES

android.permission.SET TIMEZONE

android.permission.SET_WALLPAPER

android.permission.SET WALLPAPERHINTS

android.permission.SUBSCRIBED FEEDSREAD

android.permission.TRANSMIT_IR

android.permission.USE_FINGERPRINT

android.permission.VIBRATE

android.permission.WAKE_LOCK

android.permission.WRITE SYNCSETTINGS

com.android.alarm.permission.SET_ALARM

com.android.launcher.permission.INSTALL_SHORTCUT

com.android.launcher.permission.UNINSTALL_SHORTCUT

这类权限只需要在AndroidManifest.xml中简单声明这些权限就好,安装时就授权。不需要每次使用时都检查权限,而且用户不能取消以上授权。

危险权限

这里写图片描述

android开发官网也有相关描述:
http://developer.android.com/training/permissions/requesting.html
http://developer.android.com/guide/topics/security/permissions.html

所以仔细去看自己的app,对照列表,如果需要申请其中的一个权限,就需要进行特殊操作。还有一个比较人性的地方就是如果同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_EXTERNAL_STORAGE被授权了,app也有READ_EXTERNAL_STORAGE权限了。

四、支持Marshmallow新版本权限机制

关于权限控制主要使用到
PermissionChecker类的checkSelfPermission();

ActivityCompat类的
public static boolean shouldShowRequestPermissionRationale(Activity avtivity,String permission)

Fragment类的
public boolean shouldShowRequestPermissionRationale(String permission)

ActivityCompat类的
public static void requestPermissions(final Activity activity,final String[] permissions,final int requestCode)

Fragment类的
public final void requestPermissions(String[] permissions,int requestCode)

private void startGetImageThread(){
    ...
    Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    ContentResolver contentResolver = getContentResolver();
    //获取jpeg和png格式的文件,并按时间进行倒叙
    Cursor cursor = contentResolver.query(uri,null,
        Media.MIME_TYPE + "=\"image/jpeg\"or" + 
        +"=\"image/png\"",null,
        MediaStore.Images.Media.DATE_MODIFIED+
        "desc");
    ...
}

这段嗲吗需要访问外部存储(相册图片),属于危险级别的权限,直接使用会造成应用程序崩溃,所以需要在这段代码之前进行特殊处理:

int hasWriteContactsPermission=checkSelfPermission
    (Manifest.permission.WRITE_EXTERN_STORAGE);
if(hasWriteContentsPermission != 
    PackageManager.PERMISSION_GRANTED){
    requestPermissions(new String[]
    {Manifest.permisssion.WRITE_EXTERNAL_STORAGE},
    REQUEST_CODE_WRITE_PERMISSION);

    returns;
}

写完这段代码之后,就会出现如下系统dialog:
这里写图片描述

紧接着就需要处理DENY和ALLOW的回调了。
重写ActivityCompat.OnRequestPermissionsResultCallback函数:

@Override
public void onRequestPermissionResult(
   int requestCode,String[] permissions,int[]{
   if(permissions[0].equals(
       Manifest.permission.WRITE_EXTERNAL_STORAGE)
       &&grantResults[0] == 
       PackageManager.PERMISSON_GRANTED){
           //用户同意使用write
           startGetImageThead();
       }else{
           //用户不同意,自行处理即可
           finsh();
       }
}

五、处理不再提醒

如果用户拒绝某授权。下一次弹框,用户会有一个“不再提醒”的选项来防止app以后继续请求授权
这里写图片描述
如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调onRequestPermissionsResult函数,回调结果为最后一次用户的选择。所以为了应对这种情况,系统提供了一个shouShowRequestPermissionRationale()函数,这个的作用是帮助开发者找到需要向用户额外解释权限的情况,这个函数:
应用安装后第一次访问,直接返回false;第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回true,这时候可以显示一些为什么需要这个权限的说明;第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回false;设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermission返回false;注意:第二次请求权限时,才会有“不再提醒”的选项,如果用户一直拒绝,并没有选择“不再提醒”的选项,下次请求权限时,会继承有“不再提醒”的选项,并且shouldShowRequestRationale()也会一直返回true。

所以利用这个函数我们可以进行相应的优化,针对shouldShowRequestPermissionRationable函数返回false的处理有两种方案
第一种方案:如果应用是第一次请求该权限,则直接调用requestPermissions函数去请求权限;如果不是则代表用户勾选了“不再提醒”,弹出dialog,告诉用户为什么你需要该权限,让用户手动开启该权限。
链接:
http://stackoverflow.com/questions/32347532/android-m-permissions-confused-on-the-usage-of-shouldshowrequestpermissionrati
第二种方案:在onRequestPermissionsReuslt函数中进行检测,如果返回ERMISSION_DENIED,则调用shouldShowRequestPermissionRationale函数,如果返回false代表用户已经禁止该权限上面的(3和4两种情况),弹出dialog告诉用户你需要该权限的理由,让用户手动打开。
链接:
http://stackoverflow.com/questions/30719047/android-m-check-runtime-permission-how-to-determine-if-the-user-checked-nev
处理方法已经有了,修改一下代码,我这里就以第二种方案来处理了。

public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
    if(requestCode == CODE_FOR_WRITE_PERMISSION){
        if(permissions[0].equals(Manifest.
        permission.WRITE_EXTERNAL_STORAGE) && 
        grantResults[0]==PackageManager.
        PERMISSION_GRANTED){
            //用户同意使用write
            startGetImageThread();
        }else{
            /用户不同意,向用户展示该权限作用
            if(!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)){
            AlertDialog dialog = new 
                AlertDialog.Builder(this)
                .setMessage("该相册需要赋予访问存储的权限,不开启将无法正常工作!")
                .setPositiveButton("确定",new DialogInterface.OnClickListener(){
                @Override
                public void onClick(DialogInterface dialog ,int which){
                finsh();
                }
                })
                .setNegativeButton("取消",new DialogInterface.OnClickListener(){
                @Overiride
                public void onClick(DialogInterface dialog,int which){
                finish();
                }
                }).create()
                .show();
            return;
            }
            finsh();
        }
    }
}

当勾选不再提醒,并拒绝之后,弹出dialog提醒用户该权限的重要性:

这里写图片描述

六、使用兼容库
以上代码在6.0版本以上使用没有问题,但是在之前就有问题了,最简单粗暴的解决办法就是利用Build.VERSION>SDK_INT >= 23这个判断语句来判断了,方便的是SDK 23的v4包加入了专门类进行相关的处理。

ContextCompat.checkSelfPermission()被授权函数PERMISSION_GRANTED,否则返回PERMISSION_DENIED,在所有版本都是如此。

ActivityCompat.requestPermissions()这个方法在6.0之前版本调用onRequestPermissionsResultCallback直接被调用,带着正确的PERMISSION_GRANTED或者PERMISSION_DENIED。

ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本调用永远返回false。

用v4包的这三个方法,完美兼容所有版本!下面是代码:

//使用兼容库就无须判断系统版本
int hasWriteContactsPermission = 
ContextCompat.checkSelfPermission(getApplication()
    ,Manifest.permission.WRITE_EXTERNAL_STORAGE);

if(hasWriteContactsPermission == 
    PackageManager.PERMISSION_GRANTED){
    startGetImageThread();
}
//于鏊谈出dialog个用户手动赋予权限
else{
 ActivityCompat.requestPermissions(PickOrTakeImageActvity.this,new String[]{Manifest.permisson.
WRITE_EXTERNAL_STORAGE},CODE_FOR_WRITE_PERMISSON);
}

onRequestPermissionsResult函数不变。后两个方法,我们也可以在Fragment中使用,用v13兼容包:FragmenCompat.requestPermissions()和FragmentCompat.should和showRequestPermisssionRationale()和activity效果一样。

七、一次请求多个权限
当让有时候需要多个权限,可以用上面的方法一次请求多个权限。
List permissionsNeeded = new ArrayList();
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
permissionsNeeded.add(Manifest.permission.READ_CONTACTS);
permissionsNeeded.add(Manifest.permission.WRITE_CONTACTS);
requestPermissions(permissionsNeeded.toArray(new String[permissionsList.size()]), CODE_FOR_MULTIPLE_PERMISSION);
  最后在onRequestPermissionsResult函数中一个个处理返回结果即可。
  下面是一个demo

public class 
MainActivity extends AppCompatActivity {

    private static final String TAG = "Contacts";
    private final int 
       REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
    Button button = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                insertDummyContactWrapper();
            }
        });
    }

    //获取权限判断
    private void insertDummyContactWrapper(){
        List<String> permissionsNeeded = new ArrayList<>();
        final List<String> permissionsList = new ArrayList<>();
        if(!addPermission(permissionsList,Manifest.permission.ACCESS_COARSE_LOCATION)){
            permissionsNeeded.add("GPS");
        }
        if(!addPermission(permissionsList,Manifest.permission.READ_CONTACTS)) {
            permissionsNeeded.add("Read Contacts");
        }
        if(!addPermission(permissionsList,Manifest.permission.WRITE_CONTACTS)){
            permissionsNeeded.add("Write Contacts");
        }

        if(permissionsList.size() > 0){
            if(permissionsNeeded.size() > 0){
                //Need Rationale
                String message = "You need to grant access to" + permissionsNeeded.get(0);
                for(int i = 1;i < permissionsNeeded.size();i++)
                    message = message + "," + permissionsNeeded.get(i);
                showMessageOKCancel(message,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                            }
                        });
                return;
            }
            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            return ;
        }
        insertDummyContact();
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener){
        new AlertDialog.Builder(MainActivity.this)
                .setMessage(message)
                .setPositiveButton("OK",okListener)
                .setNegativeButton("Cancel",null)
                .create()
                .show();
    }

    private boolean addPermission(List<String> permissionList,String permission){
        if(checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(permission);
            //check for Rationale Option
            if(!shouldShowRequestPermissionRationale(permission))
                return false;
        }
        return true;
    }

    //使用ContentProvider进行插入操作
    private void insertDummyContact(){
        //Two operation are needed to insert a new contact
        ArrayList<ContentProviderOperation> operations = new ArrayList<>(2);

        //First,set up a new raw contact
        ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE,null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME,null);
        operations.add(op.build());

        //Next.set the name for the contact
        op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,0)
                .withValue(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                        "_Dummy Contact from runtime permission sample");
        operations.add(op.build());

        //Apply the operation
        ContentResolver resolver = getContentResolver();

        try {
            resolver.applyBatch(ContactsContract.AUTHORITY,operations);
        } catch (RemoteException e) {
            Log.d(TAG,"Could not add a new contact:" + e.getMessage());
        } catch (OperationApplicationException e){
            Log.d(TAG,"Cound not add a new contact:" + e.getMessage());
        }
    }
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults)
    {
        switch (requestCode)
        {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
            {
                Map<String,Integer> perms = new HashMap<>();
                //Initial
                perms.put(Manifest.permission.ACCESS_COARSE_LOCATION,PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.WRITE_CONTACTS,PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.READ_CONTACTS,PackageManager.PERMISSION_GRANTED);
                //Fill with results
                for(int i = 0;i < permissions.length;i++)
                    perms.put(permissions[i],grantResults[i]);
                //check for ACCESS_FINE_LOCATION
                if(perms.get(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
                        &&perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
                        &&perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED){
                    insertDummyContact();
                }else{
                    //Permission Denied
                    Toast.makeText(MainActivity.this,"Some permission is Denied" ,Toast.LENGTH_SHORT).show();
                }
            }
                break;
            default:
                super.onRequestPermissionsResult(requestCode,permissions,grantResults);
        }
    }
}

九、App处于运行状态下,被撤销权限

如果APP正在运行中,用户进入设置-应用程序界面去手动撤销该APP权限,会出现什么情况呢?
系统又会弹出权限请求对话框。
这里写图片描述

有些在某些手机上会直接退出应用,这个应该还和测试环境有关。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值