Deprecated

此库不再维护

=============================================================

apk GitHub stars

platform license

lib processor annotation

中文|ENGLISH

前言

permissions4m

意为 Permissions for M,基于 hongyangAndroid 的 MPermissions 项目二次开发,使用编译时注解,较运行时注解效率更高。起初目的是仅作为纯粹的 Andriod 编译时注解项目,较原有项目有以下升级:

后 permissions4m 为适配国产机型而迭代,目前:

权限申请官方文档:在运行时请求权限

permissions4m 与 AndPermission

AndPermission 申请框架

AndPermission 申请地理位置(小米)/AndPermission 申请联系人(小米)/AndPermission 申请手机状态(小米)/AndPermission 申请短信、日历(OPPO A57):

图片1图片2图片3图片4

permissions4m

permissions4m 申请地理位置(小米)/permissions4m 申请联系人(小米)/permissions4m 申请手机状态(小米)/permissions4m 申请短信、日历(OPPO A57):

图片1图片2图片3图片4

引入依赖

Gradle 依赖

project 中的 build.gradle

buildscript {
  // ...
}

allprojects {
  repositories {
    // 请添加如下一行
    maven { url 'https://jitpack.io' }
  }
}

app 中的 build.gradle

dependencies {
  compile 'com.github.jokermonn:permissions4m:2.1.2-lib'
  annotationProcessor 'com.github.jokermonn:permissions4m:2.1.2-processor'
}

使用文档

版本迭代

TODO

迭代历史

注意事项

必加的二次权限申请回调

在 Activity 或 Fragment 中,需要手动添加 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 方法以支持权限申请回调,代码如下即可:

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

注解回调

单个权限申请

Permissions4M.get(MainActivity.this)
    // 是否强制弹出权限申请对话框,建议设置为 true,默认为 true
            // .requestForce(true)
    // 是否支持 5.0 权限申请,默认为 false
    // .requestUnderM(false)
    // 权限,单权限申请仅只能填入一个
    .requestPermissions(Manifest.permission.RECORD_AUDIO)
    // 权限码
    .requestCodes(AUDIO_CODE)
    // 如果需要使用 @PermissionNonRationale 注解的话,建议添加如下一行
    // 返回的 intent 是跳转至**系统设置页面**
    // .requestPageType(Permissions4M.PageType.MANAGER_PAGE)
    // 返回的 intent 是跳转至**手机管家页面**
    // .requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
    .request();

将会回调相应的 @PermissionsGranted@PermissionsDenied@PermissionsRationale/PermissionsCustomRationale@PermissionsNonRationale 所修饰的方法

多个权限同步申请

例:参考 sample 中 MainActivity 上的设置 ——

@PermissionsRequestSync(
    permission = {Manifest.permission.BODY_SENSORS, 
                    Manifest.permission.ACCESS_FINE_LOCATION, 
                        Manifest.permission.READ_CALENDAR},
    value = {SENSORS_CODE, 
                LOCATION_CODE, 
                    CALENDAR_CODE})
public class MainActivity extends AppCompatActivity

注:同步申请默认强制申请(requestForce(true)),同步申请不支持 @PermissionsNonRationale

@PermissionsGranted

授权成功时回调,注解中需要传入参数,分为两种情况:

传参的话必须是 int 型

@PermissionsGranted(LOCATION_CODE)
public void granted(int code) {
    ToastUtil.show("地理位置授权成功");
}

@PermissionsDenied

授权失败时回调,注解中需要传入参数,分为两种情况:

传参的话必须是 int 型

@PermissionsDenied(LOCATION_CODE)
public void denied(int code) {
    ToastUtil.show("地理位置授权失败");
}

@PermissionsRationale

二次授权时回调,用于解释为何需要此权限,注解中需要传入参数,分为两种情况:

传参的话必须是 int 型

@PermissionsRationale(LOCATION_CODE)
public void denied(int code) {
    ToastUtil.show("请开启读取地理位置权限");
}

注:系统弹出权限申请 dialog 与 toast 提示是异步操作,所以如果希望自行弹出一个 dialog 后(或其他同步需求)再弹出系统对话框,那么请使用 @PermissionsCustomRationale

