博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
避免使用onActivityResult,提高代码可读性
阅读量:2356 次
发布时间:2019-05-10

本文共 10927 字,大约阅读时间需要 36 分钟。

作者:asAnotherJack

链接:

https://juejin.im/post/5a4611786fb9a0451a76b565

本文由作者授权发布。

1
问题

Android中,通过startActivityForResult跳转页面获取数据应该不必多说,但是这种所有获取到的结果都需要到onActivityResult中处理的方式实在令人蛋疼。

试想一下,我们敲着代码唱着歌。突然,半路上跳出一群马匪,让我们到另一个页面获取一点数据,获取后还不让在当前代码位置处理逻辑,要去onActivityResult添加一个requestCode分支处理结果,处理完才让回来,等这一切都做完回来难免就会陷入这样的思考:我是谁,我在哪,我在干什么,我刚才写到哪了……

再想一下,你跟同事的代码,跟到一个startActivityForResult,于是不耐烦地ctrl+f找到onActivityResult,发现里面充斥着大量的requestCode分支,然后突然意识到刚才没记下requestCode是什么……

分析问题

问题的根源是所有处理结果的逻辑都要放到onActivityResult中,在里面根据requestCode作不同处理。而我们渴望的是能在发起startActivityForResult的时候捎带着把获取结果后处理的逻辑也传进去,并能在内部对requestCode判断好,不用我们再判断一遍。

2
解决问题尝试一(不完美方式)

新建一个OnResultManager类,用来管理获取结果的回调,下面详细说。

分析问题时说了,我们希望在发起startActivityForResult的时候就指定好处理结果的逻辑,这个简单,在OnResultManager中创建一个Callback接口,里面定义一个OnActivityResult方法,参数和Activity的OnActivityResult方法参数完全一样,在发起start的时候除了intent和requestCode,再传一个callback进去。而OnresultManager负责控制在系统的onActivityResult触发时,调用对应callback的方法。

下面是OnResultManager的全部代码。

public class OnResultManager {
    private static final String TAG = "OnResultManager";     //HashMap的key Integer为requestCode     private static WeakHashMap
> mCallbacks = new WeakHashMap<>();     private WeakReference
 mActivity;     public OnResultManager(Activity activity) {
        mActivity = new WeakReference
(activity);     }     public void startForResult(Intent intent, int requestCode, Callback callback){
        Activity activity = getActivity();         if(activity == null){
            return;         }         addCallback(activity,requestCode,callback);         activity.startActivityForResult(intent,requestCode);     }     public void trigger(int requestCode, int resultCode, Intent data){
        Log.d(TAG,"----------- trigger");         Activity activity = getActivity();         if(activity == null){
            return;         }         Callback callback = findCallback(activity,requestCode);         if(callback != null){
            callback.onActivityResult(requestCode,resultCode,data);         }     }     //获取该activity、该requestCode对应的callback     private Callback findCallback(Activity activity,int requestCode){
        HashMap
 map = mCallbacks.get(activity);         if(map != null){
            return map.remove(requestCode);         }         return null;     }     private void addCallback(Activity activity,int requestCode,Callback callback){
        HashMap
 map = mCallbacks.get(activity);         if(map == null){             map = new HashMap<>();             mCallbacks.put(activity,map);         }         map.put(requestCode,callback);     }     private Activity getActivity(){         return mActivity.get();     }     public interface Callback{         void onActivityResult(int requestCode, int resultCode, Intent data);     } }

逻辑很简单,里面持有一个mActivity,使用弱引用以防止内存泄漏,在构造器中为其赋值。

还有一个static的WeakHashMap<Activity,HashMap<Integer,Callback>> mCallbacks 用来存放所有的callback,先以activity分,在各个activity中又使用hashmap存储requestCode和callback的对应关系。

在startForResult时,最终还是调用的activity的startActivityForResult,只不过在跳转页面之前,把callback存入了mCallbacks中。

而trigger方法则是根据activity和requestCode从mCallbacks中取出对应的callback,调用方法。

现在callback的存和取都搞定了,那么问题来了,什么时候触发“取”的操作呢,即trigger方法怎么触发呢?

答案是在onActivityResult中调用,嫌麻烦可以在BaseActivity中调用。

使用示例:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)     setContentView(R.layout.activity_main)     go.setOnClickListener {
        val intent = Intent(this,SecondActivity::class.java)         onResultManager.startForResult(intent,REQUEST_CODE,{requestCode: Int, resultCode: Int, data: Intent? ->             if (resultCode == Activity.RESULT_OK){
                val text = data?.getStringExtra("text")                 Toast.makeText(this,"result -> "+text,Toast.LENGTH_SHORT).show()             }else{
                Toast.makeText(this,"canceled",Toast.LENGTH_SHORT).show()             }         })     } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)     onResultManager.trigger(requestCode,resultCode,data) }

