Android适配-自己封装几行代码解决Android6.0的权限申请

权限适配

  • 前言
  • 开工
    • 哪些权限需要申请
      • 普通权限
      • 通用的危险权限组:
    • 申请步骤
    • 封装库
    • library依赖
    • 几行代码调用

前言

Android6.0推出来的运行时权限申请想必大家都了解了,但是有些刚入行的兄弟们时而问起我,对这块知识点的适配工作还是有点不太熟,比如到底哪些权限需要申请?是否我配置的权限要一个一个申请呢?而且申请代码不难但是如果每次申请都这么写就有点繁杂了,能不能有点简单快捷的写法?假如用户拒绝了怎么提醒用户并引导用户去主动开启呢?今天这篇文章就来说道说道,用几行代码解决这些烦恼,效果如下图

本文所含代码随时更新,可从Github上下载最新代码
传送门

开工

哪些权限需要申请

我们知道在Android5.0以前,开发者只需要在manifest.xml文件中进行配置,应用就可以获取这些权限;Android5.0开始,用户在应用安装界面可以选择关闭不想开放的权限;从Android6.0开始,对于某些权限是需要在应用运行时向用户进行申请的(同时也需要在manifest文件配置),用户可以选择拒绝或者同意;如果拒绝了,用户也可以从设置里的应用详情界面重新选择权限

6.0以后Google对权限进行了划分,看下方

  • Normal Premission:普通权限,这类权限不会潜藏有侵害用户隐私和安全的问题,比如,访问网络的权限,访问WIFI的权限等
  • Dangerous Permission/Group: 危险权限/组,这类权限会直接的威胁到用户的安全和隐私问题,比如说访问短信,相册,地理位置等权限

普通权限

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

通用的危险权限组:

危险权限其实是分组的,分组的意义就是只要这个组里的一个权限被用户同意了,那其它权限默认就获取了;例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了

官网上的危险权限组是Google定的,但是你知道的Android碎片化很严重,很多厂家有自己的定制,会有自己的危险权限组,所以可以通过adb命令查看手机具体有哪些危险权限组

adb shell pm list permissions -d -p

 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 * 
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  

其实这些危险权限在国内的各大手机厂商的修改后,有些权限也会默认获取

申请步骤

  • 在manifest中进行配置
  • 判断系统版本并检查权限是否获取,对应的检查方法是ContextCompact.checkSelfPermission()
  • 进行权限申请,对应的申请方法是ActivityCompact.requestPermission()
  • 在申请回调中可以判断权限是否被允许还是拒绝,并可做相应处理

比如我们需要WRITE_EXTERNAL_STORAGE权限,这个权限应该是每个APP都需要的吧,那就来申请吧

先配置权限


接着进行判断

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
        
    }
}

然后进行申请

ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);

最后在回调中进行判断

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == 0) {

            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //恭喜权限申请通过
            } else {
                boolean result = ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[0]);
                if (result) {
                    //用户没有勾选不再询问,你以后还有机会再次申请
                } else {
                    //用户勾选了不再询问
                }
            }

        }
    }

这里如果用户同意申请了,那就完美了;但是如果拒绝了,你可以判断用户是否勾选了不再询问的box,如果没勾选你还能下次继续申请,但是要是勾选了你就只能提醒用户去设置界面手动设置了

Android适配-自己封装几行代码解决Android6.0的权限申请_第1张图片
注意:requestPermissions方法的第二个参数是个String数组,也就意味着里面可以放多个权限,然后一起申请,但是onRequestPermissionsResult方法是在全部权限向用户询问申请后才会回调

这里是当你有写文件需求的时候去向用户申请,接下来你可能在别的页面有访问地理位置的需求,然后又得申请;然后哪个页面要获取手机通讯录信息,又得去申请;这样每次申请都要写一堆这样的代码,这肯定不是一个喜欢偷懒还有点洁癖的程序员愿意干的,所以就得自己封装下了;其实Github上有很多这方面的库,用过几个,但是总是不如自己意,用起来不爽,既然这样就自己写吧

封装库

