-
运行时权限
2019-08-23 16:56:06运行时权限, 权限列表 private String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA}; 检查权限 public boolean checkPermission(){ for (String permission:REQUIRED_PERMISSIONS){ if (ContextC...运行时权限,
权限列表private String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA};
检查权限在onCreate里调用
public boolean checkPermission(){ for (String permission:REQUIRED_PERMISSIONS){ if (ContextCompat.checkSelfPermission(getApplicationContext(),permission) != PackageManager.PERMISSION_GRANTED){ return false; } } return true; }
请求权限
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
在onRequestPermissionsResult 再次检查权限,没有权限直接退出
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.d("mile","onRequestPermissionsResult"); if (!checkPermission()){ finish(); }
全部代码
public class MainActivity extends AppCompatActivity implements LifecycleOwner { private static int REQUEST_CODE_PERMISSIONS = 10; private String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA}; private TextureView viewFinder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewFinder = findViewById(R.id.view_finder); if (!checkPermission()){ ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS); } } @Override public Lifecycle getLifecycle() { return super.getLifecycle(); } public boolean checkPermission(){ for (String permission:REQUIRED_PERMISSIONS){ if (ContextCompat.checkSelfPermission(getApplicationContext(),permission) != PackageManager.PERMISSION_GRANTED){ return false; } } return true; } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.d("mile","onActivityResult"); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.d("mile","onRequestPermissionsResult"); if (!checkPermission()){ finish(); } } }
-
Android 6.0 运行时权限管理最佳实践
2016-09-11 13:11:36在Android M中权限系统被重新设计,发生了颠覆性的变化,很多人把握...这里有一切关于Android运行时权限你需要知道的,包括如何在代码中实现,如果你以前不知道这些东西,现在我将在详解之后给你一个最佳的实践方案。版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com
这是一篇迟来的博客,Android M已经发布一年多了(6.0的变化),在Android M中权限系统被重新设计,发生了颠覆性的变化,很多人把握不好这个变化,一是对这个权限策略和套路还没有摸透,二是没有一个很好的实践来支撑,在我的技术开发群里很多人问我关于权限的问题,往往我都没有直接回答,因为这个问题不是一两句说的清楚的,这几点是今天我写这篇博客的原因。这里有一切关于Android运行时权限你需要知道的,包括如何在代码中实现,如果你以前不知道这些东西,现在来看也为时不晚,我将在详解之后给你一个最佳的实践方案。
由于项目一直在更新,更多新内容请直接看AndPermission开源主页:
https://github.com/yanzhenjie/AndPermissionAndPermission能解决大部分国产机遇到的权限问题,请参考:国产手机权限适配方案
AndPermission特性
- 链式调用,一句话申请权限,为你省去复杂的逻辑判断。
- 支持注解回调结果、支持Listener回调结果。
- 拒绝一次某权限后,再次申请该权限时可使用
Rationale
向用户说明申请该权限的目的,在用户同意后再继续申请,避免用户勾选不再提示而导致不能再次申请该权限。 - 就算用户拒绝权限并勾选不再提示,可使用
SettingDialog
提示用户去设置中授权。 RationaleDialog
和SettingDialog
允许开发者自定义。AndPermission
自带默认对话框除可自定义外,也支持国际化。- 支持在任何地方申请权限,不仅限于
Activity
和Fragment
等。
如果你的英文够好,推荐你阅读官网的文章:
关于运行时权限
在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装(某些深度定制系统另说),App一旦安装后就可以偷偷的做一些不为人知的事情了。
在Android6.0开始,App可以直接安装,App在运行时一个一个询问用户授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog不能由开发者定制),当App需要用户授予不恰当的权限的时候,用户可以拒绝,用户也可以在设置页面对每个App的权限进行管理。
特别注意:这个对话框不是开发者调用某个权限的功能时由系统自动弹出,而是需要开发者手动调用,如果你直接调用而没有去申请权限的话,将会导致App崩溃。
也许你已经开始慌了,这对于用户来说是好事,但是对于开发者来说我们不能直接调用方法了,我们不得不在每一个需要权限的地方检查并请求用户授权,所以就引出了以下两个问题。
哪些权限需要动态申请
新的权限策略讲权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写等。
不需要运行时申请的权限
此类权限都是正常保护的权限,只需要在AndroidManifest.xml中简单声明这些权限即可,安装即授权,不需要每次使用时都检查权限,而且用户不能取消以上授权,除非用户卸载App。
- 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_IGNORE_BATTERY_OPTIMIZATIONS
- 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
需要运行时申请的权限
所有危险的Android系统权限属于权限组,如果APP运行在
Android 6.0 (API level 23)
或者更高级别的设备中,而且targetSdkVersion>=23
时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限权限而直接调用了相关代码,你的App可能就崩溃了,综上所述你需要注意:- 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
- 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦
WRITE_CONTACTS
被授权了,App也有READ_CONTACTS
和GET_ACCOUNTS
了。 - 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请
READ_EXTERNAL_STORAGE
,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"
。
如果App运行在
Android 5.1 (API level 22)
或者更低级别的设备中,或者targetSdkVersion<=22
时(此时设备可以是Android 6.0 (API level 23)
或者更高),在所有系统中仍将采用旧的权限管理策略,系统会要求用户在安装的时候授予权限。其次,系统就告诉用户App需要什么权限组,而不是个别的某个权限。- CALENDAR(日历)
- READ_CALENDAR
- WRITE_CALENDAR
- CAMERA(相机)
- CAMERA
- CONTACTS(联系人)
- READ_CONTACTS
- WRITE_CONTACTS
- GET_ACCOUNTS
- LOCATION(位置)
- ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
- MICROPHONE(麦克风)
- RECORD_AUDIO
- PHONE(手机)
- READ_PHONE_STATE
- CALL_PHONE
- READ_CALL_LOG
- WRITE_CALL_LOG
- ADD_VOICEMAIL
- USE_SIP
- PROCESS_OUTGOING_CALLS
- SENSORS(传感器)
- BODY_SENSORS
- SMS(短信)
- SEND_SMS
- RECEIVE_SMS
- READ_SMS
- RECEIVE_WAP_PUSH
- RECEIVE_MMS
- STORAGE(存储卡)
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
使用adb命令可以查看这些需要授权的权限组:
adb shell pm list permissions -d -g
使用adb命令同样可以授权/撤销某个权限:
adb shell pm [grant|revoke] <permission-name>...
关于运行时权限的一些建议
只请求你需要的权限,减少请求的次数,或用隐式Intent来让其他的应用来处理。
- 如果你使用Intent,你不需要设计界面,由第三方的应用来完成所有操作。比如打电话、选择图片等。
- 如果你请求权限,你可以完全控制用户体验,自己定义UI。但是用户也可以拒绝权限,就意味着你的应用不能执行这个特殊操作。
防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:
- 普通权限(Normal PNermissions):只需要在Androidmanifest.xml中声明相应的权限,安装即许可。
- 需要运行时申请的权限(Dangerous Permissions):
- 必要权限:最好在应用启动的时候,进行请求许可的一些权限(主要是应用中主要功能需要的权限)。
- 附带权限:不是应用主要功能需要的权限(如:选择图片时,需要读取SD卡权限)。
解释你的应用为什么需要这些权限:在你调用
requestPermissions()
之前,你为什么需要这个权限。- 例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在
requestpermissions()
之前告诉用户你为什么需要这个权限。
- 例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在
使用兼容库
support-v4
中的方法
ContextCompat.checkSelfPermission() ActivityCompat.requestPermissions() ActivityCompat.shouldShowRequestPermissionRationale()
几个重要的方法与常量解释
PackageManager中的两个常量:
- PackageManager.PERMISSION_DENIED:该权限是被拒绝的。
- PackageManager.PERMISSION_GRANTED:该权限是被授权的。
Activity中或者Fragment都会有以下几个方法:
int checkSelfPermission(String) void requestPermissions(int, String...) boolean shouldShowRequestPermissionRationale(String) void onRequestPermissionsResult()
上述四个方法中,前三个方法在
support-v4
的ActivityCompat
中都有,建议使用兼容库中的方法。最后一个方法是用户授权或者拒绝某个权限组时系统会回调Activity或者Fragment中的方法。checkSelfPermission() 检查权限
- 检查某一个权限的当前状态,你应该在请求某个权限时检查这个权限是否已经被用户授权,已经授权的权限重复申请可能会让用户产生厌烦。
- 该方法有一个参数是权限名称,有一个int的返回值,用这个值与上面提到的两个常量做比较可判断检查的权限当前的状态。
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // 没有权限,申请权限。 }else{ // 有权限了,去放肆吧。 }
requestPermissions() 申请权限
- 请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,App不能配置和修改这个对话框,如果需要提示用户这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理,该方法有两个参数:
- int requestCode,会在回调
onRequestPermissionsResult()
时返回,用来判断是哪个授权申请的回调。 - String[] permissions,权限数组,你需要申请的的权限的数组。
- int requestCode,会在回调
- 由于该方法是异步的,所以无返回值,当用户处理完授权操作时,会回调Activity或者Fragment的
onRequestPermissionsResult()
方法。
对于Activity我们直接调用
requestPermissions(int, String[])
即可,不过这个方法是在api leve 23以上,所以我们为了适配可以是使用兼容包提供的方法:ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
对于support包的
Fragment
就可以直接调用requestPermissions(int, String[])
,对于app包的Fragment
就需要做版本判断了,这样就显得比较麻烦。onRequestPermissionsResult() 处理权限结果回调
- 该方法在Activity/Fragment中应该被重写,当用户处理完授权操作时,系统会自动回调该方法,该方法有三个参数:
- int requestCode,在调用
requestPermissions()
时的第一个参数。 - String[] permissions,权限数组,在调用
requestPermissions()
时的第二个参数。 - int[] grantResults,授权结果数组,对应permissions,具体值和上方提到的PackageManager中的两个常量做比较。
- int requestCode,在调用
@Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MMM: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限被用户同意,可以去放肆了。 } else { // 权限被用户拒绝了,洗洗睡吧。 } return; } } }
shouldShowRequestPermissionRationale()
- 望文生义,是否应该显示请求权限的说明。
- 第一次请求权限时,用户拒绝了,调用
shouldShowRequestPermissionRationale()
后返回true,应该显示一些为什么需要这个权限的说明。 - 用户在第一次拒绝某个权限后,下次再次申请时,授权的dialog中将会出现“不再提醒”选项,一旦选中勾选了,那么下次申请将不会提示用户。
- 第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项,调用
shouldShowRequestPermissionRationale()
后返回false。 - 设备的策略禁止当前应用获取这个权限的授权:
shouldShowRequestPermissionRationale()
返回false 。 - 加这个提醒的好处在于,用户拒绝过一次权限后我们再次申请时可以提醒该权限的重要性,免得再次申请时用户勾选“不再提醒”并决绝,导致下次申请权限直接失败。
综上所述,整合代码后:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // 没有权限。 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) { // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。 } else { // 申请授权。 ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM); } } ... @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MMM: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限被用户同意,可以去放肆了。 } else { // 权限被用户拒绝了,洗洗睡吧。 } return; } } }
总结
从上面来看,判断很多,逻辑也很多,这样就加重了我们开发的负担,加上很多人反馈说国产手机有各种各样的bug,这样兼容起来就更加麻烦了,那么下面我就为大家介绍一个开源内裤来解决这一系列问题。
AndPermission
这个开源库名叫AndPermission:https://github.com/yanzhenjie/AndPermission,经过我的实践是完全解决了上述问题,推荐大家使用。
- Gradle
compile 'com.yanzhenjie:permission:1.0.6'
- Maven
<dependency> <groupId>com.yanzhenjie</groupId> <artifactId>permission</artifactId> <version>1.0.5</version> <type>pom</type> </dependency>
- Eclipse 请放弃治疗
使用介绍
我建议看官去Github下载
Demo
并阅读本文会帮助你理解。申请权限
// 在Activity: AndPermission.with(activity) .requestCode(100) .permission(Manifest.permission.WRITE_CONTACTS) .rationale(...) .callback(...) .start(); // 在Fragment: AndPermission.with(fragment) .requestCode(100) .permission( // 多个权限,以数组的形式传入。 Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_SMS ) .rationale(...) .callback(...) .start(); // 在其它任何地方: AndPermission.with(context) .requestCode(100) .permission( Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_SMS ) .rationale(...) .callback(...) .start();
接受回调结果
接受回调结果目前有两种方式:一、
Listener
方式,二、注解方式。方式一:Listener方式回调
在
callback()
方法传入PermissionListener
即可,授权成功或者失败至少会回调其中一个方法。AndPermission.with(context) ... .requestCode(200) .callback(listener) .start(); private PermissionListener listener = new PermissionListener() { @Override public void onSucceed(int requestCode, List<String> grantedPermissions) { // 权限申请成功回调。 // 这里的requestCode就是申请时设置的requestCode。 // 和onActivityResult()的requestCode一样,用来区分多个不同的请求。 if(requestCode == 200) { // TODO ... } } @Override public void onFailed(int requestCode, List<String> deniedPermissions) { // 权限申请失败回调。 if(requestCode == 200) { // TODO ... } } };
方式二:注解方式回调
在
callback()
方法传入你的回调方法所在实例的对象即可。AndPermission.with(context) ... .requestCode(300) .callback(this) .start(); // 成功回调的方法,用注解即可,这里的300就是请求时的requestCode。 @PermissionYes(300) private void getPermissionYes(List<String> grantedPermissions) { // TODO 申请权限成功。 } @PermissionNo(300) private void getPermissionNo(List<String> deniedPermissions) { // TODO 申请权限失败。 }
如果你会用了,你就可以大刀阔斧的干了,博客中讲到的各种复杂逻辑,
AndPermission
自动完成。Rationale能力
Android
运行时权限有一个特点,在拒绝过一次权限后,再此申请该权限,在申请框会多一个[不再提示]的复选框,当用户勾选了[不再提示]并拒绝了权限后,下次再申请该权限将直接回调申请失败。
因此Rationale
功能是在用户拒绝一次权限后,再次申请时检测到已经申请过一次该权限了,允许开发者弹窗说明申请权限的目的,获取用户的同意后再申请权限,避免用户勾选不再提示,导致不能再次申请权限。方式一:使用AndPermssion默认MD风格对话框
AndPermission.with(this) ... .requestCode(...) .rationale((requestCode, rationale) -> // 此对话框可以自定义,调用rationale.resume()就可以继续申请。 AndPermission.rationaleDialog(context, rationale).show() ) .start()
方式二:自定义对话框
AndPermission.with(this) ... .requestCode(...) .rationale(rationaleListener) .start() /** * Rationale支持,这里自定义对话框。 */ private RationaleListener rationaleListener = (requestCode, rationale) -> { AlertDialog.newBuilder(this) .setTitle("友好提醒") .setMessage("你已拒绝过定位权限,沒有定位定位权限无法为你推荐附近的妹子,你看着办!") .setPositiveButton("好,给你", (dialog, which) -> { rationale.resume(); }) .setNegativeButton("我拒绝", (dialog, which) -> { rationale.cancel(); }).show(); };
提示用户在系统设置中授权
当用户拒绝权限并勾选了不再提示时,此时再次申请权限时将会直接回调申请失败,因此
AndPermission
提供了一个供用户在系统Setting
中给我们授权的能力。我们在授权失败的回调方法中添加如下代码,以下三种选择一种即可:
// 是否有不再提示并拒绝的权限。 if (AndPermission.hasAlwaysDeniedPermission(activity, deniedPermissions)) { // 第一种:用AndPermission默认的提示语。 AndPermission.defaultSettingDialog(activity, 400).show(); // 第二种:用自定义的提示语。 AndPermission.defaultSettingDialog(activity, 400) .setTitle("权限申请失败") .setMessage("您拒绝了我们必要的一些权限,已经没法愉快的玩耍了,请在设置中授权!") .setPositiveButton("好,去设置") .show(); // 第三种:自定义dialog样式。 SettingService settingService = AndPermission.defineSettingDialog(activity, 400); ... // 你的dialog点击了确定调用: settingService.execute(); // 你的dialog点击了取消调用: settingService.cancel(); }
如果你是在
Activity/Fragment
中调用的上述代码,那么当用户在系统Setting
中操作完成后,会回调Activity/Fragment
中的这个方法:@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case 400: { // 这个400就是你上面传入的数字。 // 你可以在这里检查你需要的权限是否被允许,并做相应的操作。 break; } } }
混淆
- 如果使用
Listener
接受回调结果,不用任何配置。 - 使用注解的方式回调结果,在
proguard
中添加如下配置:
-keepclassmembers class ** { @com.yanzhenjie.permission.PermissionYes <methods>; } -keepclassmembers class ** { @com.yanzhenjie.permission.PermissionNo <methods>; }
国产手机适配方案
AndPermission是严格按照
Android
系统的运行时权限
设计的,并最大限度上兼容了国产手机,目前发现的国产手机bug及解决方案:- 部分中国厂商生产手机(例如小米某型号)的
Rationale
功能,在第一次拒绝后,第二次申请时不会返回true
,并且会回调申请失败,也就是说在第一次决绝后默认勾选了不再提示
,所以建议一定使用SettingDialog
:提示用户在系统设置中授权。 - 部分中国厂商生产手机(例如小米、华为某型号)在申请权限时,用户点击确定授权后,还是回调我们申请失败,这个时候其实我们是拥有权限的,所以我们可以在失败的方法中使用
AppOpsManager
进行权限判断,AndPermission
已经封装好了:
if(AndPermission(context, permission1, permission2)) { // 执行操作。 }
- 部分中国厂商生产手机(例如vivo、pppo某型号)在用户允许权限,并且回调了权限授权陈功的方法,但是实际执行代码时并没有这个权限,建议开发者在回调成功的方法中也利用
AppOpsManager
判断下:
if(AndPermission(context, permission1, permission2)) { // 执行操作。 } else { // 提醒用户手机问题,请用户去Setting中授权。这里可以使用AndPermission的SettingDialog。 }
- 部分开发者反馈,在某些手机的
Setting
中授权后实际,检查时还是没有权限,部分执行代码也是没有权限,这种手机真的兼容不到了,我也觉得没必要兼容了,建议直接放弃这种平台。
最后希望咱中国Android手机厂商早日修复这些问题,祝你们事业越来越成功,产品越做越好。
版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com
-
Android 6.0运行时权限讲解
2016-12-28 09:30:00本节课中会对Android 6.0系统中新增的运行时权限功能进行一次透彻的讲解,包含运行时权限的引入背景、基本用法、佳封装实践等,还会讲一些很多人并不知道的运行时权限微技巧。 -
Android RuntimePermissions运行时权限:单个运行时权限申请简例
2017-09-19 23:10:29Android RuntimePermissions运行时权限:单个运行时权限申请简例Android运行时权限申请的框架结构和步骤比较简单和固定,一般现状代码启动后检查当前的Android SDK版本是否大于等于23,在SDK版本大于等于23时候,才...Android RuntimePermissions运行时权限:单个运行时权限申请简例
Android运行时权限申请的框架结构和步骤比较简单和固定,一般现状代码启动后检查当前的Android SDK版本是否大于等于23,在SDK版本大于等于23时候,才启动运行时权限申请。在过去的版本,直接在AndroidManifest.xml写进去申请即可。
在检查当前代码未获得相应权限时候,进而使用标准方式requestPermissions,然后在回到函数onRequestPermissionsResult里面处理用户授权的结果。
给出一个简单例子,以相机权限为例。在AndroidManifest添加相机权限:<uses-permission android:name="android.permission.CAMERA"/>
然后在MainActivity.java处理运行时权限申请逻辑:package zhangphil.permission; import android.Manifest; import android.content.pm.PackageManager; import android.os.Build; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private final String TAG = "ZHANG PHIL"; private final int PERMISSION_REQUEST = 0xa01; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST); } } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { if (permissions != null && permissions.length > 0) { for (String s : permissions) Log.d(TAG + "权限列表", s + ""); } switch (requestCode) { case PERMISSION_REQUEST: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "授权获得"); } else { Log.d(TAG, "授权未获得"); } break; } } }
代码运行在Android SDK高于或等于23时候有效。代码运行后如图:点击 总是运行 后log日志输出:
09-19 23:11:47.557 31470-31470/zhangphil.permission D/ZHANG PHIL权限列表: android.permission.CAMERA 09-19 23:11:47.557 31470-31470/zhangphil.permission D/ZHANG PHIL: 授权获得
-
Android运行时权限终极方案,用PermissionX吧
2020-05-19 08:03:13没有人愿意编写处理Android运行时权限的代码,因为它真的太繁琐了。 这是一项没有什么技术含量,但是你又不得不去处理的工作,因为不处理它程序就会崩溃。但如果处理起来比较简单也就算了,可事实上,Android提供给...本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。
各位小伙伴们大家早上好,不知道你的《第三行代码》已经读到哪里了?
有些朋友的阅读速度真是令人印象深刻,我记得在《第三行代码》刚刚发售一周不到的时间里,竟然就有人已经读到第9章了(因为公众号后台有人回复第9章里隐藏的关键字)。现在,《第三行代码》已经出版一个月有余了,相信已经有不少朋友将全本书都看完了。
全书都看完的朋友一定知道,《第三行代码》的最后一章是带着大家一起开发了一个开源库:PermissionX。这一章的主旨是为了让你了解一个开源库整体的开发与发布过程,为了更好地演示这个过程,我想到了去写PermissionX这样一个库。
不过,书中PermissionX库的整体功能还是比较简单的,因为这一章的重点不在于如何将开源库做得完善与强大,而是强调的一个开发与发布的过程。
但是后来,我觉得PermissionX确实可以做成一个真正用于简化Android运行时权限处理的库,它所存在的意义应该不仅限于书中的教学目的,而是可以真的应用到实际的项目当中,帮助大家解决处理运行时权限的痛点。
所以,后期我又对PermissionX进行了诸多功能拓展,现在已经达到对外发布的标准了,那么今天正式向大家宣布:PermissionX已经上线!
开源库的地址是:https://github.com/guolindev/PermissionX
痛点在哪里?
没有人愿意编写处理Android运行时权限的代码,因为它真的太繁琐了。
这是一项没有什么技术含量,但是你又不得不去处理的工作,因为不处理它程序就会崩溃。但如果处理起来比较简单也就算了,可事实上,Android提供给我们的运行时权限API并不友好。
以一个拨打电话的功能为例,因为CALL_PHONE权限是危险权限,所以在我们除了要在AndroidManifest.xml中声明权限之外,还要在执行拨打电话操作之前进行运行时权限处理才行。
权限声明如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.permissionx.app"> <uses-permission android:name="android.permission.CALL_PHONE" /> ... </manifest>
然后,编写如下代码来进行运行时权限处理:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) makeCallBtn.setOnClickListener { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) { call() } else { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1) } } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 1 -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { call() } else { Toast.makeText(this, "You denied CALL_PHONE permission", Toast.LENGTH_SHORT).show() } } } } private fun call() { try { val intent = Intent(Intent.ACTION_CALL) intent.data = Uri.parse("tel:10086") startActivity(intent) } catch (e: SecurityException) { e.printStackTrace() } } }
这段代码中真有正意义的功能逻辑就是call()方法中的内容,可是如果直接调用call()方法是无法实现拨打电话功能的,因为我们还没有申请CALL_PHONE权限。
那么整段代码其他的部分就都是在处理CALL_PHONE权限申请。可以看到,这里需要先判断用户是否已授权我们拨打电话的权限,如果没有的话则要进行权限申请,然后还要在onRequestPermissionsResult()回调中处理权限申请的结果,最后才能去执行拨打电话的操作。
你可能觉得,这也不算是很繁琐呀,代码量并不是很多。那是因为,目前我们还只是处理了运行时权限最简单的场景,而实际的项目环境中有着更加复杂的场景在等着我们。
比如说,你的App可能并不只是单单申请一个权限,而是需要同时申请多个权限。虽然ActivityCompat.requestPermissions()方法允许一次性传入多个权限名,但是你在onRequestPermissionsResult()回调中就需要判断哪些权限被允许了,哪些权限被拒绝了,被拒绝的权限是否影响到应用程序的核心功能,以及是否要再次申请权限。
而一旦牵扯到再次申请权限,就引出了一个更加复杂的问题。你申请的权限被用户拒绝过了一次,那么再次申请将很有可能再次被拒绝。为此,Android提供了一个shouldShowRequestPermissionRationale()方法,用于判断是否需要向用户解释申请这个权限的原因,一旦shouldShowRequestPermissionRationale()方法返回true,那么我们最好弹出一个对话框来向用户阐明为什么我们是需要这个权限的,这样可以增加用户同意授权的几率。
是不是已经觉得很复杂了?不过还没完,Android系统还提供了一个“拒绝,不要再询问”的选项,如下图所示:
只要用户选择了这个选项,那么我们以后每次执行权限申请的代码都将会直接被拒绝。
可是如果我的某项功能就是必须要依赖这个权限才行呢?没有办法,你只能提示用户去应用程序设置当中手动打开权限,程序方面已无法进行操作。
可以看出,如果想要在项目中对运行时权限做出非常全面的处理,是一件相当复杂的事情。事实上,大部分的项目都没有将权限申请这块处理得十分恰当,这也是我编写PermissionX的理由。
PermissionX的实现原理
在开始介绍PermissionX的具体用法之前,我们先来讨论一下它的实现原理。
其实之前并不是没有人尝试过对运行时权限处理进行封装,我之前在做直播公开课的时候也向大家演示过一种运行时权限API的封装过程。
但是,想要对运行时权限的API进行封装并不是一件容易的事,因为这个操作是有特定的上下文依赖的,一般需要在Activity中接收onRequestPermissionsResult()方法的回调才行,所以不能简单地将整个操作封装到一个独立的类中。
为此,也衍生出了一系列特殊的封装方案,比如将运行时权限的操作封装到BaseActivity中,或者提供一个透明的Activity来处理运行时权限等。
不过上述两种方案都不够轻量,因为改变Activity的继承结构这可是大事情,而提供一个透明的Activty则需要在AndroidManifest.xml中进行额外的声明。
现在,业内普遍比较认可使用另外一种小技巧来进行实现。是什么小技巧呢?回想一下,之前所有申请运行时权限的操作都是在Activity中进行的,事实上,Android在Fragment中也提供了一份相同的API,使得我们在Fragment中也能申请运行时权限。
但不同的是,Fragment并不像Activity那样必须有界面,我们完全可以向Activity中添加一个隐藏的Fragment,然后在这个隐藏的Fragment中对运行时权限的API进行封装。这是一种非常轻量级的做法,不用担心隐藏Fragment会对Activity的性能造成什么影响。
这就是PermissionX的实现原理了,书中其实也已经介绍过了这部分内容。但是,在其实现原理的基础之上,后期我又增加了很多新功能,让PermissionX变得更加强大和好用,下面我们就来学习一下PermissionX的具体用法。
基本用法
要使用PermissionX之前,首先需要将其引入到项目当中,如下所示:
dependencies { ... implementation 'com.permissionx.guolindev:permissionx:1.1.1' }
我在写本篇文章时PermissionX的最新版本是1.1.1,想要查看它的当前最新版本,请访问PermissionX的主页:https://github.com/guolindev/PermissionX
PermissionX的目的是为了让运行时权限处理尽可能的容易,因此怎么让API变得简单好用就是我优先要考虑的问题。
比如同样实现拨打电话的功能,使用PermissionX只需要这样写:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) makeCallBtn.setOnClickListener { PermissionX.init(this) .permissions(Manifest.permission.CALL_PHONE) .request { allGranted, grantedList, deniedList -> if (allGranted) { call() } else { Toast.makeText(this, "您拒绝了拨打电话权限", Toast.LENGTH_SHORT).show() } } } } ... }
是的,PermissionX的基本用法就这么简单。首先调用init()方法来进行初始化,并在初始化的时候传入一个FragmentActivity参数。由于AppCompatActivity是FragmentActivity的子类,所以只要你的Activity是继承自AppCompatActivity的,那么直接传入this就可以了。
接下来调用permissions()方法传入你要申请的权限名,这里传入CALL_PHONE权限。你也可以在permissions()方法中传入任意多个权限名,中间用逗号隔开即可。
最后调用request()方法来执行权限申请,并在Lambda表达式中处理申请结果。可以看到,Lambda表达式中有3个参数:allGranted表示是否所有申请的权限都已被授权,grantedList用于记录所有已被授权的权限,deniedList用于记录所有被拒绝的权限。
因为我们只申请了一个CALL_PHONE权限,因此这里直接判断:如果allGranted为true,那么就调用call()方法,否则弹出一个Toast提示。
运行结果如下:
怎么样?对比之前的写法,是不是觉得运行时权限处理没那么繁琐了?
核心用法
然而我们目前还只是处理了最普通的场景,刚才提到的,假如用户拒绝了某个权限,在下次申请之前,我们最好弹出一个对话框来向用户解释申请这个权限的原因,这个又该怎么实现呢?
别担心,PermissionX对这些情况进行了充分的考虑。
onExplainRequestReason()方法可以用于监听那些被用户拒绝,而又可以再次去申请的权限。从方法名上也可以看出来了,应该在这个方法中解释申请这些权限的原因。
而我们只需要将onExplainRequestReason()方法串接到request()方法之前即可,如下所示:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason { deniedList -> } .request { allGranted, grantedList, deniedList -> if (allGranted) { Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show() } }
这种情况下,所有被用户拒绝的权限会优先进入onExplainRequestReason()方法进行处理,拒绝的权限都记录在deniedList参数当中。接下来,我们只需要在这个方法中调用showRequestReasonDialog()方法,即可弹出解释权限申请原因的对话框,如下所示:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason { deniedList -> showRequestReasonDialog(deniedList, "即将重新申请的权限是程序必须依赖的权限", "我已明白", "取消") } .request { allGranted, grantedList, deniedList -> if (allGranted) { Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show() } }
showRequestReasonDialog()方法接受4个参数:第一个参数是要重新申请的权限列表,这里直接将deniedList参数传入。第二个参数则是要向用户解释的原因,我只是随便写了一句话,这个参数描述的越详细越好。第三个参数是对话框上确定按钮的文字,点击该按钮后将会重新执行权限申请操作。第四个参数是一个可选参数,如果不传的话相当于用户必须同意申请的这些权限,否则对话框无法关闭,而如果传入的话,对话框上会有一个取消按钮,点击取消后不会重新进行权限申请,而是会把当前的申请结果回调到request()方法当中。
另外始终要记得将所有申请的权限都在AndroidManifest.xml中进行声明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.permissionx.app"> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CALL_PHONE" /> ... </manifest>
重新运行一下程序,效果如下图所示:
当前版本解释权限申请原因对话框的样式还无法自定义,1.3.0版本当中已支持了自定义权限提醒对话框样式的功能,详情请参阅 PermissionX重磅更新,支持自定义权限提醒对话框 。
当然,我们也可以指定要对哪些权限重新申请,比如上述申请的3个权限中,我认为CAMERA权限是必不可少的,而其他两个权限则可有可无,那么在重新申请的时候也可以只申请CAMERA权限:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION) .onExplainRequestReason { deniedList -> val filteredList = deniedList.filter { it == Manifest.permission.CAMERA } showRequestReasonDialog(filteredList, "摄像机权限是程序必须依赖的权限", "我已明白", "取消") } .request { allGranted, grantedList, deniedList -> if (allGranted) { Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show() } }
这样当再次申请权限的时候就只会申请CAMERA权限,剩下的两个权限最终会被传入到request()方法的deniedList参数当中。
解决了向用户解释权限申请原因的问题,接下来还有一个头疼的问题要解决:如果用户不理会我们的解释,仍然执意拒绝权限申请,并且还选择了拒绝且不再询问的选项,这该怎么办?通常这种情况下,程序层面已经无法再次做出权限申请,唯一能做的就是提示用户到应用程序设置当中手动打开权限。
那么PermissionX是如何处理这种情况的呢?我相信绝对会给你带来惊喜。PermissionX中还提供了一个onForwardToSettings()方法,专门用于监听那些被用户永久拒绝的权限。另外从方法名上就可以看出,我们可以在这里提醒用户手动去应用程序设置当中打开权限。代码如下所示:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason { deniedList -> showRequestReasonDialog(deniedList, "即将重新申请的权限是程序必须依赖的权限", "我已明白", "取消") } .onForwardToSettings { deniedList -> showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白", "取消") } .request { allGranted, grantedList, deniedList -> if (allGranted) { Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show() } }
可以看到,这里又串接了一个onForwardToSettings()方法,所有被用户选择了拒绝且不再询问的权限都会进行到这个方法中处理,拒绝的权限都记录在deniedList参数当中。
接下来,你并不需要自己弹出一个Toast或是对话框来提醒用户手动去应用程序设置当中打开权限,而是直接调用showForwardToSettingsDialog()方法即可。类似地,showForwardToSettingsDialog()方法也接收4个参数,每个参数的作用和刚才的showRequestReasonDialog()方法完全一致,我这里就不再重复解释了。
showForwardToSettingsDialog()方法将会弹出一个对话框,当用户点击对话框上的我已明白按钮时,将会自动跳转到当前应用程序的设置界面,从而不需要用户自己慢慢进入设置当中寻找当前应用了。另外,当用户从设置中返回时,PermissionX将会自动重新请求相应的权限,并将最终的授权结果回调到request()方法当中。效果如下图所示:
同样,1.3.0版本也支持了自定义这个对话框样式的功能,详情请参阅 PermissionX重磅更新,支持自定义权限提醒对话框 。
更多用法
PermissionX最主要的功能大概就是这些,不过我在使用一些App的时候发现,有些App喜欢在第一次请求权限之前就先弹出一个对话框向用户解释自己需要哪些权限,然后才会进行权限申请。这种做法是比较提倡的,因为用户同意授权的概率会更高。
那么PermissionX中要如何实现这样的功能呢?
其实非常简单,PermissionX还提供了一个explainReasonBeforeRequest()方法,只需要将它也串接到request()方法之前就可以了,代码如下所示:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .explainReasonBeforeRequest() .onExplainRequestReason { deniedList -> showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白") } .onForwardToSettings { deniedList -> showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白") } .request { allGranted, grantedList, deniedList -> if (allGranted) { Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show() } }
这样,当每次请求权限时,会优先进入onExplainRequestReason()方法,弹出解释权限申请原因的对话框,用户点击我已明白按钮之后才会执行权限申请。效果如下图所示:
不过,你在使用explainReasonBeforeRequest()方法时,其实还有一些关键的点需要注意。
第一,单独使用explainReasonBeforeRequest()方法是无效的,必须配合onExplainRequestReason()方法一起使用才能起作用。这个很好理解,因为没有配置onExplainRequestReason()方法,我们怎么向用户解释权限申请原因呢?
第二,在使用explainReasonBeforeRequest()方法时,如果onExplainRequestReason()方法中编写了权限过滤的逻辑,最终的运行结果可能和你期望的会不一致。这一点可能会稍微有点难理解,我用一个具体的示例来解释一下。
观察如下代码:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .explainReasonBeforeRequest() .onExplainRequestReason { deniedList -> val filteredList = deniedList.filter { it == Manifest.permission.CAMERA } showRequestReasonDialog(filteredList, "摄像机权限是程序必须依赖的权限", "我已明白") } ...
这里在onExplainRequestReason()方法中编写了刚才用到的权限过滤逻辑,当有多个权限被拒绝时,我们只重新申请CAMERA权限。
在没有加入explainReasonBeforeRequest()方法时,一切都可以按照我们所预期的那样正常运行。但如果加上了explainReasonBeforeRequest()方法,在执行权限请求之前会先进入onExplainRequestReason()方法,而这里将除了CAMERA之外的其他权限都过滤掉了,因此实际上PermissionX只会请求CAMERA这一个权限,剩下的权限将完全不会尝试去请求,而是直接作为被拒绝的权限回调到最终的request()方法当中。
效果如下图所示:
针对于这种情况,PermissionX在onExplainRequestReason()方法中提供了一个额外的beforeRequest参数,用于标识当前上下文是在权限请求之前还是之后,借助这个参数在onExplainRequestReason()方法中执行不同的逻辑,即可很好地解决这个问题,示例代码如下:
PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .explainReasonBeforeRequest() .onExplainRequestReason { deniedList, beforeRequest -> if (beforeRequest) { showRequestReasonDialog(deniedList, "为了保证程序正常工作,请您同意以下权限申请", "我已明白") } else { val filteredList = deniedList.filter { it == Manifest.permission.CAMERA } showRequestReasonDialog(filteredList, "摄像机权限是程序必须依赖的权限", "我已明白") } } ...
可以看到,当beforeRequest为true时,说明此时还未执行权限申请,那么我们将完整的deniedList传入showRequestReasonDialog()方法当中。
而当beforeRequest为false时,说明某些权限被用户拒绝了,此时我们只重新申请CAMERA权限,因为它是必不可少的,其他权限则可有可无。
最终运行效果如下:
Permission-Support
这个库的名字叫PermissionX,因此不用多说,它肯定是与AndroidX兼容的。以防还有部分朋友不清楚AndroidX是什么的,这里有一篇我之前写的科普文章 总是听到有人说AndroidX,到底什么是AndroidX?
但是,我相信现在仍然存在很多项目没有使用AndroidX,而是在继续使用着之前的Android Support Library。为此,我又专门提供了一份面向Android Support Library的版本:Permission-Support。
在用法层面,两个版本没有任何区别,本文以上讨论的所有内容在Permission-Support上都适用。只是在引用库的时候,如果你准备使用Permission-Support,请使用以下依赖库地址:
dependencies { ... implementation 'com.permissionx.guolindev:permission-support:1.1.1' }
不过,Android Support Library注定将会在不久的将来被Google完全淘汰,因此Permission-Support我也不会维护太久的时间,只是暂时过渡一下。而PermissionX我是准备长期维护下去的,并会持续增加更多好用的新功能。
后记
最后,一定也会有朋友想要询问,Java语言的项目能不能使用PermissionX呢?
从1.2.2版本开始,PermissionX加入了对Java语言的支持。不过为了能够兼容Java的语法,PermissionX在用法方法稍微做了一点点调整,详情可以参考这篇文章 PermissionX现在支持Java了!还有Android 11权限变更讲解 。
新库刚刚发布,可能还存在很多我自己没能测出来的bug,也请大家帮忙多多测试,共同将这个库变得更加完善。
再次贴上PermissionX的开源库地址,欢迎大家star和fork。
https://github.com/guolindev/PermissionX
另外,如果你想学习Kotlin语言或Android 10、Jetpack等最新的Android知识,可以阅读我的新书:《第一行代码——Android 第3版》,详情点击这里查看。
关注我的技术公众号,每天都有优质技术文章推送。
微信扫一扫下方二维码即可关注:
-
AndPermission 运行时权限
2016-09-18 10:38:57Android6.0 运行时权限管理最佳实践详解。 -
Android 6.0运行时权限Demo
2016-09-12 13:43:53Android 6.0运行时权限Demo -
Android 运行时权限
2017-04-27 08:07:47Android 运行时权限 Android开发团队在Android 6.0系统中引用了运行时权限这个功能,从而更好的保护了用户的安全和隐私. 用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某... -
Android运行时权限
2017-08-03 19:02:24Android运行时权限 危险权限和危险权限组 原生API申请及封装 EasyPermission申请 PermissionsDispatcher申请 -
Android 6.0 运行时权限处理完全解析
2016-02-22 09:31:58转载请标明出处: ...随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个主要的变化,查看查看官网的这篇文章http://develope -
RxPermissions 获取运行时权限
2017-11-08 15:14:36RxPermissions 获取运行时权限 -
RxPermissions获取运行时权限
2018-12-19 16:32:07所以android6.0+使用扫描功能之前,必须先获取运行时权限 2.关于运行时权限 Android6.0+添加了运行时权限分为两类: 一类是Normal Permissions,这类权限不涉及个人隐私,不需要用户进行授权,比如手机震动,访问... -
6.0运行时权限
2017-02-22 16:22:34运行时权限是在6.0之后引用的,起到的作用是什么呐?就是一些软件会申请很多很多的权限,但是有很多权限在这个软件上也用不到,但是你又不得不同意,否则只能选择不安装。但是有了运行时权限,用户在安装软件的时候... -
android运行时权限
2018-05-17 15:51:54但是那时候的权限机制对于保护用的安全和隐私起到的作用有限,需要一些权限都是写在AndroidManifest.xml中,用户安装应用的实现提示必须同意这些权限,为此在android 6.0系统中提出了运行时权限的功能,更好的保护了... -
Android 6.0 运行时权限
2017-05-01 21:05:301、运行时权限定义android6.0 运行时权限是指抛弃以前我们在manifest.xml文件中一次性赋予app权限,转而在程序真正运行时授予权限(需要用户同意),如果没有授予权限,则出现异常的一种机制。6.0之前的权限模型 在... -
Android6.0运行时权限
2017-06-17 15:21:27一、运行时权限所谓运行时权限,指的是在Android6.0及以上版本中,在app运行时才请求相关权限,从而让每项权限都在用户知情的情况下被授权(当然用户可以拒绝)。而不同于Android6.0之前在安装app时告知用户获取全部... -
Android RuntimePermissions运行时权限:批量权限申请
2017-09-25 11:16:06Android RuntimePermissions运行时权限:批量权限申请绝大多数情况一个APP不可能只有单个权限,往往需要运行时批量申请n多个权限。比如在Androidmanifest里面写了两个权限: 那么在运行时申请权限,要点是把全部... -
Android-运行时权限
2017-06-15 20:35:20在记事本应用中插入图片时,参考网上的知识来获取手机中的图片,然而报错说 权限需要在运行时获取,然后发现在Android6.0之后,加入了运行时权限,以保障用户的隐私定义Android的权限机制从第一个版本就有了,一些软件... -
Android动态获取运行时权限RxPermissions
2017-05-09 15:26:42Android动态获取运行时权限RxPermissions新版的Android权限控制更加严格,一般需要在APP的运行时动态获取,如果按照谷歌官方的方法比葫芦画瓢获取动态运行时权限,代码比较繁琐,如果和业务逻辑再搅和在一起,代码的... -
Android 6.0运行时权限
2017-09-28 15:21:52一、运行时权限的变化及特点在6.0以下的系统,安装的时候有一个权限列表,一般情况下点了安装就都给应用授权了相应的权限。在6.0以上,我们安装APP也是直接安装了,不用管权限,一些比较隐私的权限,安装的时候是... -
Android6.0 运行时权限
2016-04-28 19:26:19Android6.0 运行时权限 Runtime Permission -
Android运行时权限分析
2017-07-18 15:13:41首先,来讲一讲运行时权限。从Android 6.0开始,Google引入了运行时权限。Android中的权限分为两类:Normal Permission和Dangerous Permission。Normal Permission(即普通权限,一般不涉及到用户隐私),比如访问网络... -
【Android开发技巧】android6.0动态获取权限,运行时权限处理
2018-01-08 11:03:47随着Android6.0的发布,也带来了一些变革,这里主要说的就是在Android6.0里面运行时权限的处理。 关于运行时权限 在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装... -
Android 6.0 运行时权限处理
2017-06-08 10:29:06运行时权限介绍 Android 6.0在我们原有的AndroidManifest.xml声明权限的基础上, 又新增了运行时权限动态检测,以下权限都需要在运行时判断: 身体传感器 日历 摄像头 通讯录 地理位置 麦克风 电话 ... -
Android 6.0运行时权限处理
2016-12-30 14:48:33运行时权限介绍Android 6.0在原有的AndroidManifest.xml声明权限的基础上增加了动态申请运行时权限。新增的运行时权限有 身体传感器、日历、摄像头、通讯录、地理位置、麦克风、电话、短信、SD卡存储。运行时权限...
-
nodejs作为中间层的实践「详细介绍」
-
PyCharm 2020.3.2 专业版,永久使用!(亲测可行)
-
c++ 邻接数组实现四种图类
-
vue.js---webpack(三)
-
1443:【例题4】Addition Chains
-
腾讯精选50题—Day5题目23,26,33
-
STM32F4工程--IO口--寄存器详解
-
HZ视频配音配字幕工具v1.0版本一键配音配字幕.zip
-
01.05. 一次编辑
-
最新人教版五年级下册语文第七单元基础过关知识整理.doc
-
常用专业词
-
Fisher线性判别.pdf
-
thinkphp5.1博客后台实战视频
-
21年新MybatisPlus+Swagger3.x+SpringBo
-
redis模型及pipeline
-
国家注册渗透测试工程师(Web安全)
-
【Day 2】跟着Google学Go——A Tour of Go
-
Kotlin协程极简入门与解密
-
最新人教版五年级下册语文第五单元基础过关知识整理.doc
-
(新)备战2021软考网络规划设计师培训学习套餐