@PermissionsCustomRationale

二次授权时回调,用于解释为何需要此权限,注解中需要传入参数,分为两种情况:

传参的话必须是 int 型

@PermissionsCustomRationale(LOCATION_CODE)
public void cusRationale(int code) {
    new AlertDialog.Builder(this)
                    .setMessage("读取地理位置权限申请:\n我们需要您开启读取地理位置权限(in activity with annotation)")
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Permissions4M.get(MainActivity.this)
                                    // 注意添加 .requestOnRationale()
                                    .requestOnRationale()
                                    .requestPermissions(Manifest.permission.READ_SMS)
                                    .requestCodes(SMS_CODE)
                                    .request();
                        }
                    })
                    .show();
}

注:除上述以外的 dialog,开发者可以自定义其他展示效果,调用权限申请时请使用如下代码,否则会陷入无限调用自定义 Rationale 循环中

Permissions4M.get(MainActivity.this)
        // 务必添加下列一行
      .requestOnRationale()
      .requestPermissions(Manifest.permission.RECORD_AUDIO)
      .requestCodes(AUDIO_CODE)
      .request();

@PermissionsNonRationale

拒绝权限不再提示国产畸形权限适配扩展)情况下调用,此时意味着无论是 @PermissionsCustomRationale 或者 @PermissionsRationale 都不会被调用,无法给予用户提示。permission 将会返回一个跳转至 手机管家界面或者应用设置界面的 intent,具体的设置方法请参考 注解回调.requestPageType(int) 设置方法。。此时该注解修饰的函数被调用,注解中需要传入参数,分为两种情况:

也可传入 int 参数和 Intent 参数,例:

@PermissionsNonRationale({LOCATION_CODE})
public void nonRationale(int code, Intent intent) {
    startActivity(intent);
}

注:请勿对同步请求的权限使用该注解,理由见项目答疑第1条

Listener 回调

单个权限申请

Permissions4M.get(MainActivity.this)
    // 是否强制弹出权限申请对话框,建议为 true,默认为 true
    // .requestForce(true)
    // 是否支持 5.0 权限申请,默认为 false
    // .requestUnderM(false)
    // 权限
    .requestPermissions(Manifest.permission.READ_CONTACTS)
    // 权限码
    .requestCodes(READ_CONTACTS_CODE)
    // 权限请求结果
    .requestListener(new Wrapper.PermissionRequestListener() {
        @Override
        public void permissionGranted(int code) {
            ToastUtil.show("读取通讯录权限成功 in activity with listener");
        }

        @Override
        public void permissionDenied(int code) {
            ToastUtil.show("读取通讯录权失败 in activity with listener");
        }

        @Override
        public void permissionRationale(int code) {
            ToastUtil.show("请打开读取通讯录权限 in activity with listener");
        }
    })
    // 二次请求时回调
    .requestCustomRationaleListener(new Wrapper.PermissionCustomRationaleListener() {
            @Override
            public void permissionCustomRationale(int code) {
                new AlertDialog.Builder(getActivity())
                   .setMessage("通讯录权限申请:\n我们需要您开启通讯录权限(in fragment with annotation)")
                   .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                       @Override
                       public void onClick(DialogInterface dialog, int which) {
                           Permissions4M.get(NormalFragment.this)
                                      .requestOnRationale()
                                      .requestPermissions(Manifest.permission.READ_PHONE_STATE)
                                      .requestCodes(PHONE_STATE_CODE)
                                      .request();
                        }
                    })
                    .show();
             }
    })
    // 权限完全被禁时回调函数中返回 intent 类型(手机管家界面)
    .requestPageType(Permissions4M.PageType.MANAGER_PAGE)
    // 权限完全被禁时回调函数中返回 intent 类型(系统设置界面)
    //.requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
    // 权限完全被禁时回调,接口函数中的参数 Intent 是由上一行决定的
    .requestPage(new Wrapper.PermissionPageListener() {
            @Override
            public void pageIntent(final Intent intent) {
                new AlertDialog.Builder(MainActivity.this)
                .setMessage("读取通讯录权限申请:\n我们需要您开启读取通讯录权限(in activity with listener)")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        startActivity(intent);
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .show();
            }
        })
    .request();

