Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
目前,我正在参加 2018 CSDN 博客之星评选,如果你觉得我的博客对你有一点点帮助的话,请帮忙投一下票。 https://bss.csdn.net/m/topic/blog_star2018#118 号码是 118,博主 gdutxiaoxu,哈哈,谢了。
前言
Fragment,简称碎片,可以简单地认为它就是一个“控件”,更加具体一点就是“View控制器”。它自身有生命周期。在开发中,我们经常用到,再熟悉不过了。然而,Fragment 的一些巧妙引用,不知道你是否了解过?
使用 Fragment 封装权限申请
使用 Fragment 优雅处理 onActivityResult
Activity reCreate 的时候用来存储数据
这篇文章主要讲解以下内容
使用 Fragment 封装权限申请
使用 Fragment 优雅处理 onActivityResult
当然,这些封装,网上都有相应的开源库了, RxPermission, EasyPermision, RxActivityReslut 等,这里讲解如何封装,主要是让大家了解背后的原理,加深理解。想要成为一名优秀的工程师(程序猿),光停留在表面是远远不够的,我们要多读源码,理解背后的原理。哈哈,废话不多说,开始进入正文。
Fragment 封装权限申请
Android 6.0 动态权限机制,大家再熟悉不过了,如果我们没有对其进行封装,那我们每一次在申请权限的时候,大概需要以下几步:
这里我们已拨打电话为例子进行讲解
检查是否拥有电话权限,没有的话进行申请
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 10); } else{ callPhone(); }
在 onRequestPermissionsResult 方法里面进行处理,然后进行相应的操作。
@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == 10) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { callPhone(); } else { // Permission Denied Toast.makeText(TestResultActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show(); } return; } }
看到这样的代码你会不会很烦,每次涉及权限操作的时候,都要写这样一堆这样重复的代码,枯燥,且很多代码逻辑会耦合在 Activity 中,不方便维护。那有没有办法,将这些繁琐的步骤封装起来呢,答案是有的。
现在网上的做法一般有以下几种
使用透明的 Activity 进行申请
使用 Fragment 进行申请
反射
AOSP
这里我们使用 Fragment 进行封装。
我们知道, Fragment 一般依赖于 Activity 存活,并且生命周期跟 Activity 差不多,因此,我们进行权限申请的时候,可以利用透明的 Fragment 进行申请,在里面处理完之后,再进行相应的回调。
当我们申请权限申请的时候,先查找我们当前 Activity 是否存在代理 fragment,不存在,进行添加,并使用代理 Fragment 进行申请权限
第二步:在代理 Fragment 的 onRequestPermissionsResult 方法进行相应的处理,判断是否授权成功
第三步:进行相应的回调
首先,我们先来看一下代理 Fragment EachPermissionFragment 是怎么封装的?
public class EachPermissionFragment extends Fragment { private SparseArray<IPermissionListenerWrap.IPermissionListener> mCallbacks = new SparseArray<>(); private SparseArray<IPermissionListenerWrap.IEachPermissionListener> mEachCallbacks = new SparseArray<>(); private Random mCodeGenerator = new Random(); private FragmentActivity mActivity; public EachPermissionFragment() { // Required empty public constructor } public static EachPermissionFragment newInstance() { return new EachPermissionFragment(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置为 true,表示 configuration change 的时候,fragment 实例不会背重新创建 setRetainInstance(true); mActivity = getActivity(); } public void requestPermissions(@NonNull String[] permissions, IPermissionListenerWrap.IPermissionListener callback) { int requestCode = makeRequestCode(); mCallbacks.put(requestCode, callback); requestPermissions(permissions, requestCode); } public void requestEachPermissions(@NonNull String[] permissions, IPermissionListenerWrap.IEachPermissionListener callback) { int requestCode = makeRequestCode(); mEachCallbacks.put(requestCode, callback); requestPermissions(permissions, requestCode); } /** * 随机生成唯一的requestCode,最多尝试10次 * * @return */ private int makeRequestCode() { int requestCode; int tryCount = 0; do { requestCode = mCodeGenerator.nextInt(0x0000FFFF); tryCount++; } while (mCallbacks.indexOfKey(requestCode) >= 0 && tryCount < 10); return requestCode; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); handlePermissionCallBack(requestCode, grantResults); handleEachPermissionCallBack(requestCode, permissions, grantResults); } private void handlePermissionCallBack(int requestCode, @NonNull int[] grantResults) { IPermissionListenerWrap.IPermissionListener callback = mCallbacks.get(requestCode); mCallbacks.remove(requestCode); if (callback == null) { return; } boolean allGranted = false; int length = grantResults.length; for (int i = 0; i < length; i++) { int grantResult = grantResults[i]; if (grantResult != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } allGranted = true; } if (allGranted) { callback.onAccepted(true); } else { callback.onAccepted(false); } } private void handleEachPermissionCallBack(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { IPermissionListenerWrap.IEachPermissionListener eachCallback = mEachCallbacks.get(requestCode); if (eachCallback == null) { return; } mEachCallbacks.remove(requestCode); int length = grantResults.length; for (int i = 0; i < length; i++) { int grantResult = grantResults[i]; Permission permission; String name = permissions[i]; if (grantResult == PackageManager.PERMISSION_GRANTED) { permission = new Permission(name, true); eachCallback.onAccepted(permission); } else { if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, name)) { permission = new Permission(name, false, true); } else { permission = new Permission(name, false, false); } eachCallback.onAccepted(permission); } } } }
我们先来看一下它的 onCreate 方法,在 onCreate 方法里面,我们调用了 setRetainInstance 方法。
setRetainInstance(boolean retain)
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)
表示当 Activity 重新创建的时候, fragment 实例是否会被重新创建(比如横竖屏切换),设置为 true,表示 configuration change 的时候,fragment 实例不会背重新创建,这样,有一个好处,即
configuration 变化的时候,我们不需要再做额外的处理。因此, fragment 该方法也常常被用来处理 Activity re-creation 时候数据的保存,是不是又 get 到了什么?
接着我们来看 requestEachPermissions 方法
在申请权限的时候,即 requestEachPermissions 方法中,我们先生成一个随机的 requestCode,并确保不会重复
第二步:将我们的 callBack 及 requestCode 缓存起来,通过 key 可以查找相应的 requestCode。
第三步:调用 Fragment 的 requestPermissions 方法进行权限申请
然后看 onRequestPermissionsResult 方法
这里我们主要关注 handleEachPermissionCallBack(requestCode, permissions, grantResults); 方法, handlePermissionCallBack 方法思路也是类似的
我们先从我们的 mEachCallbacks 方法中查找,是否有相应的缓存(即根据 requestCode 查找是否有相应的权限申请,有的话进行处理,没有的话,忽略。)
处理完毕之后,我们将权限的信息封装在 Permission 中,并进行相应的回调
public class Permission { public final String name; public final boolean granted; /** * false 选择了 Don’t ask again */ public final boolean shouldShowRequestPermissionRationale; }
Permission 含有三个字段,name 表示权限的名字,granted 表示是否授权,shouldShowRequestPermissionRationale 表示是否勾选了 Don’t ask again(即不再询问),通常我们的做法是若勾选了不再询问,我们需要引导用户,跳转到相应的 Setting 页面。
大致代码如下
/** * 打开设置页面打开权限 * * @param context */public static void startSettingActivity(@NonNull Activity context) { try { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + context.getPackageName())); intent.addCategory(Intent.CATEGORY_DEFAULT); context.startActivityForResult(intent, 10); //这里的requestCode和onActivityResult中requestCode要一致 } catch (Exception e) { e.printStackTrace(); } }
封装完成之后,我们只需要调用以下方法即可,简单,方便,快捷。
private void requestPermission(final String[] permissions) { PermissionsHelper.init(MainActivity.this).requestEachPermissions(permissions, new IPermissionListenerWrap.IEachPermissionListener() { @Override public void onAccepted(Permission permission) { show(permission); } @Override public void onException(Throwable throwable) { } }); }
OK,我们再来梳理一下流程
image
使用 Fragment 优雅处理 onActivityResult
我们先来看一下没封装之前 onActivityresult 的处理方式
我们先来看下正常情况下启动 Activity 和接收回调信息的方式:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 启动Activity startActivityForResult(new Intent(this, TestActivity.class), 10); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // 接收Activity回调 if (requestCode == 10) { // 处理回调信息 } }
这样在简单页面下,看起来没什么问题,也简单易懂。但实际上,这种方式会存在一些局限
onActivityResult 必须在原始 Activity 中才能接收,如果想在非 Activity 中调用startActivityForResult,那么调用和接收的地方就不在同一个地方了,代码可读性会大大降低。
onActivityResult 都在同一个 activity 处理,如果这种方式特别多的话,我们要写一大堆的 if else,代码可读性大大较低,也不是很优雅。
同理,我们也可以跟上面的权限封装一样,用空白的 fragment 进行代理,进行封装。封装后的代码调用如下。
Intent intent = new Intent(MainActivity.this, TestResultActivity.class); ActivityResultHelper.init(MainActivity.this).startActivityForResult(intent, new ActivityResultHelper.Callback() { @Override public void onActivityResult(int resultCode, Intent data) { String result = data.getStringExtra(TestResultActivity.KEY_RESULT); show(" resultCode = " + resultCode + " result = " + result); } });
思路如下
当我们想发起 startActivityresult 的时候,使用代理 Fragment 进行代理,调用startActivityForResult 方法,它需要两个参数, intent, 和 requestCode, intent 代表要跳转的动作, requestCode 用来区分是那个动作。这里,为了简化,我们随机生成 requestCode ,并缓存起来,下次申请的时候,再随机申请,确保不会重复。
在 onActivityresult 里面根据 requestCode 找到相应的 callback,并进行相应的回调。
中转的 Fragment RouterFragment 核心代码如下
public class RouterFragment extends Fragment { private SparseArray<ActivityResultHelper.Callback> mCallbacks = new SparseArray<>(); private Random mCodeGenerator = new Random(); public RouterFragment() { // Required empty public constructor } public static RouterFragment newInstance() { return new RouterFragment(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } public void startActivityForResult(Intent intent, ActivityResultHelper.Callback callback) { int requestCode = makeRequestCode(); mCallbacks.put(requestCode, callback); startActivityForResult(intent, requestCode); } /** * 随机生成唯一的requestCode,最多尝试10次 * * @return */ private int makeRequestCode() { int requestCode; int tryCount = 0; do { requestCode = mCodeGenerator.nextInt(0x0000FFFF); tryCount++; } while (mCallbacks.indexOfKey(requestCode) >= 0 && tryCount < 10); return requestCode; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); ActivityResultHelper.Callback callback = mCallbacks.get(requestCode); mCallbacks.remove(requestCode); if (callback != null) { callback.onActivityResult(resultCode, data); } } }
其他的代码这里就不贴了,有兴趣的请自行到 GitHub 上面查看
GitHub 地址
https://github.com/gdutxiaoxu/FragmentDemo
题外话
看了上面 Fragment 的妙用,封装权限,处理 onActivityResult,你是否想到了什么?
其实,跟 Activity onActivityReslut 相关的,我们都可以转移到代理 Fragment 进行操作,如截屏,处理悬浮窗权限
setRetainInstance 方法,设置为 true 的话,当 activity recreate 的时候,fragment 实例不会被重新创建(如 configuration change 的时候,fragment 实例不会背重新创建),这样我们可以利用该属性来保存数据。如 architecture-components 的 ViewModel 其实也是利用 Fragment 的这种特征来保存数据
architecture-components 里面的 lifeCycle 生命周期的回调其实也是添加一个空白的 Fragment,从而进行生命周期的回调。
你呢, Fragment 的妙用你还知道哪些,欢迎留言评论。
Android 技术人,一位不羁的码农,撩天撩地撩技术,期待你的参与。
Android 技术人
- 点赞
- 收藏
- 关注作者
评论(0)