封装之前要考虑的几个问题:

  • 其一:onRequestPermissionsResult方法是在Activity里回调的,如果你封装的库把这个方法抛给每个申请的Activity去重写,那就违背我要封装的初衷了,不够偷懒啊;我就想在申请的时候几行代码搞定,结果你还要我重写这个方法,然后吭哧吭哧的去判断申请结果,那多累啊

  • 其二:我把申请的逻辑放在一个Activity里去做,但是问题来了,每次申请还要跳转到另一个Activity去,那肯定影响用户体验的;但是又必须要在Activity里做,那我们就让用户感觉不出来是跳转到Activity,这是可以做到的

  • 其三:既然是在Activity里做,那我总不能每次申请的时候代码写成跳转到Activity的吧,比如:

    Intent intent = new Intent(mContext.get(),PermissionActivity.class);
    intent.putExtra("permission",permission);
    intent.putExtra("type",mApplyType);
    intent.putExtra("title",mTitle);
    intent.putExtra("content",mContent);
    intent.putExtra("show",mIsShowAlarm);
    mContext.get().startActivity(intent);
    

    这写法多丑啊,一点链式编程的感觉都没有;而且有些事情需要在跳转到Activity进行申请前判断,所以需要一个代理提供给开发者调用

思路理清了,那就看代码吧

/**
 * @Description TODO(申请代理类)
 * @author cxy
 * @Date 2018/11/29 15:29
 */
public class PermissionApply {

    private String TAG = PermissionApply.class.getSimpleName();

    private static PermissionApply instance;

    private WeakReference mContext;

    public PermissionListener mListener;
    private String[] mPermissions;
    private String mTitle;
    private String mContent;
    private boolean mIsShowAlarm;

    private int mApplyType;

    private PermissionApply(Context context) {
        mContext = new WeakReference<>(context);
    }

    public static PermissionApply Build(Context context){
        if (instance == null) {
            instance = new PermissionApply(context);
        }
        return instance;
    }


