如今谈论Android权限适配可能有点不必,由于网上关于权限适配的文章不少,搜一下Android6.0权限适配关键词能搜到一堆文章,并且不少写的还很不错。不过本身想了想仍是总结一下,由于那些文章都是别人的,不是本身的,以前一直想总结一下,可是一直没作,今天就简单记录一下,方便之后查阅,也对Android6.0的权限机制再次进行一次全面的认识。
从Android M开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。这样更友好的让用户选择,当真正须要权限的时候再去申请权限,而不是Android M以前在安装时一会儿去申请。javascript
正常权限不会直接给用户隐私权带来风险。若是您的应用在其清单中列出了正常权限,系统将自动授予该权限。而不须要咱们去请求权限。html
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复制代码
危险权限涵盖应用须要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其余应用的操做产生影响的区域。例如,可以读取用户的联系人属于危险权限。若是应用声明其须要危险权限,则用户必须明确向应用授予该权限。java
在危险权限中,咱们须要了解一个权限组的概念,全部危险的 Android 系统权限都属于权限组,若是应用请求其清单中列出的危险权限,而应用目前在权限组中没有拥有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。android
例如咱们须要读取获取手机卡imsi,此时须要请求权限READ_PHONE_STATE,发现此时提示框也展现了请求打电话权限。(系统只告诉用户应用须要的权限组,而不告知具体权限)其实READ_PHONE_STATE和打电话权限CALL_PHONE都属于一个权限组PHONE,若是咱们此时容许了权限,那么下次再其余地方使用了打电话权限时系统将当即授予该权限。
git
权限组 | 权限 |
---|---|
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 |
/** * 肯定权限是否已经被授予 * @param permission 被检测权限的名字. * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 若是权限被授予, * {@link android.content.pm.PackageManager#PERMISSION_DENIED} 若是权限被拒绝返回值. */
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
/** * 是否显示自定义UI提示用户 * 华为手机测试 第一次使用时返回false * 若是拒绝返回true * 若是拒绝并点击不在提醒返回false * 已经赞成过权限,但在设置拒绝此时返回true * 没有赞成过权限,在设置中开启并拒绝权限返回false * @param activity 请求权限Activity. * @param permission 须要请求的权限. * @return 是否显示自定义对话框提示用户. */
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission)
/** * 给应用申请权限,申请的权限必须在manifest文件注册,正常权限在安装时自动被受权,不须要使用此方法请求权限 * 请求以后会弹出系统提示框,供咱们选择是拒绝仍是容许,点击后 * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult( * int, String[], int[])} 方法将会被回调, * @param activity 请求权限的Activity. * @param permissions 须要请求的权限. * @param requestCode 指定一个请求码,用于区别返回结果 * */
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode)
/** * 调用requestPermissions方法请求权限的回调 *须要注意的是可能请求的权限与用户互动中断;正在这种状况下回调将接收一个空的permissions和grantResults数组 * @param permissions 请求的权限. 不为null,长度可能为0. * @param grantResults 请求权限的结果PERMISSION_GRANTED表示权限被容许,PERMISSION_DENIED表示权限被拒绝 */
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults)复制代码
须要注意的是,对于若是在Activity中请求权限则可以使用上面API ActivityCompat类,若是在Frament请求权限则,须要使用Fragment类中的对应方法,不然回调会有问题。github
/** * 判断是否具有全部权限 * * @param permissions 全部权限 * @return true 具备全部权限 false没有具备全部权限,此时包含未授予的权限 */
public static boolean isHasPermissions(String... permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return true;
for (String permission : permissions) {
if (!isHasPermission(permission))
return false;
}
return true;
}
/** * 判断该权限是否已经被授予 * * @param permission * @return true 已经授予该权限 ,false未授予该权限 */
private static boolean isHasPermission(String permission) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return true;
return ContextCompat.checkSelfPermission(MyApplication.getAppContext(), permission) == PackageManager.PERMISSION_GRANTED;
}
/** * 请求权限,经测试发现TabActivity管理Activity时,在Activity中请求权限时须要传入父Activity对象,即TabActivity对象 * 并在TabActivity管理Activity中重写onRequestPermissionsResult并分发到子Activity,不然回调不执行 。TabActivity回调中 调用getLocalActivityManager().getCurrentActivity().onRequestPermissionsResult(requestCode, permissions, grantResults);分发到子Activity * * * @param object Activity or Fragment * @param requestCode 请求码 * @param permissions 请求权限 */
public static void requestPermissions(Object object, int requestCode, String... permissions) {
ArrayList<String> arrayList = new ArrayList<>();
for (String permission : permissions) {
if (!isHasPermissions(permission)) {
arrayList.add(permission);
}
}
if (arrayList.size() > 0) {
if (object instanceof Activity) {
Activity activity = (Activity) object;
Activity activity1 = activity.getParent() != null && activity.getParent() instanceof TabActivity ? activity.getParent() : activity;
ActivityCompat.requestPermissions(activity1, arrayList.toArray(new String[]{}), requestCode);
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
//当Fragment嵌套Fragment时使用getParentFragment(),而后在父Fragment进行分发,不然回调不执行
Fragment fragment1 = fragment.getParentFragment() != null ? fragment.getParentFragment() : fragment;
fragment1.requestPermissions(arrayList.toArray(new String[]{}), requestCode);
} else {
throw new RuntimeException("the object must be Activity or Fragment");
}
}
}复制代码
若是想展现自定义UI友好的提示用户申请该权限的缘由,则须要使用shouldShowRequestPermissionRationale方法,简要封装以下api
public static boolean shouldShowRequestPermissionRationale(@NonNull Object object, String... permissions) {
for (String permission : permissions) {
if (object instanceof Activity) {
if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, permission)) {
return true;
}
} else if (object instanceof Fragment) {
if(((Fragment) object).shouldShowRequestPermissionRationale(permission)){
return true;
}
} else {
throw new RuntimeException("the object must be Activity or Fragment");
}
}
return false;
}
/** * 二次申请权限时,弹出自定义提示对话框 * * @param activity * @param message * @param iPermissionRequest * @see com.example.xh.ui.BaiduLocationFragment 能够查看该类onRequestPermissionsResult方法当选择永不提醒时的处理办法。 */
public static void showDialog(Activity activity, String message, final IPermissionRequest iPermissionRequest) {
new AlertDialog.Builder(activity)
.setPositiveButton("容许", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
iPermissionRequest.agree();
dialog.dismiss();
}
})
.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
iPermissionRequest.refuse();
dialog.dismiss();
}
})
.setCancelable(false)
.setMessage(message)
.show();
}复制代码
弹出对话框后,点击了拒绝或者容许后,给一个回调,方便进行不一样的处理,固然若是统一处理的话,就不须要写接口,直接在上述点击容许的时候请求权限,点击不容许的时候,显示一个Toast再次作下权限拒绝提示。固然也可在onRequestPermissionsResult中进行判断,当选中永不提醒后给用户一个友好跳转到权限设置界面。
接口方法数组
public interface IPermissionRequest {
void agree();
void refuse();
}复制代码
有许多权限其行为方式与正常权限及危险权限都不一样。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特别敏感,所以大多数应用不该该使用它们。若是某应用须要其中一种权限,必须在清单中声明该权限,而且发送请求用户受权的 intent(注意特殊权限和危险权限请求方式不同)。系统将向用户显示详细管理屏幕,以响应该 intent。app
/** * 测试请求WRITE_SETTINGS权限 */
@OnClick(R.id.request_write_setting)
@TargetApi(android.os.Build.VERSION_CODES.M)
public void requestWriteSetting() {
if (!Settings.System.canWrite(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, requestCodeWriteSetting);
} else {
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 已经被受权", Toast.LENGTH_SHORT).show();
}
}
@TargetApi(Build.VERSION_CODES.M)
private void showToast() {
if (Settings.System.canWrite(this)) {
//检查返回结果
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 被受权", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 没有被受权", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == requestCodeWriteSetting) {
showToast();
}else if(requestCode==requestCodeAlertWindow){
showToastAlerterWindow();
}
}复制代码
/** * 测试请求SYSTEM_ALERT_WINDOW权限 */
@OnClick(R.id.request_alert_window)
@TargetApi(android.os.Build.VERSION_CODES.M)
public void requestAlertWindow() {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings. ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, requestCodeAlertWindow);
} else {
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 已经被受权", Toast.LENGTH_SHORT).show();
}
}
@TargetApi(Build.VERSION_CODES.M)
private void showToastAlerterWindow() {
if (Settings.System.canWrite(this)) {
//检查返回结果
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 被受权", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 没有被受权", Toast.LENGTH_SHORT).show();
}
}复制代码
注意:权限必须在清单文件中声明,不然进入上面界面时开关是不可点击的灰色。ide
在上面危险权限申请中,若是用户拒绝了权限,而且选中永不提醒,那么下次请求权限时直接执行onRequestPermissionsResult回调,而且返回状态是权限被拒绝状态,那么若想授予权限,必须去手机的权限管理中设置,若是用户去手机里找是否是很麻烦,何况一步人不知道设置权限的地方在哪,那么为了程序的体验更好,咱们能够在咱们的应用中引导用户跳转到设置权限的界面。实现代码以下
/** * 打开应用权限设置界面 */
@OnClick(R.id.open_permission_setting)
public void requestOpenPermissionSetting() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
// Uri uri = Uri.fromParts("package", getPackageName(), null);
Uri uri1=Uri.parse("package:" + getPackageName());
intent.setData(uri1);
startActivity(intent);
}复制代码
介绍到此就结束了,水平有限如有问题请指出,Hava a wonderful day.
最后放几篇感受不错的文章:
Android6.0权限适配之WRITE_EXTERNAL_STORAGE(SD卡写入)
本次征文活动的连接: juejin.im/post/58d8e9…