可是这样好蠢啊,你是不是觉得要是不用手动触发,能自动触发多好。

作者原文中还更新了一个 Hook的方式,避免手动去触发,由于篇幅原因去掉了,感兴趣原文查看; Hook 的方案不建议使用,可以用于学习。

3
第二种方式(参考RxPermissions的做法)

前段时间又来了个小项目,领导扔给我了,然后在这个项目里就把之前没用过(没错,之前总用H5开发……)的rxjava、kotlin都加进来了。

有一天做动态权限处理,惊奇地发现RxPermissions居然摆脱了Activity的onRequestPermissionsResult方法!!!

大家都知道,动态权限大体就是先调用requestPermissions方法,然后授权的结果要到onRequestPermissionsResult中处理,简直和startActivityForResult如出一辙

那RxPermissions是怎么做到的呢!!!

接着在前几天不太忙的时候看了下RxPermissions的源码,发现它内部持有一个Fragment,这个fragment没有视图,只负责请求权限和返回结果,相当于一个桥梁的作用,我们通过rxPermissions发起request的时候,其实并不是activity去request,而是通过这个fragment去请求,然后在fragment的onRequestPermissionsResult中把结果发送出来,如此来避开activity的onRequestPermissionsResult方法。

当时,没见过什么大场面的我差点就给跪了。

640?wx_fmt=other

RxPermissions的源码就不贴了,网上的讲解应该也很多。

同样,Fragment也有startActivityForResult方法啊,那我们也可以采取类似的方法,为所欲为。

这次取名叫AvoidOnResult,主要就是AvoidOnResult和AvoidOnResultFragment两个类。先上代码:

AvoidOnResult

public class AvoidOnResult {
    private static final String TAG = "AvoidOnResult";     private AvoidOnResultFragment mAvoidOnResultFragment;     public AvoidOnResult(Activity activity) {
        mAvoidOnResultFragment = getAvoidOnResultFragment(activity);     }     public AvoidOnResult(Fragment fragment){
        this(fragment.getActivity());     }     private AvoidOnResultFragment getAvoidOnResultFragment(Activity activity) {
        AvoidOnResultFragment avoidOnResultFragment = findAvoidOnResultFragment(activity);         if (avoidOnResultFragment == null) {
            avoidOnResultFragment = new AvoidOnResultFragment();             FragmentManager fragmentManager = activity.getFragmentManager();             fragmentManager                     .beginTransaction()                     .add(avoidOnResultFragment, TAG)                     .commitAllowingStateLoss();             fragmentManager.executePendingTransactions();         }         return avoidOnResultFragment;     }     private AvoidOnResultFragment findAvoidOnResultFragment(Activity activity) {
        return (AvoidOnResultFragment) activity.getFragmentManager().findFragmentByTag(TAG);     }     public Observable
 startForResult(Intent intent, int requestCode) {
        return mAvoidOnResultFragment.startForResult(intent, requestCode);     }     public Observable
 startForResult(Class
 clazz, int requestCode) {
        Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);         return startForResult(intent, requestCode);     }     public void startForResult(Intent intent, int requestCode, Callback callback) {
        mAvoidOnResultFragment.startForResult(intent, requestCode, callback);     }     public void startForResult(Class
 clazz, int requestCode, Callback callback) {
        Intent intent = new Intent(mAvoidOnResultFragment.getActivity(), clazz);         startForResult(intent, requestCode, callback);     }     public interface Callback {
        void onActivityResult(int requestCode, int resultCode, Intent data);     } }

AvoidOnResultFragment