    /**
     * 检测权限是否已经申请
     * @param permission
     * @return
     */
    public boolean isPermissionGranted(String permission){
        return ContextCompat.checkSelfPermission(mContext.get(),permission) == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * 重置一些变量
     * @return
     */
    public PermissionApply resetState(){
        mTitle = null;
        mContent = null;
        mIsShowAlarm = false;
        mPermissions = null;
        mListener = null;
        return this;
    }

    /**
     * 添加弹窗的标题和内容
     * @param title
     * @param content
     * @return
     */
    public PermissionApply addDialogString(String title,String content){
        mTitle = title;
        mContent = content;
        return this;
    }

    /**
     * 当用户拒绝申请后是否需要弹框提醒
     * @param isAlarm
     * @return
     */
    public PermissionApply shouldAlarm(boolean isAlarm){
        mIsShowAlarm = isAlarm;
        return this;
    }


    /**
     * 添加要注册的权限
     * @param permission
     * @return
     */
    public PermissionApply addPermission(String... permission){
        mPermissions = permission;
        return this;
    }

    /**
     * 添加监听
     * @param listener
     * @return
     */
    public PermissionApply addListener(PermissionListener listener){
        mListener = listener;
        return this;
    }

    /**
     * 提交权限申请
     */
    public void commit(){
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ) {
            if (mListener != null) mListener.isFine();
            return;
        }

        if (mPermissions == null || mPermissions.length == 0) {
            if (mListener != null) mListener.isFine();
            return;
        }

        String[] filter ;

        if (mPermissions.length == 1) {
            if (isPermissionGranted(mPermissions[0])) {
                if (mListener != null) mListener.isFine();
                return;
            }
            filter = mPermissions;
        } else {
            StringBuffer sb = new StringBuffer();
            for (int i=0; i

PermissionApply 主要是做一些申请前的准备工作

/**
 * @Description TODO(申请Activity)
 * @author cxy
 * @Date 2018/11/29 15:22
 *
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 * 
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *  
 *
 */
public class PermissionActivity extends AppCompatActivity{

    private String TAG = PermissionActivity.class.getSimpleName();

    public static int PERMISSION_APPLY_SINGLE = 1;
    public static int PERMISSION_APPLY_MULTI = 2;
    private static final int REQUEST_CODE_SINGLE = 3;
    private static final int REQUEST_CODE_MULTI = 4;
    private static final int REQUEST_SETTING = 5;

    private String[] permission = null;
    private int mApplyType;
    private String mAppName;

    private String mTitle;
    private String mContent;
    private String mCancelTxt;
    private String mPosTxt;
    private boolean mIsShowAlarm;

    private PermissionListener mListener;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mListener = PermissionApply.Build(this).mListener;
        Intent intent = getIntent();
        permission = intent.getStringArrayExtra("permission");
        mApplyType = intent.getIntExtra("type",1);
        mTitle = intent.getStringExtra("title");
        mContent = intent.getStringExtra("content");
        mIsShowAlarm = intent.getBooleanExtra("show",false);
        mAppName = getApplicationInfo().loadLabel(getPackageManager()) +"";

        if (TextUtils.isEmpty(mTitle))
            mTitle = getString(R.string.title);
        if (TextUtils.isEmpty(mContent))
            mContent = getString(R.string.content_first_half) + "\n\n" + getString(R.string.content_second_half,mAppName);
        mCancelTxt = getString(R.string.negative);
        mPosTxt = getString(R.string.positive);

        if (mApplyType == PERMISSION_APPLY_SINGLE)
            ActivityCompat.requestPermissions(this,permission,REQUEST_CODE_SINGLE);
        else
            ActivityCompat.requestPermissions(this,permission,REQUEST_CODE_MULTI);

    }

    /**
     * 权限被拒绝了,那就推荐用户到设置界面去添加
     * @param permissions 被拒绝的权限
     */
    private void showAlertDialog(String... permissions) {

        if (!mIsShowAlarm || permissions == null || permissions.length == 0 || TextUtils.isEmpty(permissions[0])) {
            putRefusePermission();
            finish();
            return;
        }

        permission = permissions;
        AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle(mTitle)
                .setMessage(mContent)
                .setCancelable(false)
                .setNegativeButton(mCancelTxt, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        putRefusePermission();
                        finish();
                    }
                })
                .setPositiveButton(mPosTxt, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Uri uri = Uri.parse("package:"+PermissionActivity.this.getPackageName());
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,uri);
                        startActivityForResult(intent,REQUEST_SETTING);
                        dialog.dismiss();
                    }
                }).create();
        alertDialog.show();
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_CODE_SINGLE) {
            if (ContextCompat.checkSelfPermission(this,permissions[0]) == PackageManager.PERMISSION_GRANTED) {
                onGranted(permissions);
                finish();
            } else {
                showAlertDialog(permissions);
            }
        } else {

            StringBuffer granted = new StringBuffer();
            StringBuffer refused = new StringBuffer();
            int size = permissions == null ? 0 : permissions.length;
            for (int i=0; i map = new HashMap<>();
        int size = permission == null ? 0 : permission.length;
        for (int i=0; i map){
        if (mListener != null) mListener.onRefuse(map);
    }

}

可以看到这个Activity是没有设置layout的,这是为了让用户感受不到是跳转到Activity;同时还要做一些主题的配置才能完美避免Activity跳转

library依赖

使用的话可以先添加依赖:

  • 先在项目根目录下的build.gradle中添加maven

    allprojects {
        repositories {
            maven { url 'https://jitpack.io' }
        }
    }
    
  • 然后在moudle的build,gradle中添加依赖

    dependencies {
        compile 'com.github.Mangosir:MangoPermission:v1.0.0-mango'
    }
    

几行代码调用

如果不需要在用户拒绝申请后提示用户去设置界面主动开启和申请结果,那就简单几行代码

PermissionApply.Build(this)
               .resetState()
               .addPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
               .commit();

如果需要提醒和获取申请结果,可以这样调用

String  mContent = getString(R.string.content_first_half) + "\n\n" + getString(R.string.content_second_half,getApplicationInfo().loadLabel(getPackageManager()));
PermissionApply.Build(this)
        .resetState()
        .addDialogString(getString(R.string.title),mContent)
        .shouldAlarm(true)
        .addPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.GET_ACCOUNTS)
        .addListener(listener)
        .commit();

你可能感兴趣的:(【Android常用开发】)