多个权限同步申请

Permissions4M.get(NormalFragment.this)
        .requestPermissions(Manifest.permission.BODY_SENSORS, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CALENDAR)
        .requestCodes(SENSORS_CODE, LOCATION_CODE, CALENDAR_CODE)
        .requestListener(new Wrapper.PermissionRequestListener() {
            @Override
            public void permissionGranted(int code) {
                switch (code) {
                    case LOCATION_CODE:
                        ToastUtil.show("地理位置权限授权成功 in fragment with annotation");
                        break;
                    case SENSORS_CODE:
                        ToastUtil.show("传感器权限授权成功 in fragment with annotation");
                        break;
                    case CALENDAR_CODE:
                        ToastUtil.show("读取日历权限授权成功 in fragment with annotation");
                        break;
                    default:
                        break;
                }
            }

            @Override
            public void permissionDenied(int code) {
                switch (code) {
                    case LOCATION_CODE:
                        ToastUtil.show("地理位置权限授权失败 in fragment with annotation");
                        break;
                    case SENSORS_CODE:
                        ToastUtil.show("传感器权限授权失败 in fragment with annotation");
                        break;
                    case CALENDAR_CODE:
                        ToastUtil.show("读取日历权限授权失败 in fragment with annotation");
                        break;
                    default:
                        break;
                }
            }

            @Override
            public void permissionRationale(int code) {
                switch (code) {
                    case LOCATION_CODE:
                        ToastUtil.show("请开启地理位置权限 in fragment with annotation");
                        break;
                    case SENSORS_CODE:
                        ToastUtil.show("请开启传感器权限 in fragment with annotation");
                        break;
                    case CALENDAR_CODE:
                        ToastUtil.show("请开启读取日历权限 in fragment with annotation");
                        break;
                    default:
                        break;
                }
            }
        })
        .requestCustomRationaleListener(new Wrapper.PermissionCustomRationaleListener() {
            @Override
            public void permissionCustomRationale(int code) {
                switch (code) {
                    case LOCATION_CODE:
                        ToastUtil.show("请开启地理位置权限 in fragment with annotation");
                        Log.e("TAG", "permissionRationale: 请开启地理位置权限 ");

                        new AlertDialog.Builder(getActivity())
                                .setMessage("地理位置权限权限申请:\n我们需要您开启地理位置权限(in fragment with " +
                                        "annotation)")
                                .setPositiveButton("确定", new DialogInterface
                                        .OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        Permissions4M.get(NormalFragment.this)
                                                .requestOnRationale()
                                                .requestPermissions(Manifest.permission
                                                        .ACCESS_FINE_LOCATION)
                                                .requestCodes(LOCATION_CODE)
                                                .request();
                                    }
                                })
                                .show();
                        break;
                    case SENSORS_CODE:
                        ToastUtil.show("请开启传感器权限 in fragment with annotation");
                        Log.e("TAG", "permissionRationale: 请开启传感器权限 ");

                        new AlertDialog.Builder(getActivity())
                                .setMessage("传感器权限申请:\n我们需要您开启传感器权限(in fragment with " +
                                        "annotation)")
                                .setPositiveButton("确定", new DialogInterface
                                        .OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        Permissions4M.get(NormalFragment.this)
                                                .requestOnRationale()
                                                .requestPermissions(Manifest.permission
                                                        .BODY_SENSORS)
                                                .requestCodes(SENSORS_CODE)
                                                .request();
                                    }
                                })
                                .show();
                        break;
                    case CALENDAR_CODE:
                        ToastUtil.show("请开启读取日历权限 in fragment with annotation");
                        Log.e("TAG", "permissionRationale: 请开启读取日历权限 ");

                        new AlertDialog.Builder(getActivity())
                                .setMessage("日历权限申请:\n我们需要您开启日历权限(in fragment with " +
                                        "annotation)")
                                .setPositiveButton("确定", new DialogInterface
                                        .OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        Permissions4M.get(NormalFragment.this)
                                                .requestOnRationale()
                                                .requestPermissions(Manifest.permission
                                                        .READ_CALENDAR)
                                                .requestCodes(CALENDAR_CODE)
                                                .request();
                                    }
                                })
                                .show();
                        break;
                    default:
                        break;
                }

            }
        })
        .request();

