android 6.0以后的运行时权限申请及封装 相关记录
来源:互联网 发布:fps游戏鼠标推荐 知乎 编辑:程序博客网 时间:2024/05/16 06:25
本人最近因为app需求要把部分涉及到系统权限的功能进行更加精准的提示,所以要对权限获取状态进行判断,然后6.0(sdk23)以前的版本没有提供判断的api,所以一直使用自己功能运行情况进行权限判断(如录音使用录音文件内容进行判断)但会出现部分厂商获取的结果不同的情况,幸好6.0以后版本 哥哥提供了部分用于权限判断的api(虽然发现有点问题),赶紧先适配6.0再说。
首先,系统权限分为两类:正常权限(NormalPermissions)和危险权限(DangerousPermissions):1.正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。2.危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
这里说明下上面说发现的问题就是关于这个NormalPermissions的,官方声明:If an app declares in its manifest that it needs a normal permission, the system automatically grants the app that permission at install time. The system does not prompt the user to grant normal permissions, and users cannot revoke these permissions.这里说正常权限系统会自动给予权限,并且不会提示用户进行权限授予,而且用户不能撤销这部分权限,然而你会发现并不是这样,系统会提示用户授权,但不管用户允许还是拒绝,你得到的都是已经获取权限,并且当用户选择Don't ask again选项,提示没了而且哥哥提供的检测是否拒绝和勾选不再询问的api也始终返回false...汗了看来哥哥家里也是有马虎的人,原文见:https://developer.android.com/guide/topics/permissions/normal-permissions.html?hl=zh-cn
下面将贴出上述2种6.0以后修改的权限:
//6.0权限部分中 危险权限 public static final String[] DangerousPermissions = { //group:android.permission-group.CONTACTS Manifest.permission.WRITE_CONTACTS, Manifest.permission.GET_ACCOUNTS, Manifest.permission.READ_CONTACTS, //group:android.permission-group.PHONE Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_CALL_LOG, Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS, Manifest.permission.ADD_VOICEMAIL, //group:android.permission-group.CALENDAR Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, //group:android.permission-group.CAMERA Manifest.permission.CAMERA, //group:android.permission-group.SENSORS Manifest.permission.BODY_SENSORS, //group:android.permission-group.LOCATION Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, //group:android.permission-group.STORAGE Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, //group:android.permission-group.MICROPHONE Manifest.permission.RECORD_AUDIO, //group:android.permission-group.SMS Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH, Manifest.permission.RECEIVE_MMS, Manifest.permission.RECEIVE_SMS, Manifest.permission.SEND_SMS, //permission:android.permission.READ_CELL_BROADCASTS };所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:
1.如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
2.如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。
注:您的应用仍需要明确请求其需要的每项权限,即使用户已向应用授予该权限组中的其他权限。此外,权限分组在将来的 Android 版本中可能会发生变化。您的代码不应依赖特定权限属于或不属于相同组这种假设。
下面就是就是不在服务区的正常权限(我觉得这个最不正常)了:
//6.0权限部分中 普通权限 public static final String[] NormalPermissions = { Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_NOTIFICATION_POLICY, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BROADCAST_STICKY, Manifest.permission.CHANGE_NETWORK_STATE, Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.DISABLE_KEYGUARD, Manifest.permission.EXPAND_STATUS_BAR, Manifest.permission.GET_PACKAGE_SIZE, Manifest.permission.INSTALL_SHORTCUT, Manifest.permission.INTERNET, Manifest.permission.KILL_BACKGROUND_PROCESSES, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.NFC, Manifest.permission.READ_SYNC_SETTINGS, Manifest.permission.READ_SYNC_STATS, Manifest.permission.RECEIVE_BOOT_COMPLETED, Manifest.permission.REORDER_TASKS, Manifest.permission.REQUEST_INSTALL_PACKAGES, Manifest.permission.SET_ALARM, Manifest.permission.SET_TIME_ZONE, Manifest.permission.SET_WALLPAPER, Manifest.permission.SET_WALLPAPER_HINTS, Manifest.permission.TRANSMIT_IR, Manifest.permission.UNINSTALL_SHORTCUT, Manifest.permission.USE_FINGERPRINT, Manifest.permission.VIBRATE, Manifest.permission.WAKE_LOCK, Manifest.permission.WRITE_SYNC_SETTINGS };下面将使用打电话(危险)和wifi(正常)2个权限进行举个栗子。
过程就是 将要进行电话操作->判断权限->根据结果进行提示或是打电话
public void callPhone() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { //没有权限,将进行权限申请 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, requestCode_permission_CALL_PHONE); } else { Intent intent = new Intent(Intent.ACTION_CALL); Uri data = Uri.parse("tel:" + "10086"); intent.setData(data); startActivity(intent); } }然后requestPermissions()的回应对应在onRequestPermissionsResult()进行处理,其中前者第第二个和第三参数分别权限字串和申请识别id,此id用来在后者中进行处理申请权限辨别:
public final int requestCode_permission_ACCESS_WIFI_STATE = 1; public final int requestCode_permission_CALL_PHONE = 2; @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case requestCode_permission_ACCESS_WIFI_STATE: // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { mywifi.openWifi(); Toast.makeText(this, "Permission ACCESS_WIFI_STATE 申请权限成功:", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Permission ACCESS_WIFI_STATE 申请权限失败:", Toast.LENGTH_SHORT).show(); } return; case requestCode_permission_CALL_PHONE: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { //获得权限 callPhone(); } else { Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) { Toast.makeText(this, "Permission CALL_PHONE 被拒绝", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(this, "Permission CALL_PHONE 被拒绝 且设置了 不再询问 ", Toast.LENGTH_SHORT).show(); } return; } } }最后下面就是为了更好的使用而进行的封装了,使用的时候在activity中继承封装的基类,然后按注释进行使用就OK了:
package wjx.helloworld;import android.Manifest;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.os.Build;import android.os.Bundle;import android.util.Log;import android.widget.Toast;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * Created by wjx on 2017/3/23. * 用于6.0 以后(VERSION.SDK>23)的权限判断和申请,本类中处理均为列举,更多需求请自行增加,文字部分请自行用资源文件进行替换 */public abstract class PermActivity extends Activity{ public static final int questCode_ALL_PERMISSIONS = 0xff; public static final int questCode_WRITE_CONTACTS = 0x00; public static final int questCode_GET_ACCOUNTS = 0x01; public static final int questCode_READ_CONTACTS = 0x02; public static final int questCode_READ_CALL_LOG = 0x03; public static final int questCode_READ_PHONE_STATE = 0x04; public static final int questCode_CALL_PHONE = 0x05; public static final int questCode_WRITE_CALL_LOG = 0x06; public static final int questCode_USE_SIP = 0x07; public static final int questCode_PROCESS_OUTGOING_CALLS = 0x08; public static final int questCode_ADD_VOICEMAIL = 0x09; public static final int questCode_READ_CALENDAR = 0x0a; public static final int questCode_WRITE_CALENDAR = 0x0b; public static final int questCode_CAMERA = 0x0c; public static final int questCode_BODY_SENSORS = 0x0d; public static final int questCode_ACCESS_FINE_LOCATION = 0x0e; public static final int questCode_ACCESS_COARSE_LOCATION = 0x0f; public static final int questCode_READ_EXTERNAL_STORAGE = 0x10; public static final int questCode_WRITE_EXTERNAL_STORAGE = 0x11; public static final int questCode_RECORD_AUDIO = 0x12; public static final int questCode_READ_SMS = 0x13; public static final int questCode_RECEIVE_WAP_PUSH = 0x14; public static final int questCode_RECEIVE_MMS = 0x15; public static final int questCode_RECEIVE_SMS = 0x16; public static final int questCode_SEND_SMS = 0x17; public static final int questCode_ACCESS_LOCATION_EXTRA_COMMANDS = 0x20; public static final int questCode_ACCESS_NETWORK_STATE = 0x21; public static final int questCode_ACCESS_NOTIFICATION_POLICY = 0x22; public static final int questCode_ACCESS_WIFI_STATE = 0x23; public static final int questCode_BLUETOOTH = 0x24; public static final int questCode_BLUETOOTH_ADMIN = 0x25; public static final int questCode_BROADCAST_STICKY = 0x26; public static final int questCode_CHANGE_NETWORK_STATE = 0x27; public static final int questCode_CHANGE_WIFI_MULTICAST_STATE = 0x28; public static final int questCode_CHANGE_WIFI_STATE = 0x29; public static final int questCode_DISABLE_KEYGUARD = 0x2a; public static final int questCode_EXPAND_STATUS_BAR = 0x2b; public static final int questCode_GET_PACKAGE_SIZE = 0x2c; public static final int questCode_INSTALL_SHORTCUT = 0x2d; public static final int questCode_INTERNET = 0x2e; public static final int questCode_KILL_BACKGROUND_PROCESSES = 0x2f; public static final int questCode_MODIFY_AUDIO_SETTINGS = 0x30; public static final int questCode_NFC = 0x31; public static final int questCode_READ_SYNC_SETTINGS = 0x32; public static final int questCode_READ_SYNC_STATS = 0x33; public static final int questCode_RECEIVE_BOOT_COMPLETED = 0x34; public static final int questCode_REORDER_TASKS = 0x35; public static final int questCode_REQUEST_INSTALL_PACKAGES = 0x36; public static final int questCode_SET_ALARM = 0x37; public static final int questCode_SET_TIME_ZONE = 0x38; public static final int questCode_SET_WALLPAPER = 0x39; public static final int questCode_SET_WALLPAPER_HINTS = 0x3a; public static final int questCode_TRANSMIT_IR = 0x3b; public static final int questCode_UNINSTALL_SHORTCUT = 0x3c; public static final int questCode_USE_FINGERPRINT = 0x3d; public static final int questCode_VIBRATE = 0x3e; public static final int questCode_WAKE_LOCK = 0x3f; public static final int questCode_WRITE_SYNC_SETTINGS = 0x40; //6.0权限部分中 危险权限 public static final String[] DangerousPermissions = { //group:android.permission-group.CONTACTS Manifest.permission.WRITE_CONTACTS, Manifest.permission.GET_ACCOUNTS, Manifest.permission.READ_CONTACTS, //group:android.permission-group.PHONE Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_CALL_LOG, Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS, Manifest.permission.ADD_VOICEMAIL, //group:android.permission-group.CALENDAR Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, //group:android.permission-group.CAMERA Manifest.permission.CAMERA, //group:android.permission-group.SENSORS Manifest.permission.BODY_SENSORS, //group:android.permission-group.LOCATION Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, //group:android.permission-group.STORAGE Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, //group:android.permission-group.MICROPHONE Manifest.permission.RECORD_AUDIO, //group:android.permission-group.SMS Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH, Manifest.permission.RECEIVE_MMS, Manifest.permission.RECEIVE_SMS, Manifest.permission.SEND_SMS, //permission:android.permission.READ_CELL_BROADCASTS }; //6.0权限部分中 普通权限 public static final String[] NormalPermissions = { Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_NOTIFICATION_POLICY, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BROADCAST_STICKY, Manifest.permission.CHANGE_NETWORK_STATE, Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.DISABLE_KEYGUARD, Manifest.permission.EXPAND_STATUS_BAR, Manifest.permission.GET_PACKAGE_SIZE, Manifest.permission.INSTALL_SHORTCUT, Manifest.permission.INTERNET, Manifest.permission.KILL_BACKGROUND_PROCESSES, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.NFC, Manifest.permission.READ_SYNC_SETTINGS, Manifest.permission.READ_SYNC_STATS, Manifest.permission.RECEIVE_BOOT_COMPLETED, Manifest.permission.REORDER_TASKS, Manifest.permission.REQUEST_INSTALL_PACKAGES, Manifest.permission.SET_ALARM, Manifest.permission.SET_TIME_ZONE, Manifest.permission.SET_WALLPAPER, Manifest.permission.SET_WALLPAPER_HINTS, Manifest.permission.TRANSMIT_IR, Manifest.permission.UNINSTALL_SHORTCUT, Manifest.permission.USE_FINGERPRINT, Manifest.permission.VIBRATE, Manifest.permission.WAKE_LOCK, Manifest.permission.WRITE_SYNC_SETTINGS }; protected String TAG = "BaseActivity_Permission"; public List NormalPermissionsArray = new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); onCreateView(); init(); for (int i=0;i<NormalPermissions.length;i++){ NormalPermissionsArray.add(i,NormalPermissions[i]); } } /** * 用于setContentView(R.id.layout) */ abstract void onCreateView(); /** * 用于处理view的findbyid()和初始化 运行于onCreateView()之后 */ abstract void init(); /** * 判断是否得到某个权限 * <p>暂时对normalpermission无法检测,应该是google那边的bug ,官方声明:If an app declares in its manifest that it needs a normal permission, the system automatically grants the app that permission at install time. The system does not prompt the user to grant normal permissions, and users cannot revoke these permissions. *<b>见https://developer.android.com/guide/topics/permissions/normal-permissions.html?hl=zh-cn</b></p> * @param permissionName 权限名称 eg:Manifest.permission.ACCESS_WIFI_STATE * @param questCode 请求确认id 用于onRequestPermissionsResult()中鉴别请求的序列 * @return true 获得权限 false 获取权限失败 对于normalpermission 暂时返回true 等待修改 */ protected boolean isPermissionGranted(String permissionName,int questCode){ toLog("VERSION.SDK_INT:"+Build.VERSION.SDK_INT+" ? "+Build.VERSION_CODES.M); if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){ return true; } if(NormalPermissionsArray.contains(permissionName)){ //一直得到false.....看来google那边也有马虎人 哈哈 if(shouldShowRequestPermissionRationale(permissionName)){ //权限被拒绝 toLog("check_NormalPermission:"+permissionName+"->true"); }else{ //权限被拒绝且勾选Don't ask again toLog("check_NormalPermission:"+permissionName+"->false"); } return true; } //判断是否需要请求允许权限 注意:normal 权限始终返回0 requestPermissions后不管是否允许也是返回0 拒绝后运行不报错 但无结果 int hasPermision = checkSelfPermission(permissionName); toLog("check_permission:"+permissionName+"->"+hasPermision); if (hasPermision != PackageManager.PERMISSION_GRANTED) { toLog("requestPermissions:"+permissionName+":"+questCode); requestPermissions(new String[] { permissionName }, questCode); return false; } return true; } /** * 判断是否得到某个权限 * @param permArray 权限名称 eg:String[]{Manifest.permission.ACCESS_WIFI_STATE,Manifest.permission.CALL_PHONE} * @param questCode 请求确认id 用于onRequestPermissionsResult()中鉴别请求的序列 */ protected boolean isPermissionsAllGranted(String[] permArray,int questCode){ toLog("VERSION.SDK_INT:"+Build.VERSION.SDK_INT+" ? "+Build.VERSION_CODES.M); if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){ return true; } //获得批量请求但被禁止的权限列表 List<String> deniedPerms = new ArrayList<String>(); for(int i=0;permArray!=null&&i<permArray.length;i++){ if(PackageManager.PERMISSION_GRANTED != checkSelfPermission(permArray[i])){ deniedPerms.add(permArray[i]); } } //进行批量请求 int denyPermNum = deniedPerms.size(); if(denyPermNum != 0){ requestPermissions(deniedPerms.toArray(new String[denyPermNum]),questCode); return false; } return true; } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if(grantResults.length==0){ return; } switch (requestCode) { case questCode_ALL_PERMISSIONS: doPermissionAll(permissions,grantResults); break; case questCode_READ_CALL_LOG: case questCode_READ_PHONE_STATE: case questCode_CALL_PHONE: case questCode_WRITE_CALL_LOG: case questCode_USE_SIP: case questCode_PROCESS_OUTGOING_CALLS: case questCode_ADD_VOICEMAIL: if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { toLog("permissionRequestResult:"+permissions[0]+"->"+grantResults[0]); popAlterDialog("拨打电话","拨打电话权限被禁止,无法使用拨打电话功能。是否开启该权限?(步骤:应用信息->权限->'勾选'电话)"); }else{ toLog("permissionRequestResult:"+permissions[0]+"->"+grantResults[0]); showShortMsg("恭喜,用户已经授予权限:"+permissions[0]); //进行获得权限后的操作 toDoAfterGetPermission(permissions[0]); } break; case questCode_ACCESS_WIFI_STATE: case questCode_CHANGE_WIFI_STATE: if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { toLog("permissionRequestResult:"+permissions[0]+"->"+grantResults[0]); popAlterDialog("无线网络","无线网络权限被禁止,无法使用相关功能。是否开启该权限?(步骤:应用信息->权限->'勾选'网络)"); }else{ toLog("permissionRequestResult:"+permissions[0]+"->"+grantResults[0]); showShortMsg("恭喜,用户已经授予权限:"+permissions[0]); //进行获得权限后的操作 toDoAfterGetPermission(permissions[0]); } break; default: break; } super.onRequestPermissionsResult(requestCode, permissions,grantResults); } /** * 用于处理权限判断失败后进行权限申请,成功之后将要进行的操作 * eg: * <p><b>switch(permission){</p> * <b>case Manifest.permission.CHANGE_WIFI_STATE:<br /> * dosomething();<br /> * break;<br /> * }</b><p> */ abstract void toDoAfterGetPermission(String permission); /** * 批量申请权限 */ private void doPermissionAll(String[] permissions, int[] grantResults) { int grantedPermNum = 0; int totalPermissons = permissions.length; int totalResults = grantResults.length; if(totalPermissons == 0 || totalResults == 0){ return; } Map<String,Integer> permResults = new HashMap<String,Integer>(); //初始化Map容器,用于判断哪些权限被授予 for(String perm : permissions){ permResults.put(perm,PackageManager.PERMISSION_DENIED); } //根据授权的数目和请求授权的数目是否相等来判断是否全部授予权限 for(int i=0;i<totalResults;i++){ permResults.put(permissions[i],grantResults[i]); if(permResults.get(permissions[i]) == PackageManager.PERMISSION_GRANTED){ grantedPermNum ++; } toLog("权限:"+permissions[i]+"-->"+grantResults[i]); } if (grantedPermNum == totalPermissons) { //用于授予全部权限 showShortMsg( "批量申请权限成功!"); }else{ showShortMsg( "批量申请权限失败,将会影响正常使用!"); } } private void showShortMsg(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } private void toLog(String msg) { //用于处理log(可使用自定义log工具进行log分级与记录) Log.d(TAG,msg); } private void popAlterDialog(final String msgFlg, String msgInfo) { new AlertDialog.Builder(PermActivity.this) .setTitle("使用警告") .setMessage(msgInfo) .setNegativeButton("取消", new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton("设置",new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //前往应用详情界面 try { Uri packUri = Uri.parse("package:"+getPackageName()); Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,packUri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PermActivity.this.startActivity(intent); } catch (Exception e) { showShortMsg("跳转失败,请手动跳转进行权限设置..."); } dialog.dismiss(); } }).create().show(); }}参考:https://developer.android.com/training/permissions/requesting.html
- android 6.0以后的运行时权限申请及封装 相关记录
- Android 6.0运行时权限的改变及封装
- android 6.0及以上 运行时权限申请(动态权限申请)
- Android 6.0 权限的申请 与 封装
- Android 6.0运行时权限的申请使用及EasyPermissions的使用
- Android 6.0运行时权限的申请使用及EasyPermissions的使用
- 安卓学习笔记--- Android 6.0运行时权限的申请使用及EasyPermissions的使用
- Android M(6.0)运行时权限申请及遇到的坑
- android 6.0权限申请封装
- android 6.0权限申请封装
- Android 6.0以后权限申请问题
- Android M 6.0以上 需要运行时申请的权限
- Android 6.0的运行时权限 批量申请
- Android 6.0以上 需要运行时申请的权限(一)
- Android 6.0以上 需要运行时申请的权限(二)
- Android 6.0运行时申请权限
- 【Android】6.0 运行时权限申请
- Android 6.0 运行时权限申请
- 使用自定义的类作为Map接口key的参数时
- 【面试题12】打印1到最大的n位数
- namespace
- C++上机实验3—(4)
- 常用命令汇总
- android 6.0以后的运行时权限申请及封装 相关记录
- mongodb API 官方资料
- ERROR [qtp-ambari-client-28] BaseManagementHandler:57
- 第三次实验:项目二
- js中substring和substr的用法
- C++的复习
- [AtCoder2045]Circle and Many Triangles二分答案
- Unity3D StrangeIoc框架类图
- 判断语句