Skip to content

国产手机5.0、6.0权限适配框架/编译时注解框架/an Android Runtime Permissions Tool by using APT

License

Notifications You must be signed in to change notification settings

PengJary/permissions4m

Repository files navigation

apk GitHub stars

platform license

lib processor annotation

中文|ENGLISH

前言

  • 还在为 ActivityCompat.shouldShowRequestPermissionRationale(Activity, String) 无法弹出权限申请对话框困惑么?Permissions4M 让它必须弹出来
  • 还在为明明授权失败,却回调的是权限申请成功方法而苦恼么? Permissions4M 让它必须回调期望的方法
  • 还在为用户拒绝且不再提示权限申请对话框而烦躁么? Permissions4M 让它跳转到手机管家权限设置界面

permissions4m

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

  • 支持 java8
  • 支持一行代码同步请求多个权限
  • 支持多种回调函数,代码可以更简洁

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

  • 支持国产机型适配
  • 支持国产机型5.0权限申请

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

引入依赖

Gradle 依赖

project 中的 build.gradle

buildscript {
  // ...
}

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

app 中的 build.gradle

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

使用文档

版本迭代

  • TODO:help me

  • v2.0.4

    • 支持手动关闭5.0权限申请
    • 修复代理类空指针异常
  • v2.0.0

    • 修复 fragment/support fragment 中低于6.0版本注解回调无效
    • 修复 activity 强制申请回调失效
    • 修复 activity 自定义二次回调失效
    • 增强函数回调方式
    • 增强录音权限申请
    • 适配魅族5.0权限申请
  • v1.1.2

    • 增强小米中的 READ_CONTACTS 申请(国产畸形权限适配)
    • 增强小米中的 PHONE_STATE 申请
    • 增强小米中的 READ_SMS 申请
  • v1.0.9

    • 修复内存泄露
  • v1.0.6

    • 修复代理类空指针异常
  • v1.0.5

    • 低版本默认授权成功
    • 更改最低版兼容 sdk 到11
  • v1.0.3

  • v1.0.2

    • 适配大量国产机型,包括小米、OPPO、华为等
    • 更改为流式 API
    • 支持 Listener 接口回调
    • 截止于第81次 commits
  • v1.0.0

注意事项

必加的二次权限申请回调

在 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();

如:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Permissions4M.get(MainActivity.this)
				.requestPageType(Permissions4M.PageType.MANAGER_PAGE)
                .requestPermissions(Manifest.permission.RECORD_AUDIO)
                .requestCodes(AUDIO_CODE)
                .request();
    }
});

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

@PermissionsGranted

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

  • 单参数:@PermissionsGranted(LOCATION_CODE),被修饰函数可不传入参数,例:
@PermissionsGranted(LOCATION_CODE)
public void granted() {
	ToastUtil.show("地理位置授权成功");
}

传参的话必须是 int 型

@PermissionsGranted(LOCATION_CODE)
public void granted(int code) {
	ToastUtil.show("地理位置授权成功");
}
  • 多参数:@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}),被修饰函数需要传入一个 int 参数,例:
@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void granted(int code) {
    switch (code) {
        case LOCATION_CODE:
            ToastUtil.show("地理位置权限授权成功");
            break;
        case SENSORS_CODE:
            ToastUtil.show("传感器权限授权成功");
            break;
        case CALENDAR_CODE:
            ToastUtil.show("读取日历权限授权成功");
            break;
        default:
            break;
    }
}

@PermissionsDenied

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

  • 单参数:@PermissionsDenied(LOCATION_CODE),被修饰函数可不传入参数,例:
@PermissionsDenied(LOCATION_CODE)
public void denied() {
	ToastUtil.show("地理位置授权失败");
}

传参的话必须是 int 型

@PermissionsDenied(LOCATION_CODE)
public void denied(int code) {
	ToastUtil.show("地理位置授权失败");
}
  • 多参数:@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}),被修饰函数需要传入一个 int 参数,例:
@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void denied(int code) {
    switch (code) {
        case LOCATION_CODE:
            ToastUtil.show("地理位置权限授权失败");
            break;
        case SENSORS_CODE:
            ToastUtil.show("传感器权限授权失败");
            break;
        case CALENDAR_CODE:
            ToastUtil.show("读取日历权限授权失败");
            break;
        default:
            break;
    }
}

@PermissionsRationale

二次授权时回调,用于解释为何需要此权限,注解中需要传入参数,分为两种情况:
  • 单参数:@PermissionsRationale(LOCATION_CODE),被修饰函数可不传入参数,例:
@PermissionsRationale(LOCATION_CODE)
public void rationale() {
	ToastUtil.show("请开启读取地理位置权限");
}

传参的话必须是 int 型

@PermissionsRationale(LOCATION_CODE)
public void denied(int code) {
	ToastUtil.show("请开启读取地理位置权限");
}
  • 多参数:@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}),被修饰函数需要传入一个 int 参数,例:
@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void rationale(int code) {
    switch (code) {
        case LOCATION_CODE:
            ToastUtil.show("请开启地理位置权限授权");
            break;
        case SENSORS_CODE:
            ToastUtil.show("请开启传感器权限授权");
            break;
        case CALENDAR_CODE:
            ToastUtil.show("请开启读取日历权限授权");
            break;
        default:
            break;
    }

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

@PermissionsCustomRationale

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

  • 单参数:@PermissionsCustomRationale(LOCATION_CODE),被修饰函数可不传入参数,例:
@PermissionsCustomRationale(LOCATION_CODE)
public void cusRationale() {
	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();
}

传参的话必须是 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();
}
  • 多参数:@PermissionsCustomRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE}),被修饰函数需要传入一个 int 参数,例:
