其中完全拒绝以后,SoulPermission 提供了跳转到系统权限设置页的方法,我们再来看看效果:
很多时候,其实绿色部分(shouldShowRequestPermissionRationale)其实并不一定必要,反复的弹框用户可能会厌烦,大多数情况,我们这么封装就好:
public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {
@Override
public void onPermissionDenied(Permission permission) {
//SoulPermission提供栈顶Activity
Activity activity = SoulPermission.getInstance().getTopActivity();
if (null == activity) {
return;
}
String permissionDesc = permission.getPermissionNameDesc();
new AlertDialog.Builder(activity)
.setTitle(“提示”)
.setMessage(permissionDesc + “异常,请前往设置->权限管理,打开” + permissionDesc + “。”)
.setPositiveButton(“去设置”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//去设置页
SoulPermission.getInstance().goPermissionSettings();
}
}).create().show();
}
}
我们再写一个选择联系人的方法:
/**
- 选择联系人
*/
public static void chooseContact(final Activity activity, final int requestCode) {
SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.READ_CONTACTS,
new CheckPermissionAdapter() {
@Override
public void onPermissionOk(Permission permission) {
activity.startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), requestCode);
}
});
}
在Activity中也是一行解决问题:
findViewById(R.id.bt_choose_contact).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);
}
});
代码细节请参考demo,我们再来看看效果:
主要源码分析
1、优雅的避掉onPermissionResult
适配权限最大的痛点在于:项目业务页面繁多,如果你想实现“真运行时权限”的话就需要在业务的Activity或者Fragment中去重写权限请求回调方法。
斟酌一番并且在参考了下RxPermission中对权限请求的处理,我决定用同样的方式—用一个没有界面的Fragment去完成我们权限请求的操作,下面贴上部分代码:
首先定义一个接口,用于封装权限请求的结果
public interface RequestPermissionListener {
/**
-
得到权限检查结果
-
@param permissions 封装权限的数组
*/
void onPermissionResult(Permission[] permissions);
}
然后是我们的Fragment:
public class PermissionSupportFragment extends Fragment implements IPermissionActions {
/**
- 内部维护requestCode
*/
private static final int REQUEST_CODE = 11;
/**
- 传入的回调
*/
private RequestPermissionListener listener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//当状态发生改变,比如设备旋转时候,Fragment不会被销毁
setRetainInstance(true);
}
/**
-
外部请求的最终调用方法
-
@param permissions 权限
-
@param listener 回调
*/
@TargetApi(M)
@Override
public void requestPermissions(String[] permissions, RequestPermissionListener listener) {
requestPermissions(permissions, REQUEST_CODE);
this.listener = listener;
}
@TargetApi(M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Permission[] permissionResults = new Permission[permissions.length];
//拿到授权结果以后对结果做一些封装
if (requestCode == REQUEST_CODE) {
for (int i = 0; i < permissions.length; ++i) {
Permission permission = new Permission(permissions[i], grantResults[i], this.shouldShowRequestPermissionRationale(permissions[i]));
permissionResults[i] = permission;
}
}
if (listener != null && getActivity() != null && !getActivity().isDestroyed()) {
listener.onPermissionResult(permissionResults);
}
}
}
其中Permission是我们的权限名称、授予结果、是否需要给用于一个解释的包装类:
public class Permission {
private static final String TAG = Permission.class.getSimpleName();
/**
- 权限名称
*/
public String permissionName;
/**
- 授予结果
*/
public int grantResult;
/**
- 是否需要给用户一个解释
*/
public boolean shouldRationale;
/**
- 权限是否已经被授予
*/
public boolean isGranted() {
return grantResult == PackageManager.PERMISSION_GRANTED;
}
//。。。
}
至此,我们已经利用自己实现的一个没有界面的Fragment封装了运行时权限相关的请求、RequestCode的维护、以及onPermissionResult的回调、在我们真正调用的时候代码是这样的:
/**
-
@param activity 栈顶 Activity
-
@param permissionsToRequest 待请求的权限
-
@param listener 回调
*/
private void requestRuntimePermission(final Activity activity, final Permission[] permissionsToRequest, final CheckRequestPermissionsListener listener) {
new PermissionRequester(activity)
.withPermission(permissionsToRequest)
.request(new RequestPermissionListener() {
@Override
public void onPermissionResult(Permission[] permissions) {
List refusedListAfterRequest = new LinkedList<>();
for (Permission requestResult : permissions) {
if (!requestResult.isGranted()) {
refusedListAfterRequest.add(requestResult);
}
}
if (refusedListAfterRequest.size() == 0) {
listener.onAllPermissionOk(permissionsToRequest);
} else {
listener.onPermissionDenied(PermissionTools.convert(refusedListAfterRequest));
}
}
});
}
其中PermissionRequester也就是一个简单的构建者模式,其中包含了对Activity的类型判断,根据Activity类型去确定Fragment的实现:
如果是FragmentActivity的实例,则使用Support包中的Fragment,否则用默认的Fragment,这样就兼容了有些应用的项目的基类不是AppComponentActivity(FragmentActivity)的情形,当然,原则上最低支持4.0,即默认Fragment的支持版本。
class PermissionFragmentFactory {
private static final String FRAGMENT_TAG = “permission_fragment_tag”;
static IPermissionActions create(Activity activity) {
IPermissionActions action;
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
PermissionSupportFragment permissionSupportFragment = (PermissionSupportFragment) supportFragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (null == permissionSupportFragment) {
permissionSupportFragment = new PermissionSupportFragment();
supportFragmentManager.beginTransaction()
.add(permissionSupportFragment, FRAGMENT_TAG)
.commitNowAllowingStateLoss();
}
action = permissionSupportFragment;
} else {
android.app.FragmentManager fragmentManager = activity.getFragmentManager();
PermissionFragment permissionFragment = (PermissionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (null == permissionFragment) {
permissionFragment = new PermissionFragment();
activity.getFragmentManager().beginTransaction()
.add(permissionFragment, FRAGMENT_TAG)
.commitAllowingStateLoss();
}
action = permissionFragment;
}
return action;
}
}
至此,整个请求链已经很像最外层暴露的CheckAndRequestPermission方法了,就差一个Activity了,那么参数Activity怎么来呢?
我们继续想办法。
2、再舍去Activity
当然是使用Application中的ActivityLifecycleCallbacks,使用它的registerActivityLifecycleCallbacks,感知Activity声明周期变化,获取到当前应用栈顶的Activity,这样我们就不需要自己手动传入了。
public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {
WeakReference topActWeakReference;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//原则上只需要onResume,兼容如果在onCreate的时候做权限申请保证此时有Activity对象
topActWeakReference = new WeakReference<>(activity);
}
//…
@Override
public void onActivityResumed(Activity activity) {
topActWeakReference = new WeakReference<>(activity);
}
//…
}
注册它仅仅需要一个Application:
/**
- @param context Application
*/
private void registerLifecycle(Application context) {
if (null != lifecycle) {
context.unregisterActivityLifecycleCallbacks(lifecycle);
}
lifecycle = new PermissionActivityLifecycle();
context.registerActivityLifecycleCallbacks(lifecycle);
}
这样一来,只要调用了初始化方法registerLifecycle,我们就能提供提供栈顶Activity了
/**
-
获取栈顶Activity
-
@return 当前应用栈顶Activity
-
@throws InitException 初始化失败
-
@throws ContainerStatusException Activity状态异常
*/
private Activity getContainer() {
// may auto init failed
if (null == lifecycle || null == lifecycle.topActWeakReference) {
throw new InitException();
}
// activity status error
if (null == lifecycle.topActWeakReference.get() || lifecycle.topActWeakReference.get().isFinishing()) {
throw new ContainerStatusException();
}
return lifecycle.topActWeakReference.get();
}
结合起来回到我们之前申请权限的方法(省略了日志打印和线程的判断,如果需要再细看源码):
private void requestPermissions(final Permissions permissions, final CheckRequestPermissionsListener listener) {
//check container status
final Activity activity;
try {
activity = getContainer();
} catch (Exception e) {
//activity status error do not request
return;
}
//…
//finally request
requestRuntimePermission(activity, permissions.getPermissions(), listener);
}
至此,我们已经能脱离Activity和Fragment,也无需重写onPermissionResult了,只需要一个ApplicationContext初始化即可。
3、能否更简便一点?
最后避掉Application(免初始化):
我们可以自定义ContentProvider来完成库的初始化,我们可以参考Lifecycle组件的初始化:
http://chaosleong.github.io/2017/05/27/How-Lifecycle-aware-Components-actually-works/
//lifeCycle定义的初始化Provider
public class LifecycleRuntimeTrojanProvider extends ContentProvider {
@Override
public boolean onCreate() {
LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}
}
和它的Manifest文件:
<provider
android:name=“android.arch.lifecycle.LifecycleRuntimeTrojanProvider”
android:authorities=“${applicationId}.lifecycle-trojan”
android:exported=“false”
android:multiprocess=“true” />
参照它的实现给我们提供了一个很好的思路,我们可以自定义Provider去初始化一些库或者其他的内容,现在我们写一个自己的initContentProvider:
public class InitProvider extends ContentProvider {
@Override
public boolean onCreate() {
//初始化我们的库
SoulPermission.getInstance().autoInit((Application) getContext());
return true;
}
//…
}
在库的AndroidManifest文件中声明:
<provider android:authorities=“${applicationId}.permission.provider”
android:name=“.permission.InitProvider”
android:multiprocess=“true”
android:exported=“false”/>
至于为什么这个Context就是Application,我们可以参考ActivityThread中的对ContentProvider的初始化:
public void handleInstallProvider(ProviderInfo info) {
//即我们的应用的Application
installContentProviders(mInitialApplication, Arrays.asList(info));
}
至此,我们权限申请流程就跟Activity、Fragment、乃至Context都没有关系了。
4. 去除if&else、涵盖版本判断:
虽然我们完成了对运行时权限的申请流程,但是毕竟只针对6.0以上机型,如果上面流程还想一句话完成的话,那我们还得兼容老的机型,so,我们需要做在方法内做一个版本判断:
首先判断系统版本
public static boolean isOldPermissionSystem(Context context) {
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
return android.os.Build.VERSION.SDK_INT < M || targetSdkVersion < M;
}
然后是检查权限:
6.0以上当然是走系统Api:
class RunTimePermissionChecker implements PermissionChecker {
private String permission;
private Context context;
RunTimePermissionChecker(Context context, String permission) {
this.permission = permission;
this.context = context;
}
@TargetApi(M)
@Override
public boolean check() {
int checkResult = ContextCompat.checkSelfPermission(context, permission);
return checkResult == PackageManager.PERMISSION_GRANTED;
}
}
6.0以下、4.4以上通过AppOps反射获取(为了保证一致性,把权限名称参数在check方法中做了映射,把权限的String参数映射成checkOp的整形参数):
class AppOpsChecker implements PermissionChecker {
private Context context;
private String permission;
AppOpsChecker(Context context, String permission) {
this.context = context;
this.permission = permission;
}
/**
-
老的通過反射方式檢查權限狀態
-
结果可能不准确,如果返回false一定未授予
-
按需在里面添加
-
如果没匹配上或者异常都默认权限授予
-
@return 检查结果
*/
@Override
public boolean check() {
if (null == permission) {
return true;
}
switch (permission) {
case Manifest.permission.READ_CONTACTS:
return checkOp(4);
case Manifest.permission.WRITE_CONTACTS:
return checkOp(5);
case Manifest.permission.CALL_PHONE:
return checkOp(13);
…
default:
break;
}
return true;
}
boolean checkOp(int op) {
if (Build.VERSION.SDK_INT < KITKAT) {
return true;
}
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Method method = AppOpsManager.class.getDeclaredMethod(“checkOp”, int.class, int.class, String.class);
return 0 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
和版本判断起来就是这样:
public static PermissionChecker create(Context context, String permission) {
if (PermissionTools.isOldPermissionSystem(context)) {
return new AppOpsChecker(context, permission);
} else {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

面试复习笔记:
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960页Android开发笔记》
《1307页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
iFobF-1712219064021)]
[外链图片转存中…(img-9tedwfRp-1712219064021)]
[外链图片转存中…(img-XGXctqfH-1712219064021)]
[外链图片转存中…(img-wd1Oj8c5-1712219064021)]
[外链图片转存中…(img-VdzBkklU-1712219064022)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

面试复习笔记:
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960页Android开发笔记》
[外链图片转存中…(img-PCiSPstm-1712219064022)]
《1307页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
[外链图片转存中…(img-cv0SJ4gA-1712219064022)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。