讲讲郭大的运行时权限的封装姿势
一.WHAT
Android 6.0在我们原有的AndroidManifest.xml声明权限的基础上,又新增了运行时权限动态检测,以下权限都需要在运行时判断:
身体传感器、日历、摄像头、通讯录、地理位置、麦克风、电话、短信、存储空间
二.HOW
----笨笨的方式
Android6.0系统默认为targetSdkVersion小于23的应用默认授予了所申请的所有权限,所以如果你以前的APP设置的targetSdkVersion低于23,在运行时也不会崩溃,但这也只是一个临时的救急策略,用户还是可以在设置中取消授予的权限。
声明目标SDK版本
我们需要在build.gradle中声明targetSdkVersion为23
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.yourcomany.app
minSdkVersion 18
targetSdkVersion 23 ...........................
我们需要在用到权限的地方,每次都检查是否APP已经拥有权限,比如我们有一个下载功能,需要写SD卡的权限,我们在写入之前检查是否有WRITE_EXTERNAL_STORAGE权限,没有则申请权限
if(ContextCompat.checkSelfPermission(this,Manifest.permission.
WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
//申请WRITE_EXTERNAL_STORAGE权限 ActivityCompat.requestPermissions(this,newString[{Manifest
.permission.WRITE_EXTERNAL_STORAGE},WRITE_EXTERNAL_STORAGE_REQUEST_CODE);}
请求权限后,系统会弹出请求权限的Dialog
用户选择允许或需要后,会回调onRequestPermissionsResult方法, 该方法类似于onActivityResult
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
doNext(requestCode,grantResults);
}
我们接着需要根据requestCode和grantResults(授权结果)做相应的后续处理
private void doNext(int requestCode, int[] grantResults) {
if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
} else {
// Permission Denied
}}}
Fragment中运行时权限的特殊处理
在Fragment中申请权限,不要使用ActivityCompat.requestPermissions, 直接使用Fragment的requestPermissions方法,否则会回调到Activity的 onRequestPermissionsResult
如果在Fragment中嵌套Fragment,在子Fragment中使用requestPermissions方 法,onRequestPermissionsResult不会回调回来,建议使用 getParentFragment().requestPermissions方法,这个方法会回调到父Fragment中的onRequestPermissionsResult,加入以下代码可以把回调透传到子Fragment
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
List fragments = getChildFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment != null) {
fragment.onRequestPermissionsResult(requestCode,permissions,grantResults);}}}}
上面这种方式,那么我们每次都需要在使用此权限的页面重复上面的代码!
平时最最不能容忍的就是重复的代码,
我想大家现在肯定有了一点思路,傻呀,直接写到BaseActivity里。
------恩,一个不错的方式
首先呢,我们应该新建一个权限申请的监听回调接口,就叫他RequestPermisListener吧
它呢主要是处理申请权限时,对于申请的结果的回调
我们来看看它长神马样子:
public interface RequestPermisListener {
//全部成功
void onGranted();
//拒绝的权限,集合是为了回调的时候,能够清楚拒绝了哪些权限,并作出相应的处理
void onDenide(List<String> permissions);
}
嗯短小精干,每次申请权限时实现它就行了
现在我们来看看BaseActivity的样子
public abstract class BaseActivity{
private final String TAG="BaseActivity";
private static RequestPermisListener mListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
ButterKnife.bind(this);}
/**
* 权限的申请
*
* @param permis
* @param listener
*/
public void requestPermission(String permis[],RequestPermisListener listener) {
mListener=listener;
//装我们要处理申请的权限
List<String> permissions=new ArrayList<>();
//开启循环,遍历传入的权限是不是已经被用户同意了,
for (int i = 0; i < permis.length; i++) {
if (ContextCompat.checkSelfPermission(this, permis[i]) != PackageManager.PERMISSION_GRANTED) {
//将没有同意过的权限添加到申请的集合中去
permissions.add(permis[i]);
}
}
//集合不为空,则表示有没有同意的权限
if (!permissions.isEmpty()) {
//我们需要处理申请
ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), 1);
} else {
//为空则表示申请的权限全部已经同意过,
listener.onGranted();
}
}
/**
* 权限申请结果的处理
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode,@NonNull String[]permissions,@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch(requestCode) {
case 1:
if (grantResults.length > 0) {
List<String> denidePermission = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[I];
//将用户拒绝的权限,添加到拒绝的集合中去
if (grantResult == PackageManager.PERMISSION_DENIED) {
denidePermission.add(permission);
}
}
//拒绝的集合为空,自然全部同意,回调申请成功
if (denidePermission.isEmpty()) {
mListener.onGranted();
} else {
//否则,回调失败的接口,并将拒绝的权限传回,
mListener.onDenide(denidePermission);
}
}
break;
}
}
/**
* @return 返回视图的布局id
*/
protected abstract int getLayoutId();
}
一个项目往往都会有一个Activity的基类,我们将权限请求的逻辑,统统放在了里面,每次申请权限时,只需要将要请求的权限装在一个String数组里,这样满足了大多数的场景,那么问题来了,假如我们想要在其他情况下申请呢,大家注意到面申请那里吗,首先我们需要Activity的引用,还有就是回调是发生在Activity中,
那么就引发了下面的一个方法。。
-----这也许是更好的方式
首先我们需要一个Activity的管理类,就叫他ActivityManager吧
/**
* Created by Zcoder
* Email : 1340751953@qq.com
* Time : 2017/5/2
* Description : Activity的管理类
*/
public class ActivityManager {
private static List<Activity> activitys = new ArrayList<>();
//当Activity创建的时候,我们将其添加
public static void addActivity(Activity activity) {
activitys.add(activity);
}
//当Activity销毁的时候,我们将其移除
public static void removeActivity(Activity activity) {
activitys.remove(activity);
}
//获得目前处于顶端的Activity
public static Activity getTopActivitys() {
if (!activitys.isEmpty()) {
return activitys.get(activitys.size() - 1);
} else {
return null;
}
}
public static List<Activity> getActivitys(){
return activitys;
}
}
BaseActivity的修改,将权限请求方法,静态并Public,其中的Activity是从我们的ActivityManager中拿到当前处于顶部的Activity
//创建的时候将其添加到ActivityManager中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityManager.addActivity(this);
setContentView(getLayoutId());
ButterKnife.bind(this);
}
//注意看。。。。。。
public static void requestPermission(String permis[], RequestPermisListener listener) {
mListener = listener;
List<String> permissions = new ArrayList<>();
//注意*********************** Activity并不是当前的this
Activity activity = ActivityManager.getTopActivitys();
for (int i = 0; i < permis.length; i++) {
if (ContextCompat.checkSelfPermission(activity, permis[i]) != PackageManager.PERMISSION_GRANTED) {
permissions.add(permis[i]);
}
}
if (!permissions.isEmpty()) {
ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), 1);
} else {
listener.onGranted();
}
}
//销毁的时候将其从 ActivityManager移除
@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.removeActivity(this);
}
}
我的一段权限申请
我的一个App启动界面每次服务器壁纸与本地的不一致时,就需要下载服务器端的壁纸,位置时存在外部储存
所以没次进行下载都需要判断下权限,不然6.0会写入失败
如果你感兴趣可以去看看
养眼.一个看妹子图片的开源App(MVP+Retrofit+Rxjava)
地址:http://www.jianshu.com/p/3d744d4dc726
//下载新的壁纸
private void downloadNewImage(final String path, final String url) {
Logger.e("开始下载新的图片");
requestPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, new RequestPermisListener() {
@Override
public void onGranted() {
RxVolley.download(path, url, null, new HttpCallback() {
@Override
public void onSuccess(String t) {
super.onSuccess(t);
Logger.e("下载成功");
}
});
}
@Override
public void onDenide(List<String> permissions) {
ToastUtils.showLongToast(getString(R.string.permission_error_sd));
}
});
}
三.WHY
我认为郭大的这种方法十分的不错,而且也是很容易理解的
源码地址:
BaseActivity.java
https://github.com/miaoMiaoDaShi/Yangyan/blob/master/app/src/main/java/com/xxp/yangyan/pro/base/BaseActivity.java
ActivityManager.java
https://github.com/miaoMiaoDaShi/Yangyan/blob/master/app/src/main/java/com/xxp/yangyan/pro/utils/ActivityManager.java