@PermissionsCustomRationale({SMS_CODE, AUDIO_CODE})
public void cusRationale(int code) {
    switch (code) {
        case SMS_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();
            break;
        case AUDIO_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.RECORD_AUDIO)
                                    .requestCodes(AUDIO_CODE)
                                    .request();
                        }
                    })
                    .show();
            break;
        default:
            break;
    }

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

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

@PermissionsNonRationale

用户太傻逼,拒绝权限不再提示国产畸形权限适配扩展)情况下调用,此时意味着无论是 @PermissionsCustomRationale 或者 @PermissionsRationale 都不会被调用,无法给予用户提示,此时该注解修饰的函数被调用,注解中需要传入参数,分为两种情况:

  • 单参数:@PermissionsNonRationale(LOCATION_CODE),被修饰函数可只传入 Intent 参数,例:
@PermissionsNonRationale({LOCATION_CODE})
public void nonRationale(Intent intent) {
	startActivity(intent);
}

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

@PermissionsNonRationale({LOCATION_CODE})
public void nonRationale(int code, Intent intent) {
	startActivity(intent);
}
  • 多参数:@PermissionsNonRationale(AUDIO_CODE, CALL_LOG_CODE),被修饰函数需传入 int 参数和 Intent 参数,例:
@PermissionsNonRationale({AUDIO_CODE, CALL_LOG_CODE})
public void nonRationale(int code, final Intent intent) {
    switch (code) {
        case AUDIO_CODE:
            new AlertDialog.Builder(MainActivity.this)
                    .setMessage("读取录音权限申请:\n我们需要您开启读取录音权限")
                    .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();
            break;
        case CALL_LOG_CODE:
            new AlertDialog.Builder(MainActivity.this)
                    .setMessage("读取通话记录权限申请:\n我们需要您开启读取通话记录权限")
                    .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();
            break;
        default:
            break;
    }
}

Intent 类型为两种,一种是跳转至系统设置页面,另一种是跳至手机管家页面,而具体的设置方法请参考 注解回调.requestPageType(int) 设置方法。

Listener 回调

例:

Permissions4M.get(MainActivity.this)
	// 是否强制弹出权限申请对话框,建议为 true,默认为 true
	// .requestForce(true)
	// 是否支持 5.0 权限申请,默认为 false
	// .requestUnderM(false)
	// 权限
	.requestPermissions(Manifest.permission.READ_CONTACTS)
	// 权限码
	.requestCodes(READ_CONTACTS_CODE)
	// 权限请求结果
	.requestCallback(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");
    	}
	})
	// 权限完全被禁时回调函数中返回 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();

同步申请

  • 使用 @PermissionsRequestSync 修饰 Activity 或 Fragment

  • 传入两组参数

    • value 数组:请求码
    • permission 数组:请求权限
  • 使用 Permissions4M.get(MainActivity.this).requestSync(); 进行同步权限申请

例:参考 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

help me

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

  • 机型、Android 版本
  • 手机管家设置界面的包名(可使用 AndroidTracker
  • 权限申请的流程(ContextCompat.checkSelfPermission(Context, String) 返回结果是正常结果吗? ActivityCompat.requestPermissions(Activity, String[], int) 能否正常弹出权限申请对话框? Fragment.requestPermissions(String[], int) 能否正常弹出权限申请对话框?ActivityCompat.shouldShowRequestPermissionRationale(Activity, String) 是否是正常结果,还是说跟小米一样,永远只会 false?

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

  • Manifest.permission.GET_ACCOUNTS
  • Manifest.permission.USE_SIP
  • Manifest.permission.ADD_VOICEMAIL
  • Manifest.permission.WRITE_CALENDAR
  • Manifest.permission.CAMERA
  • Manifest.permission.SEND_SMS
  • Manifest.permission.RECEIVE_WAP_PUSH
  • Manifest.permission.RECEIVE_MMS
  • Manifest.permission.RECEIVE_SMS

国产畸形权限适配扩展

  • 小米
  • 目前小米通讯录授权在授权失败时的原因有两种,可能是用户手机中不存在联系人,可能是用户拒绝授权,所以建议针对小米机型应给予相应的提示

原因在于,小米手机在授权拒绝后 Cursor 对象不会为 null(其他手机拒绝权限后为 null),仅会返回一个 getCount() 为0的 Cursor 对象,

  • OPPO

部分机型针对 ContextCompat.checkSelfPermission(Context, String) 判断是根据是否 AndroidManifest.xml 中声明了该权限来决定返回值,如果 AndroidManifest.xml 中声明了该权限,那么就将直接返回已授予权限(但实际上权限可能授予也可能未授予,国产小米和魅族等手机在低于6.0版本以下也是这样设计的),且不一定弹出权限申请对话框。

  • 华为

部分权限的 ActivityCompat.shouldShowRequestPermissionRationale(Activity, String) 返回 false,故权限被拒后将会调用 @PermissionsNonRationale 所修饰的函数

项目答疑

1. Listener 回调暂不支持自定义 Rationale

2. 同步申请不支持 @PermissionsNonRationale 回调(假设同步申请的权限为 A -> B -> C,那么当 B 被拒时,应该是弹出对话框让用户继续申请还是应该继续 C 权限申请?)

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

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.

About

国产手机5.0、6.0权限适配框架/编译时注解框架/an Android Runtime Permissions Tool by using APT

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 100.0%