注:同步申请不支持 PermissionPageListener 回调,理由见项目答疑第1条

混淆

1.如果你是使用 listener 可以不必混淆

2.如果你是使用 annotation ,如下:

-dontwarn com.joker.api. -keep class com.joker.api. {*;} -keep interface com.joker.api.* { ; } -keep class *$$PermissionsProxy { ; }

help me

1. 作者司里没有几部测试机,所以写到这一步之后就需要各位开发者共同努力,如果你在开发过程中使用了 vivo、魅族等权限适配也很畸形的手机,请联系作者或提交 issue 或 pull request。需要提交的资料包含:

2. 目前针对主流权限的强制对话框弹出已经基本完成,但苦于作者功力有限,所以对于涉及到使用以下权限的代码暂时并未完善,如果各位开发者知道能够触发以下权限的代码,可以及时联系作者完善项目:

国产畸形权限适配扩展

总结:

解决方案

6.0:国产大部分机型手机的申请权限实际上应该细致地分为申请权限应用权限 。它们的 ContextCompat.checkSelfPermission(Context, String) 判断是根据是否 AndroidManifest.xml 中声明了该权限来决定返回值,在 AndroidManifest.xml 中声明了权限就返回 true,当然也会有一些会返回 false,这个是申请权限的过程。而真正对话框的弹出是在开发者应用权限的过程中,什么叫做应用权限?就是调用了会触发权限的代码,这个时候就会激活对话框,但是如果仅到这里那就 too young too simple 了,当用户点击拒绝授权时,还是可能会回调授权成功的方法。另外,国产机大部分权限是有三个状态——询问、允许、拒绝——大部分权限都是询问状态,但是有些权限默认是允许状态,有些是拒绝状态,这就导致了调用 ContextCompat.checkSelfPermission(Context, String) 方法时会更畸形,例如小米手机的获取 READ_PHONE_STATE 状态,默认是授予状态,但是如果用户手动设置为拒绝之后,就很麻烦了

5.0:此时 google 还未着手处理动态权限申请这么个东西,但是我们牛(sha)逼(bi)的小米、魅族等厂商就开始提前设置了强大的权限管理,所以 6.0 权限申请代码在 5.0 上压根不管用,但是说来也简单,5.0 的权限申请对话框激活就是靠触发危险权限代码,然后根据返回值来判断权限是否获取到了(不同手机的返回值判断方式不同,此处需要一一定制)

6.0 上,permissions4m 首先调用原生申请权限,如果回调的结果是权限成功的话,那么为了确保真的获得到了这个权限,permissions4m 增加了一层应用权限的过程来确保是否真的授权,而在这个过程中权限申请对话框也是会被弹出的

5.0 上,permissions4m 直接通过触发危险代码来激活权限申请对话框,然后根据返回值来判断是否授权成功

当然,当用户完全拒绝权限时,permissions4m 将会回调响应函数返回一个可以跳转至手机管家系统设置的 intent,方便用户打开权限。另外,由于作者个人能力有限,目前仅通过了小米、OPPO、魅族、华为等机型测试,不过这里面的机型已经可以代表许多国产机型的通病了。另外一点就是还有部分涉及到危险权限的代码并未补全,请参考 help me 第二条

项目答疑

1. 同步申请不支持 @PermissionsNonRationale / PermissionPageListener 回调(假设同步申请的权限为 A -> B -> C,那么当 B 被拒时,应该是弹出跳转权限的对话框还是申请权限 C 的对话框?)

2. 为什么不支持权限组申请?国产手机有部分畸形适配,虽然可以以一组权限的格式进行申请授权,但是却可以分别关闭同组权限内单个权限,如果要针对被拒绝的权限做回调,那么代码会显得很冗余。

find job/求职

寻实习一份,请发至我邮箱 [email protected],诚挚感谢

License

Copyright 2016 joker

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.