避免使用onActivityResult,提高代码可读性

2024-02-20 23:10

本文主要是介绍避免使用onActivityResult,提高代码可读性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者: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<Activity,HashMap<Integer,Callback>> mCallbacks = new WeakHashMap<>();
    private WeakReference<Activity> mActivity;

    public OnResultManager(Activity activity{
        mActivity = new WeakReference<Activity>(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<Integer,Callback> map = mCallbacks.get(activity);
        if(map != null){
            return map.remove(requestCode);
        }
        return null;
    }

    private void addCallback(Activity activity,int requestCode,Callback callback){
        HashMap<Integer,Callback> 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: Intdata: 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: IntdataIntent?) {
    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<ActivityResultInfo> startForResult(Intent intent, int requestCode{
        return mAvoidOnResultFragment.startForResult(intent, requestCode);
    }

    public Observable<ActivityResultInfo> 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<Integer, PublishSubject<ActivityResultInfo>> mSubjects = new HashMap<>();
    private Map<Integer, AvoidOnResult.Callback> mCallbacks = new HashMap<>();

    public AvoidOnResultFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    public Observable<ActivityResultInfo> startForResult(final Intent intent, final int requestCode) {
        PublishSubject<ActivityResultInfo> subject = PublishSubject.create();
        mSubjects.put(requestCode, subject);
        return subject.doOnSubscribe(new Consumer<Disposable>() {
            @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<ActivityResultInfo> 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.javaREQUEST_CODE_CALLBACKobject : AvoidOnResult.Callback {
        override fun onActivityResult(requestCode: Int, resultCode: IntdataIntent?) =
                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.javaREQUEST_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



这篇关于避免使用onActivityResult,提高代码可读性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/729823

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没