前言
最近公司的app说装在安卓6.0的系统上程序直接崩溃了,然后呢crash日 志也没有捕获到,感觉好烦人因为公司压根就没有安卓6.0的测试机,最后呢我还是用genymotion来搞,由于用到了so库以前下载的那个jar也不知怎的5.0就运行不通过了,然后今天那到处弄Genymotion-ARM-Translation.zip,最后终于还是可以了。
由于公司是使用eclipse开发的也是醉了,然后呢网上对安卓6.0的权限有了还几个开源框架
相关开源项目
- PermissionsDispatcher
使用标注的方式,动态生成类处理运行时权限,目前还不支持嵌套Fragment。- RxPermissions
基于RxJava的运行时权限检测框架- Grant
简化运行时权限的处理,比较灵活- android-RuntimePermissions
Google官方的例子
使用eclipse和grant开源库权限管理
1. 更新api到23
这里我们选择使用grant来进行项目的演示,因为是使用eclipse来开发,所以还是和android studio里面的还是有很多的区别,我们genymotion的6.0打开 ,因为是6.0系统的开发,所以呢我们的api的target 必须是23,如果不是呢那就麻烦你去update下。
2. 获得v4包
不多扯了,去你的sdk目录下拷贝出v4包放在你工程目录的libs下,不要问为什么因为在接下来的 项目里面需要用到。如下图目录图:
当然我们选择23.1.1下面的android-support-v4.jar,放入你项目的libs下面:
哈哈为什么要这样干呢?因为在里面我们会用到
这样做的话,代码会比较复杂,而在support library v4中,已经为我们准备好了更简单的方法,我们只需要采用library包中的方法来替换上面的方法即可:
- ContextCompat.checkSelfPermission()
不管当前运行的Android版本是多少,该方法都可以正确的方法权限的许可情况,允许或者拒绝.
- ActivityCompat.requestPermissions()
当该方法在6.0之前被调用时,onRequestPermissionsResult回调方法会立即被调用,并返回给你正确的PERMISSION_GRANTED 或者
PERMISSION_DENIED结果.
- ActivityCompat.shouldShowRequestPermissionRationale()
在6.0之前调用该方法,将总会返回false.
切记,你应该总是使用这些方法来取代Activity自身的checkSelfPermission,requestPermissions和shouldShowRequestPermissionsRationale方法.这样,在不同的Android版本中,你的代码将总会完美的运行.
ContextCompat.checkSelfPermission();
ActivityCompat.requestPermissions();上面的方法是v4包下的方法,然后呢低版本的v4包还不具有这样的方法,所以as的项目放在eclipse也是相当的蛋疼。
接下我们就实战项目了,
3. 项目实战开发
不多扯,来看代码:
package com.richerpay.ryshop.permissions;
/**
* Enum class to handle the different states
* of permissions since the PackageManager only
* has a granted and denied state.
*/
enum Permissions {
GRANTED,
DENIED,
NOT_FOUND
}
上面呢就 是权限的类型了,简单翻译下注释就是 用枚举对应3种状态:
已授权,授权失败,未发现的权限。
再看下我们的PermissionsManager.java
package com.richerpay.ryshop.permissions;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* 权限管理类
*/
public class PermissionsManager {
private static final String TAG = PermissionsManager.class.getSimpleName();
private final Set<String> mPendingRequests = new HashSet<String>(1);
private final Set<String> mPermissions = new HashSet<String>(1);
private final List<WeakReference<PermissionsResultAction>> mPendingActions = new ArrayList<WeakReference<PermissionsResultAction>>(1);
private static PermissionsManager mInstance = null;
public static PermissionsManager getInstance() {
if (mInstance == null) {
mInstance = new PermissionsManager();
}
return mInstance;
}
private PermissionsManager() {
initializePermissionsMap();
}
/**
* 此方法使用反射来读取清单类中的所有权限。
*因为一些权限不存在于旧版本的安卓系统中,
*因为他们不存在,他们将被拒绝时,你检查是否有权限
*因为一个新的权限,往往是补充,那里没有以前的
*所需的许可。我们初始化一组可用的权限,并检查组
*检查是否有权限,当我们被拒绝时权限仍然不存在
*
*/
private synchronized void initializePermissionsMap() {
Field[] fields = Manifest.permission.class.getFields();
for (Field field : fields) {
String name = null;
try {
name = (String) field.get("");
} catch (IllegalAccessException e) {
Log.e(TAG, "Could not access field", e);
}
mPermissions.add(name);
}
}
/**
* 此方法检索在应用程序清单中声明的所有权限。
* 它返回一个非空数组,可以声明的权限。
*
* @param 检查我们需要哪些权限
* @return 返回在应用程序清单中声明的非空数组
*/
@NonNull
private synchronized String[] getManifestPermissions(@NonNull final Activity activity) {
PackageInfo packageInfo = null;
List<String> list = new ArrayList<String>(1);
try {
Log.d(TAG, activity.getPackageName());
packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "A problem occurred when retrieving permissions", e);
}
if (packageInfo != null) {
String[] permissions = packageInfo.requestedPermissions;
if (permissions != null) {
for (String perm : permissions) {
Log.d(TAG, "Manifest contained permission: " + perm);
list.add(perm);
}
}
}
return list.toArray(new String[list.size()]);
}
/**
* 在permissionsresultaction对象,它将变更通知这些权限
* @param 权限所需的操作的权限。
* @param 将 动作添加到当前正在执行的动作列表中。
*/
private synchronized void addPendingAction(@NonNull String[] permissions, @Nullable PermissionsResultAction action) {
if (action == null) {
return;
}
action.registerPermissions(permissions);
mPendingActions.add(new WeakReference<PermissionsResultAction>(action));
}
/**
* 删除从队列中等待的动作和执行该动作
* @param 移除动作
*/
private synchronized void removePendingAction(@Nullable PermissionsResultAction action) {
for (Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
iterator.hasNext(); ) {
WeakReference<PermissionsResultAction> weakRef = iterator.next();
if (weakRef.get() == action || weakRef.get() == null) {
iterator.remove();
}
}
}
/**
*这个静态方法可以用来检查你是否有一个特定的权限。
*
* @param 检查权限的上下文对象
* @param 要检查的权限
* @return 返回是否授权了此权限
*/
public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) {
return context != null && (ActivityCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED || !mPermissions.contains(permission));
}
/**
* 这个静态方法可以用来检查你是否有几个特定的权限。
* 为每一个权限,并将简单地返回一个布尔值是否你有所有的权限
* @param 需要检查 权限的上下文
* @param 权限 数组
* @return 返回你是否有所有的权限
*/
public synchronized boolean hasAllPermissions(@Nullable Context context, @NonNull String[] permissions) {
if (context == null) {
return false;
}
boolean hasAllPermissions = true;
for (String perm : permissions) {
hasAllPermissions &= hasPermission(context, perm);
}
return hasAllPermissions;
}
/**
*这种方法的是获取清单里面的所有权限。permissionsresultaction 用于通知允许用户允许或拒绝每一个权限。
*
* @param 需要检查权限的activity
* @param permissionsresultaction用于权限接受通知你
*/
public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullable Activity activity,
final @Nullable PermissionsResultAction action) {
if (activity == null) {
return;
}
String[] perms = getManifestPermissions(activity);
requestPermissionsIfNecessaryForResult(activity, perms, action);
}
/**
*该方法将请求的权限,如果
*他们需要被要求(即我们没有许可),并会增加
* permissionsresultaction到队列被通知的权限被授予或
*否认。在预Android棉花糖的情况下,将立即授予权限。
*活动变量为空,但如果它是无效的,不能执行的方法。
*这是唯一可作为一种礼貌片段,getactivity()可能产量空
*如果该片段没有添加到其父活动中
* @param 请求权限的活动
* @param 权限列表
* @param permissionsresultaction通知当权限授予或拒绝。
*/
public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity,
@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
if (activity == null) {
return;
}
addPendingAction(permissions, action);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
doPermissionWorkBeforeAndroidM(activity, permissions, action);
} else {
List<String> permList = getPermissionsListToRequest(activity, permissions, action);
if (permList.isEmpty()) {
//if there is no permission to request, there is no reason to keep the action int the list
removePendingAction(action);
} else {
String[] permsToRequest = permList.toArray(new String[permList.size()]);
mPendingRequests.addAll(permList);
ActivityCompat.requestPermissions(activity, permsToRequest, 1);
}
}
}
/**
* 该方法将请求的权限,如果
*他们需要被要求(即我们没有许可),并会增加
* permissionsresultaction到队列被通知的权限被授予或
*否认。在预Android棉花糖(6.0)的情况下,将立即授予权限。
*但如果 getactivity()返回null,这方法
*将无法工作作为活动引用,必须检查权限。
* @param 需要检查 权限的fragmnet
* @param 需要请求的权限列表
* @param permissionsresultaction通知当权限授予或拒绝。
*/
public synchronized void requestPermissionsIfNecessaryForResult(@NonNull Fragment fragment,@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
Activity activity = fragment.getActivity();
if (activity == null) {
return;
}
addPendingAction(permissions, action);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
doPermissionWorkBeforeAndroidM(activity, permissions, action);
} else {
List<String> permList = getPermissionsListToRequest(activity, permissions, action);
if (permList.isEmpty()) {
//if there is no permission to request, there is no reason to keep the action int the list
removePendingAction(action);
} else {
String[] permsToRequest = permList.toArray(new String[permList.size()]);
mPendingRequests.addAll(permList);
fragment.requestPermissions(permsToRequest, 1);
}
}
}
/**
* 这个方法通知permissionsmanager,权限已经改变。如果你正在做
*使用活动的权限请求,则应调用此方法,将通知所有悬而未决 的,permissionsresultaction当前对象
*在队列中,并将从挂起的请求列表中删除权限请求。
* @param 已更改的权限。
* @param 每个权限的值
*/
public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) {
int size = permissions.length;
if (results.length < size) {
size = results.length;
}
Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
while (iterator.hasNext()) {
PermissionsResultAction action = iterator.next().get();
for (int n = 0; n < size; n++) {
if (action == null || action.onResult(permissions[n], results[n])) {
iterator.remove();
break;
}
}
}
for (int n = 0; n < size; n++) {
mPendingRequests.remove(permissions[n]);
}
}
/**
* 在安卓设备前要求权限(安卓6,api23),根据权限状态,直接进行或拒绝工作
* @param 检测权限的activity
* @param 权限数组
* @param 我们执行某项操作后权限检查
*/
private void doPermissionWorkBeforeAndroidM(@NonNull Activity activity, @NonNull String[] permissions, @Nullable PermissionsResultAction action) {
for (String perm : permissions) {
if (action != null) {
if (!mPermissions.contains(perm)) {
action.onResult(perm, Permissions.NOT_FOUND);
} else if (ActivityCompat.checkSelfPermission(activity, perm)
!= PackageManager.PERMISSION_GRANTED) {
action.onResult(perm, Permissions.DENIED);
} else {
action.onResult(perm, Permissions.GRANTED);
}
}
}
}
/**
* @param 检查权限的activity
* @param 所有权限的名字
* @param 我们执行某项操作后权限检查
* @return 尚未授予的权限名称列表
*/
@NonNull
private List<String> getPermissionsListToRequest(@NonNull Activity activity,
@NonNull String[] permissions,@Nullable PermissionsResultAction action) {
List<String> permList = new ArrayList<String>(permissions.length);
for (String perm : permissions) {
if (!mPermissions.contains(perm)) {
if (action != null) {
action.onResult(perm, Permissions.NOT_FOUND);
}
} else if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
if (!mPendingRequests.contains(perm)) {
permList.add(perm);
}
} else {
if (action != null) {
action.onResult(perm,Permissions.GRANTED);
}
}
}
return permList;
}
}
接下来我们来看PermissionsResultAction.java
package com.xxxx.xxxx.permissions;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
*将实例传递给 requestpermissionsifnecessaryforresult方法。结 *果将被送回你无论是ongranted(所有的权限已被授予),或ondenied(所需*的权限被拒绝)。
*你把你的ongranted方法和通知功能,用户什么都在ondenied方法不工作。
*/
public abstract class PermissionsResultAction {
private static final String TAG = PermissionsResultAction.class.getSimpleName();
private final Set<String> mPermissions = new HashSet<String>(1);
private Looper mLooper = Looper.getMainLooper();
/**
* Default Constructor
*/
public PermissionsResultAction() {}
/**
*如果您是在后台线程中使用权限请求,则希望
*回调是在UI线程中,通过looper刷新ui
*/
public PermissionsResultAction(@NonNull Looper looper) {mLooper = looper;}
/**
*当所有权限已被用户授予,把所有你的权限敏感的代码,可以只需执行所 需权限
*/
public abstract void onGranted();
/**
*当一个权限被拒绝的时候调用
*
* @param 被拒绝的权限
*/
public abstract void onDenied(String permission);
/**
* 忽视未发现的权限
*/
@SuppressWarnings({})
public synchronized boolean shouldIgnorePermissionNotFound(String permission) {
Log.d(TAG, "Permission not found: " + permission);
return true;
}
/*
*返回授权 结果
*/
@CallSuper
protected synchronized final boolean onResult(final @NonNull String permission, int result) {
if (result == PackageManager.PERMISSION_GRANTED) {
return onResult(permission, Permissions.GRANTED);
} else {
return onResult(permission, Permissions.DENIED);
}
}
/**
*当一个特定的权限已更改。
*此方法将调用所有权限,所以该方法确定
*如果授权影响到该状态,是否可以继续进行
*/
@CallSuper
protected synchronized final boolean onResult(final @NonNull String permission, Permissions result) {
//先从权限列表里移除当前权限
mPermissions.remove(permission);
if (result == Permissions.GRANTED) {
if (mPermissions.isEmpty()) {
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
onGranted();
}
});
return true;
}
} else if (result == Permissions.DENIED) {//权限被拒
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
onDenied(permission);
}
});
return true;
} else if (result == Permissions.NOT_FOUND) {
if (shouldIgnorePermissionNotFound(permission)) {
if (mPermissions.isEmpty()) {//权限为空
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
onGranted();//去授权
}
});
return true;
}
} else {
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
//拒绝权限
onDenied(permission);
}
});
return true;
}
}
return false;
}
/**
*注册指定的权限对象的permissionsresultaction
*让它知道哪些权限来查找更改。
*
* @param permissions名单
*/
@CallSuper
protected synchronized final void registerPermissions(@NonNull String[] perms) {
//把名单加到权限集合里
Collections.addAll(mPermissions, perms);
}
}
好了,最后我们就来使用权限管理:
// 先对app获取所有需要的授权(6.0需要去获取权限)
grantPermissions();
/***
* 获取清单文件中的所有权限
*/
private void grantPermissions() {
@Override public void onGranted() {
Toast.makeText(MainActivity.this,
Toast.LENGTH_SHORT).show();
}
@Override public void onDenied(String permission) {
String message = String.format(
Locale.getDefault(),"Permission \"%1$s\" has been denied", permission); Toast.makeText(MainActivity.this, message,
Toast.LENGTH_SHORT).show();
}});
}
上面呢 我们在程序一开始呢就去授权所有的清单里面的权限,你会看到和ios一样的权限dialog提示,这里呢我们直接来图片展示下。
如下图所示:
接下来我们要去通知权限授权的改变
@Override
public void onRequestPermissionsResult(intrequestCode,@NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions,grantResults);
}
这里呢我们以百度定位的例子来使用:
/**
* 定位信息
*/
private void initLocation() {
LocationClientOption option = new LocationClientOption();
// 可选,默认高精度,设置定位模式,高精度(LocationMode.Hight_Accuracy),
// 低功耗(LocationMode.Battery_Saving),
// 仅设备(LocationMode.Device_Sensors)
option.setLocationMode(LocationMode.Hight_Accuracy);
option.setCoorType("bd09ll");// 可选,默认gcj02,设置返回的定位结果坐标系,
// tempcoor="gcj02";//国家测绘局标准"bd09"//百度墨卡托标准"bd09ll"//百度经纬度标准
// int span=1000;
option.setScanSpan(0);// 可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
option.setIsNeedAddress(true);// 可选,设置是否需要地址信息,默认不需要
option.setOpenGps(true);// 可选,默认false,设置是否使用gps
option.setLocationNotify(true);// 可选,默认false,设置是否当gps有效时按照1S1次频率输出GPS结果
option.setIgnoreKillProcess(false);// 可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
mLocationClient.setLocOption(option);
}
/**
*页面 停止状态下关闭定位
*/
@Override
protected void onStop() {
if (mLocationClient != null) {
mLocationClient.stop();// 界面消失后取消定位
mLocationClient = null;
}
super.onStop();
}
我们先来判断定位权限是否已授权成功:
// 判断是否授权定位权限
boolean hasPermission = PermissionsManager.getInstance().hasPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION);
接下来我们根据是否授权成功来进行相关的操作:
if (hasPermission == false) {//没有授权成功
grantLoactionPermissons();//去授权
}else{//授权成功去定位
mLocationClient = ((RYApplication) getApplication()).mLocationClient;
initLocation();// 定位
mLocationClient.start();// 定位SDK
// start之后会默认发起一次定位请求,开发者无须判断isstart并主动调用request
mLocationClient.requestLocation();
}
根据定位需要的权限去授权
/**
* 授权定位权限
*/
private void grantLoactionPermissons() { PermissionsManager.getInstance().requestPermissionsIfNeces saryForResult(this,new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE },new PermissionsResultAction() { @Override public void onGranted() {
mLocationClient = ((RYApplication) getApplication()).mLocationClient;
initLocation();// 定位
mLocationClient.start();// 定位SDK // start之后会默认发起一次定位请求,开发者无须判断isstart并主动调用
requestmLocationClient.requestLocation();
}
@Override
public void onDenied(String permission) {
}
});
}
当然还有一些权限你无须去判断:
默认许可权限(无需授权)
还有一些权限是在安装的时候默认许可并且不可撤销的,我们把它们叫做普通权限,这些权限如下:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
这些权限和以前一样使用,你无需检查它们是否被撤销了
总结和建议
现在你应该很清楚整个新的权限系统了,同时你也应该知道我们面临的问题了.在Android6.0中动态权限系统已经取代了原有的权限系统,我们别无选择,退无可退,唯一能做的就是去做好适配工作.
好消息是需要进行动态申请的权限是少量的,大部分常用的权限是默认获得许可,并不需要你处理的.总而言之,你只需要修改一小部分代码来完成适配工作.
在这里给出两个建议:
1.优先对重要的部分进行适配,确保不会出现崩溃问题.
2.在完成适配工作前,不要将应用的targetSdkVersion设为23,尤其是在使用AndroidStudio创建新工程的时候,记得手动修改一下工程的targetSdkVersion,因为AndroidStudio会默认使用最新的SDK版本.说到修改代码,我必须承认工作量很大.如果之前的架构设计不合理的话,你可能需要花费一些时间来重新设计了,就像之前说的,我们别无选择,除了去做好它.
我建议你列出应用的功能所依赖的所有的权限,然后考虑如果权限被拒绝,怎样使你的应用的功能尽可能可用,并考虑好如果部分权限被拒绝的情况下,你应该怎么做.恩,这也很复杂,最好能记录好各种情况.
对于安卓6.0的权限运行问题,你可移步Android 6.0 运行时权限处理
好累啊,一不小心到12点,睡觉,加油吧。第一次使用markDown感觉好辛苦,写个东西都