public class AvoidOnResultFragment extends Fragment {
    private Map
> mSubjects = new HashMap<>();     private Map
 mCallbacks = new HashMap<>();     public AvoidOnResultFragment() {
    }     @Override     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);         setRetainInstance(true);     }     public Observable
 startForResult(final Intent intent, final int requestCode) {
        PublishSubject
 subject = PublishSubject.create();         mSubjects.put(requestCode, subject);         return subject.doOnSubscribe(new Consumer
() {             @Override             public void accept(Disposable disposable) throws Exception {                 startActivityForResult(intent, requestCode);             }         });     }     public void startForResult(Intent intent, int requestCode, AvoidOnResult.Callback callback) {         mCallbacks.put(requestCode, callback);         startActivityForResult(intent, requestCode);     }     @Override     public void onActivityResult(int requestCode, int resultCode, Intent data) {         super.onActivityResult(requestCode, resultCode, data);         //rxjava方式的处理         PublishSubject
 subject = mSubjects.remove(requestCode);         if (subject != null) {             subject.onNext(new ActivityResultInfo(requestCode, resultCode, data));             subject.onComplete();         }         //callback方式的处理         AvoidOnResult.Callback callback = mCallbacks.remove(requestCode);         if (callback != null) {             callback.onActivityResult(requestCode, resultCode, data);         }     } }

先看AvoidOnResult,和RxPermissions一样,也持有一个无视图的fragment,在构造器中先去获取这个AvoidOnResultFragment,getAvoidOnResultFragment、findAvoidOnResultFragment这两个方法是从RxPermissions扒来的,大体就是先通过TAG获取fragment,如果是null就新建一个并add进去。

这个类内部也定义了一个Callback接口,不必多说。

继续,这个类有多个startForResult方法,主要看public void startForResult(Intent intent, int requestCode, Callback callback),它本身不干实事,只是调用fragment的同名方法,所有的逻辑都是fragment中处理,待会儿我们来看这个“同名方法”。

再看这个fragment,它持有一个mCallbacks,存着requestCode和callback的对应关系。然后找到上边说的同名方法startForResult,只有两行代码,

1、把callback存起来;

2、调用fragment的startActivityForResult。

继续看fragment的onActivityResult方法,主要看注释有“callback方式的处理”的代码,就是从mCallbacks中拿到requestCode对应的callback,调用callback的onActivityResult方法。总体的就是这样了,是不是很简单。

拓展

可以看到除了返回值为void的startForResult方法外,还有几个返回值为Observable的,原理一样,只不过fragment中不再是存callback,而是存subject,然后通过doOnSubscribe使它在subscribe的时候跳转页面,最后把得到的Observable返回。

对应的,在onActivityResult中拿到对应的subject,通过onNext把数据发出去。

使用示例:

CallBack 的方式:

callback.setOnClickListener {
    AvoidOnResult(this).startForResult(FetchDataActivity::class.java, REQUEST_CODE_CALLBACK, object : AvoidOnResult.Callback {
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) =                 if (resultCode == Activity.RESULT_OK) {
                    val text = data?.getStringExtra("text")                     Toast.makeText(this@MainActivity, "callback -> " + text, Toast.LENGTH_SHORT).show()                 } else {
                    Toast.makeText(this@MainActivity, "callback canceled", Toast.LENGTH_SHORT).show()                 }     }) }

RxJava 的方式:

rxjava.setOnClickListener {
    AvoidOnResult(this)     .startForResult(FetchDataActivity::class.java, REQUEST_CODE_RXJAVA)     //下面可自由变换     .filter { it.resultCode == Activity.RESULT_OK }     .flatMap {
        val text = it.data.getStringExtra("text")         Observable.fromIterable(text.asIterable())     }     .subscribe({
        Log.d("-------> ", it.toString())     }, {
        Toast.makeText(this, "error", Toast.LENGTH_SHORT).show()     }, {
        Toast.makeText(this, "complete", Toast.LENGTH_SHORT).show()     }) }

https://github.com/AnotherJack/AvoidOnResult

所有的工具类都是用java写的,避免使用kotlin编写,出现java无法调用kotlin的情况。测试代码用的kotlin,不过没用太多kotlin的特性,所以即便没接触过kotlin的应该也很好看懂吧!

推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!

                        喜欢 就关注吧,欢迎投稿!

640?wx_fmt=jpeg

转载地址:http://aydtb.baihongyu.com/

你可能感兴趣的文章
Linux 安装Scala 详解
查看>>
MySQL 分支版本对比 详解
查看>>
MyCat 注解 详解
查看>>
MySQL 循环方法 while loop repeat 详解
查看>>
Java 创建对象以及类加载 详解
查看>>
JavaScript 闭包 详解
查看>>
Oracle 基础知识 详解
查看>>
JVM 命令参数 详解
查看>>
Java 产生随机数 详解
查看>>
Linux 后台执行命令 详解
查看>>
SpringBoot @ConfigurationProperties参数绑定 详解
查看>>
Nginx+Lua 开发的 hello world 案例 详解
查看>>
GB28181:基于JAVA的Catalog目录获取[part3]
查看>>
沙与沫
查看>>
BFS解小孩分油问题
查看>>
Bloom filter
查看>>
R语言绘制barplot(盒状图)以及plot(点状图)处理字体大小问题
查看>>
在Hadoop中使用MRUnit进行单元测试
查看>>
Type mismatch in key from map: expected .. Text, received … LongWritable
查看>>
详解Java内存机制(堆与栈)的分配
查看>>