本文原创,转载请注明出处
别说话,看正文
6.0之后,敏感权限需要申请,没有做封装之前,每次用到一个敏感权限,都需要写权限判断,然后申请,写回调,处理回调,繁琐至极
自己也封装过几次,每次都没封装完全,总有些纰漏,有一次集成环信的时候看到官方demo里面封装了一个权限请求的包,就深入了解了一番,感觉很不错,现在抽个时间记下来,方便以后使用
思路比较清晰,大致分如下几步
1.读取AndroidManifest.xml中的权限申明
2.遍历,挑选出没有被授权的权限(!= PackageManager.PERMISSION_GRANTED)
3.通过Looper(循环器)将没有获得授权的权限一个一个进行申请
整个封装由3个类组成,一个枚举,一个PermissionsManager ,和一个抽象类PermissionsResultAction 这三个类放在代码最后面,复制到项目中即可,以下是用法
在MainActivity中加入方法,然后在onCreate中调用
@TargetApi(23)
private void requestPermissions() {
PermissionsManager.getInstance()
.requestAllManifestPermissionsIfNecessary(this,new PermissionsResultAction(){
@Override
public void onGranted() {
//
}
@Override
public void onDenied(String permission) {
//
}
});
}
调用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
requestPermissions();
}
到此本应该结束了,但是不知道处于什么原因,这个onGranted(),和onDenied(String permission)是失效的,原因没有深究;这个也没多大影响,我们可以通过Activity的onRequestPermissionsResult方法来接收这些权限请求的结果回调,简单处理代码如下
@TargetApi(23)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions
, @NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean isShow = false;//是否有未被授权并且被勾选为不再提醒的权限
for (String str : permissions){this,str)
== PackageManager.PERMISSION_GRANTED){
Log.i(TAG, "申请结果 = 用户授权 " + str);
}else {
if (shouldShowRequestPermissionRationale(str)){
Log.i(TAG, "申请结果 = 用户拒绝授权(可再次申请) " + str);
}else {
isShow = true;
Log.i(TAG, "申请结果 = 用户拒绝授权(不可再次申请) " + str);
}
}
}
if (isShow){
AlertDialog dialog = new AlertDialog.Builder(this)
.setMessage("部分重要权限未获得授权,这将有可能会导致程序在某些情况下出现BUG,
如果想要避免这样的情况发生,请前往设置中心为本应用打开这些权限")
.setNegativeButton("知道了,不理", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
})
.setPositiveButton("现在就去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//这里跳转到已安装应用列表
Intent intent = new Intent(
Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
startActivity(intent);
}
})
.create();
dialog.show();
}
}
到这里就已经看完了,如果不想深究其原理,下面三个类直接复制进项目即可(害怕翻译有误,注释直接贴的源码)
1.枚举Permissions
enum Permissions {
GRANTED,
DENIED,
NOT_FOUND
}
2.PermissionsManager
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();
}
/**
* This method uses reflection to read all the permissions in the Manifest class.
* This is necessary because some permissions do not exist on older versions of Android,
* since they do not exist, they will be denied when you check whether you have permission
* which is problematic since a new permission is often added where there was no previous
* permission required. We initialize a Set of available permissions and check the set
* when checking if we have permission since we want to know when we are denied a permission
* because it doesn't exist yet.
*/
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);
}
}
/**
* This method retrieves all the permissions declared in the application's manifest.
* It returns a non null array of permisions that can be declared.
*
* @param activity the Activity necessary to check what permissions we have.
* @return a non null array of permissions that are declared in the application manifest.
*/
@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()]);
}
/**
* This method adds the {@link PermissionsResultAction} to the current list
* of pending actions that will be completed when the permissions are
* received. The list of permissions passed to this method are registered
* in the PermissionsResultAction object so that it will be notified of changes
* made to these permissions.
*
* @param permissions the required permissions for the action to be executed.
* @param action the action to add to the current list of pending actions.
*/
private synchronized void addPendingAction(@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
if (action == null) {
return;
}
action.registerPermissions(permissions);
mPendingActions.add(new WeakReference<PermissionsResultAction>(action));
}
/**
* This method removes a pending action from the list of pending actions.
* It is used for cases where the permission has already been granted, so
* you immediately wish to remove the pending action from the queue and
* execute the action.
*
* @param action the action to remove
*/
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();
}
}
}
/**
* This static method can be used to check whether or not you have a specific permission.
* It is basically a less verbose method of using {@link ActivityCompat#checkSelfPermission(Context, String)}
* and will simply return a boolean whether or not you have the permission. If you pass
* in a null Context object, it will return false as otherwise it cannot check the permission.
* However, the Activity parameter is nullable so that you can pass in a reference that you
* are not always sure will be valid or not (e.g. getActivity() from Fragment).
*
* @param context the Context necessary to check the permission
* @param permission the permission to check
* @return true if you have been granted the permission, false otherwise
*/
@SuppressWarnings("unused")
public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) {
return context != null && (ActivityCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED || !mPermissions.contains(permission));
}
/**
* This static method can be used to check whether or not you have several specific permissions.
* It is simpler than checking using {@link ActivityCompat#checkSelfPermission(Context, String)}
* for each permission and will simply return a boolean whether or not you have all the permissions.
* If you pass in a null Context object, it will return false as otherwise it cannot check the
* permission. However, the Activity parameter is nullable so that you can pass in a reference
* that you are not always sure will be valid or not (e.g. getActivity() from Fragment).
*
* @param context the Context necessary to check the permission
* @param permissions the permissions to check
* @return true if you have been granted all the permissions, false otherwise
*/
@SuppressWarnings("unused")
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;
}
/**
* This method will request all the permissions declared in your application manifest
* for the specified {@link PermissionsResultAction}. The purpose of this method is to enable
* all permissions to be requested at one shot. The PermissionsResultAction is used to notify
* you of the user allowing or denying each permission. The Activity and PermissionsResultAction
* parameters are both annotated Nullable, but this method will not work if the Activity
* is null. It is only annotated Nullable as a courtesy to prevent crashes in the case
* that you call this from a Fragment where {@link Fragment#getActivity()} could yield
* null. Additionally, you will not receive any notification of permissions being granted
* if you provide a null PermissionsResultAction.
*
* @param activity the Activity necessary to request and check permissions.
* @param action the PermissionsResultAction used to notify you of permissions being accepted.
*/
@SuppressWarnings("unused")
public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullable Activity activity,
final @Nullable PermissionsResultAction action) {
if (activity == null) {
return;
}
String[] perms = getManifestPermissions(activity);
requestPermissionsIfNecessaryForResult(activity, perms, action);
}
/**
* This method should be used to execute a {@link PermissionsResultAction} for the array
* of permissions passed to this method. This method will request the permissions if
* they need to be requested (i.e. we don't have permission yet) and will add the
* PermissionsResultAction to the queue to be notified of permissions being granted or
* denied. In the case of pre-Android Marshmallow, permissions will be granted immediately.
* The Activity variable is nullable, but if it is null, the method will fail to execute.
* This is only nullable as a courtesy for Fragments where getActivity() may yeild null
* if the Fragment is not currently added to its parent Activity.
*
* @param activity the activity necessary to request the permissions.
* @param permissions the list of permissions to request for the {@link PermissionsResultAction}.
* @param action the PermissionsResultAction to notify when the permissions are granted or denied.
*/
@SuppressWarnings("unused")
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) {
Log.i("xxx --- > ", "requestPermissionsIfNecessaryForResult: api < 23");
doPermissionWorkBeforeAndroidM(activity, permissions, action);
} else {
Log.i("xxx --- > ", "requestPermissionsIfNecessaryForResult: api >= 23");
List<String> permList = getPermissionsListToRequest(activity, permissions, action);
if (permList.isEmpty()) {
Log.i("xxx --- > ", "如果没有请求的权限,则没有理由将该操作保持在列表中");
//if there is no permission to request, there is no reason to keep the action int the list
removePendingAction(action);
} else {
Log.i("xxx --- > ", "有请求的权限");
String[] permsToRequest = permList.toArray(new String[permList.size()]);
mPendingRequests.addAll(permList);
ActivityCompat.requestPermissions(activity, permsToRequest, 1);
}
}
}
/**
* This method should be used to execute a {@link PermissionsResultAction} for the array
* of permissions passed to this method. This method will request the permissions if
* they need to be requested (i.e. we don't have permission yet) and will add the
* PermissionsResultAction to the queue to be notified of permissions being granted or
* denied. In the case of pre-Android Marshmallow, permissions will be granted immediately.
* The Fragment variable is used, but if {@link Fragment#getActivity()} returns null, this method
* will fail to work as the activity reference is necessary to check for permissions.
*
* @param fragment the fragment necessary to request the permissions.
* @param permissions the list of permissions to request for the {@link PermissionsResultAction}.
* @param action the PermissionsResultAction to notify when the permissions are granted or denied.
*/
@SuppressWarnings("unused")
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);
}
}
}
/**
* This method notifies the PermissionsManager that the permissions have change. If you are making
* the permissions requests using an Activity, then this method should be called from the
* Activity callback onRequestPermissionsResult() with the variables passed to that method. If
* you are passing a Fragment to make the permissions request, then you should call this in
* the {@link Fragment#onRequestPermissionsResult(int, String[], int[])} method.
* It will notify all the pending PermissionsResultAction objects currently
* in the queue, and will remove the permissions request from the list of pending requests.
*
* @param permissions the permissions that have changed.
* @param results the values for each permission.
*/
@SuppressWarnings("unused")
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]);
}
}
/**
* When request permissions on devices before Android M (Android 6.0, API Level 23)
* Do the granted or denied work directly according to the permission status
*
* @param activity the activity to check permissions
* @param permissions the permissions names
* @param action the callback work object, containing what we what to do after
* permission check
*/
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);
}
}
}
}
/**
* Filter the permissions list:
* If a permission is not granted, add it to the result list
* if a permission is granted, do the granted work, do not add it to the result list
*
* @param activity the activity to check permissions
* @param permissions all the permissions names
* @param action the callback work object, containing what we what to do after
* permission check
* @return a list of permissions names that are not granted yet
*/
@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) {
Log.i("xxx", "getPermissionsListToRequest: "+perm+" Permissions.NOT_FOUND");
action.onResult(perm, Permissions.NOT_FOUND);
}
} else if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
if (!mPendingRequests.contains(perm)) {
Log.i("xxx", "getPermissionsListToRequest: "+perm+" add");
permList.add(perm);
}
} else {
if (action != null) {
Log.i("xxx", "getPermissionsListToRequest: "+perm+" Permissions.GRANTED");
action.onResult(perm, Permissions.GRANTED);
}
}
}
return permList;
}
}
3.抽象类PermissionsResultAction
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() {}
/**
* Alternate Constructor. Pass the looper you wish the PermissionsResultAction
* callbacks to be executed on if it is not the current Looper. For instance,
* if you are making a permissions request from a background thread but wish the
* callback to be on the UI thread, use this constructor to specify the UI Looper.
* (不同的构造函数。通过你希望得到的许可结果如果不是当前的Looper,就会被执行的回调。
* 例如,如果您正在从后台线程中获取权限请求,但是希望回调在UI线程上,
* 使用这个构造函数来指定UI looper。)
*
* @param looper the looper that the callbacks will be called using.回调函数将被调用。
*/
@SuppressWarnings("unused")
public PermissionsResultAction(@NonNull Looper looper) {mLooper = looper;}
/**
* This method is called when ALL permissions that have been
* requested have been granted by the user. In this method
* you should put all your permissions sensitive code that can
* only be executed with the required permissions.
* (当所有的权限都被调用时,这个方法就会被调用请求已被用户授予。
* 你应该把所有权限敏感的代码都放在这个方法中,只有被要求的权限才能执行。)
*/
public abstract void onGranted();
/**
* This method is called when a permission has been denied by
* the user. It provides you with the permission that was denied
* and will be executed on the Looper you pass to the constructor
* of this class, or the Looper that this object was created on.
*
* @param permission the permission that was denied.
*/
public abstract void onDenied(String permission);
/**
* This method is used to determine if a permission not
* being present on the current Android platform should
* affect whether the PermissionsResultAction should continue
* listening for events. By default, it returns true and will
* simply ignore the permission that did not exist. Usually this will
* work fine since most new permissions are introduced to
* restrict what was previously allowed without permission.
* If that is not the case for your particular permission you
* request, override this method and return false to result in the
* Action being denied.
*
* @param permission the permission that doesn't exist on this
* Android version
* @return return true if the PermissionsResultAction should
* ignore the lack of the permission and proceed with exection
* or false if the PermissionsResultAction should treat the
* absence of the permission on the API level as a denial.
*/
@SuppressWarnings({"WeakerAccess", "SameReturnValue"})
public synchronized boolean shouldIgnorePermissionNotFound(String permission) {
Log.d(TAG, "Permission not found: " + permission);
return true;
}
@SuppressWarnings("WeakerAccess")
@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);
}
}
/**
* This method is called when a particular permission has changed.
* This method will be called for all permissions, so this method determines
* if the permission affects the state or not and whether it can proceed with
* calling onGranted or if onDenied should be called.
*
* @param permission the permission that changed.
* @param result the result for that permission.
* @return this method returns true if its primary action has been completed
* and it should be removed from the data structure holding a reference to it.
*/
@SuppressWarnings("WeakerAccess")
@CallSuper
protected synchronized final boolean onResult(final @NonNull String permission, Permissions result) {
mPermissions.remove(permission);
Log.i("xxx", "onResult: 进入onResult");
if (result == Permissions.GRANTED) {
Log.i("xxx", "onResult: GRANTED");
if (mPermissions.isEmpty()) {
Log.i("xxx", "onResult: mPermissions.isEmpty()");
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
Log.i("xxx", "onResult: onGranted");
onGranted();
}
});
return true;
}
} else if (result == Permissions.DENIED) {
Log.i("xxx", "onResult: DENIED");
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
Log.i("xxx", "onResult: onDenied(permission)");
onDenied(permission);
}
});
return true;
} else if (result == Permissions.NOT_FOUND) {
Log.i("xxx", "onResult: NOT_FOUND");
if (shouldIgnorePermissionNotFound(permission)) {
Log.i("xxx", "onResult: 可以再申请");
if (mPermissions.isEmpty()) {
Log.i("xxx", "onResult: mPermissions.isEmpty()");
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
onGranted();
}
});
return true;
}
} else {
Log.i("xxx", "onResult: else");
new Handler(mLooper).post(new Runnable() {
@Override
public void run() {
onDenied(permission);
}
});
return true;
}
}
return false;
}
/**
* This method registers the PermissionsResultAction object for the specified permissions
* so that it will know which permissions to look for changes to. The PermissionsResultAction
* will then know to look out for changes to these permissions.
*
* @param perms the permissions to listen for
*/
@SuppressWarnings("WeakerAccess")
@CallSuper
protected synchronized final void registerPermissions(@NonNull String[] perms) {
Collections.addAll(mPermissions, perms);
}
}