Android:Activity 与 Fragment 通信 (99%) 完美解决方案

2024-05-12 22:48

本文主要是介绍Android:Activity 与 Fragment 通信 (99%) 完美解决方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android:Activity 与 Fragment 通信 (99%) 完美解决方案

前言

最近一直在想着能否有一种更好的方案来解决:Android中Activity与Fragment之间通信的问题,什么叫更好呢,就是能让Fragment的复用性高,性能还有好(不用反射),代码还要好维护,不需要为每对Activity和Fragment之间定义接口而发愁。

先简单说下Javascript这门语言吧,或许有人就会问:咱们不是聊Android的java问题吗?怎么话题转到JavaScript了。因为我的解决方案的启发是从它来的,没兴趣的朋友可以略过。最近在学习javascript这门语言,同时自己搞Android(java)开发也有5年多时间了,所以在学习js的过程中,就会惯性的把这两者进行比较。

与java语言的 严谨 相比 Javascript是一门"放荡不羁"、"不拘小节"(宽泛)的语言。
为什么要用"放荡不羁"这个词呢,下面是它的一个解释:

放荡不羁 [fàng dàng bù jī][解释] 羁:约束。放纵任性,不加检点,不受约束。

因为我觉得这个词更能充分的体现js弱类型的特点。
在给变量赋值时 可以这样写:

var a = 1;

还可以这样写:

 var b = '123'; 
var o = new Object();

甚至还可以这样写:

var fun = new function(){};fun1 = new function(){};

可以把任何类型的值赋给一个变量,也可以不加var关键字来申明一个变量,是不是很任性,很不拘束啊。

"不拘小节"主要体现了JavaScript的语法更宽泛、更简单的特点: 比如:

  js代码:  //函数申明不需要定义返回值,参数前面不需要有类型出现,//函数体里面就可以有返回值function max(a,b){ return a > b? a:b; } /* *可以传递任意多个参数,在java里面根本不可以 */ function print(){ var len = arguments.length; for(var i = 0; i < len; i++){ console.log(arguments[i]);} } 相应java代码:int max(int a, int b){return a> b? a:b; } /* *传递任意多个Object类型的参数 */ void print(Object... args){for (int i = 0; i < args.length; i++){                 System.out.println(args[i]); }}

上面的代码说明了JavaScript在申明函数时,不会有像java那么严格的规定,语法不拘小节,语法更简单(这里没有说java不好的意思)。

启发点

JavaScript中有一个重要的点(万事万物皆对象),函数也不列外,并且函数可以作为另外一个函数的参数,如:

     js代码: //遍历一个数组如果是它是数组,就把它乘以10再输出 var array = [1,2, '你好' , '不' ,31,15];  //数组的each方法接收一个函数 testArray.each( function( value ){ typeof value == 'number' ? alert( value *10 ):null;})  ;

当我看到上面JavaScript中函数的用法时我眼前一亮,为啥我不可以借鉴之来解决android中activity与fragment通信的问题呢?

Fragment的使命

先让我们聊聊Fragment为什么出现,这对于我们解决Activity与Fragment的通信有帮助。一个新事物的产生总是为了解决旧事物存在的问题,Fragment是android3.0的产物,在android3.0之前解决手机、平板电脑的适配问题是很头疼的,对ActivityGroup有印象的朋友,应该能深深的体会到ActivityGroup包裹的多个Activity之间切换等一系列的性能问题。由此Fragment诞生了。个人总结的Fragment的使命:

  • 解决手机、平板电脑等各种设备的适配问题
  • 解决多个Activity之间切换性能问题
  • 模块化,因为模块化导致复用的好处

Fragment的使用

Fragment是可以被包裹在多个不同Activity内的,同时一个Activity内可以包裹多个Fragment,Activity就如一个大的容器,它可以管理多个Fragment。所有Activity与Fragment之间存在依赖关系。

Activity与Fragment通信方案

上文提到Activity与Fragment之间是存在依赖关系的,因此它们之间必然会涉及到通信问题,解决通信问题必然会涉及到对象之间的引用。因为Fragment的出现有一个重要的使命就是:模块化,从而提高复用性。若达到此效果,Fragment必须做到高内聚,低耦合。

现在大家动动脚趾都能想到的解决它们之间通信的方案有:handler,广播,EvnetBus,接口等(或许还有别的方案,请大家多多分享),那我们就聊下这些方案。

handler方案:

先上代码

   public class MainActivity extends FragmentActivity{ //申明一个Handler public Handler mHandler = new Handler(){       @Overridepublic void handleMessage(Message msg) { super.handleMessage(msg);...相应的处理代码}}...相应的处理代码} public class MainFragment extends Fragment{ //保存Activity传递的handlerprivate Handler mHandler;@Overridepublic void onAttach(Activity activity) { super.onAttach(activity);//这个地方已经产生了耦合,若还有其他的activity,这个地方就得修改 if(activity instance MainActivity){ mHandler =  ((MainActivity)activity).mHandler; }}...相应的处理代码}

该方案存在的缺点:

  • Fragment对具体的Activity存在耦合,不利于Fragment复用
  • 不利于维护,若想删除相应的Activity,Fragment也得改动
  • 没法获取Activity的返回数据
  • handler的使用个人感觉就很不爽(不知大家是否有同感)
广播方案:

具体的代码就不写了,说下该方案的缺点:

  • 用广播解决此问题有点大材小用了,个人感觉广播的意图是用在一对多,接收广播者是未知的情况
  • 广播性能肯定会差(不要和我说性能不是问题,对于手机来说性能是大问题)
  • 传播数据有限制(必须得实现序列化接口才可以)
    暂时就想到这些缺点,其他的缺点请大家集思广益下吧。
EventBus方案:

具体的EventBus的使用可以自己搜索下,个人对该方案的看法:

  • EventBus是用反射机制实现的,性能上会有问题(不要和我说性能不是问题,对于手机来说性能是大问题)
  • EventBus难于维护代码
  • 没法获取Activity的返回数据
接口方案

我想这种方案是大家最易想到,使用最多的一种方案吧,具体上代码:

  //MainActivity实现MainFragment开放的接口 public class MainActivity extends FragmentActivity implements FragmentListener{ @overridepublic void toH5Page(){ }...其他处理代码省略} public class MainFragment extends Fragment{public FragmentListener mListener;  //MainFragment开放的接口 public static interface FragmentListener{ //跳到h5页面void toH5Page();}@Override public void onAttach(Activity activity) { super.onAttach(activity); //对传递进来的Activity进行接口转换if(activity instance FragmentListener){mListener = ((FragmentListener)activity); }}...其他处理代码省略 }

这种方案应该是既能达到复用,又能达到很好的可维护性,并且性能也是杠杠的。但是唯一的一个遗憾是假如项目很大了,Activity与Fragment的数量也会增加,这时候为每对Activity与Fragment交互定义交互接口就是一个很头疼的问题(包括为接口的命名,新定义的接口相应的Activity还得实现,相应的Fragment还得进行强制转换)。 想看更好的解决方案请看下面章节。

大招来也

设计模式里经常提到的一个概念就是封装变化,同时受javascript中的函数的参数可以是函数对象的启发下,我有了下面的想法,先上代码:代码地址

  /** * + Created by niuxiaowei on 2016/1/20.* 各种方法集合的类,可以把一个方法类以key-value的形式放入本类,   * 可以通过key值来调用相应的方法 */public class Functions { //带参数方法的集合,key值为方法的名字 private  HashMap<String,FunctionWithParam> mFunctionWithParam ; //无参数无返回值的方法集合,同理key值为方法名字private HashMap<String,FunctionNoParamAndResult> mFunctionNoParamAndResult ; /** * 基础方法类 */public static abstract class Function{//方法的名字,用来做调用,也可以理解为方法的指针 public String mFunctionName; public Function(String functionName){ this.mFunctionName = functionName;} } /** * 带有参数没有返回值的方法* @param <Param> 参数 */public static abstract class FunctionWithParam<Param> extends Function{ public FunctionWithParam(String functionName) { super(functionName);} public abstract void function(Param param); } /** * 没有参数和返回值的方法 */public static abstract class FunctionNoParamAndResult extends Function{ public FunctionNoParamAndResult(String functionName) { super(functionName); } public abstract void function(); } /** * 添加带参数的函数* @param function {@link com.niu.myapp.myapp.view.util.Functions.FunctionWithParam} * @return */public Functions addFunction(FunctionWithParam function){if(function == null){ return this;} if(mFunctionWithParam == null){ mFunctionWithParam = new HashMap<>(1); }   mFunctionWithParam.put(function.mFunctionName,function); return this; } /** * 添加带返回值的函数 * @param function {@link com.niu.myapp.myapp.view.util.Functions.FunctionWithResult}* @return */public Functions addFunction(FunctionNoParamAndResult function){ if(function == null){ return this; } if(mFunctionNoParamAndResult == null){ mFunctionNoParamAndResult = new HashMap<>(1);} mFunctionNoParamAndResult.put(function.mFunctionName,function); return this; }/** * 根据函数名,回调无参无返回值的函数* @param funcName */ public void invokeFunc(String funcName) throws FunctionException {FunctionNoParamAndResult f = null; if(mFunctionNoParamAndResult != null){ f = mFunctionNoParamAndResult.get(funcName); if(f != null){ f.function(); } }if(f == null){ throw new FunctionException("没有此函数"); }} /** * 调用具有参数的函数* @param funcName * @param param * @param <Param> */ public <Param> void invokeFunc(String funcName,Param param)throws FunctionException{ FunctionWithParam f = null; if(mFunctionWithParam != null){ f = mFunctionWithParam.get(funcName);if(f != null){ f.function(param); } }} 
}
设计思路:
1. 用一个类来模拟Javascript中的一个Function

Function就是此类,它是一个基类,每个Functioon实例都有一个mFuncName 既然是方法(或者函数)它就有有参数和无参数之分
FunctionWithParam是Function的子类,代表有参数的方法类,方法参数通过泛型解决
FunctionNoParamAndResult是Function的子类,代表无参无返回值的方法类

2. 一个可以存放多个方法(或者函数)的类

Functions类就是此类,下面简单介绍下Functions有4个主要方法:

  • addFunction(FunctionNoParamAndResult function) 添加一个无参无返回值的方法类
  • addFunction(FunctionWithParam function) 添加一个有参无返回值的方法类
  • invokeFunc(String funcName) 根据funcName调用一个方法
  • invokeFunc(String funcName,Param param) 根据funcName调用有参无返回值的方法类
使用举例:代码地址

每个app都有的基础activity(BaseActivity)

     public abstract class BaseActivity extends FragmentActivity { /** * 为fragment设置functions,具体实现子类来做* @param fragmentId */ public void setFunctionsForFragment(int fragmentId){}}

其中的一个activity:

     public class MainActivity extends BaseActivity { @Override public void setFunctionsForFragment(int fragmentId) {super.setFunctionsForFragment(fragmentId); switch (fragmentId) {case R.id.fragment_main:FragmentManager fm = getSupportFragmentManager(); BaseFragment fragment = (BaseFragment) fm.findFragmentById(fragmentId);//开始添加functionsfragment.setFunctions(new Functions().addFunction(new Functions.FunctionNoParamAndResult(MainFragment.FUNCTION_NO_PARAM_NO_RESULT) {@Override public void function() {Toast.makeText(MainActivity.this, "成功调用无参无返回值方法", Toast.LENGTH_LONG).show(); }}).addFunction(new Functions.FunctionWithParam<Integer>(MainFragment.FUNCTION_HAS_PARAM_NO_RESULT) { @Override public void function(Integer o) { Toast.makeText(MainActivity.this, "成功调用有参无返回值方法 参数值=" + o, Toast.LENGTH_LONG).show(); } }));}}}

每个app都会有的基础fragment(BaseFragment)

     public abstract class BaseFragment extends Fragment { protected BaseActivity mBaseActivity; /** * 函数的集合 */ protected Functions mFunctions; /** * activity调用此方法进行设置Functions* @param functions */public void setFunctions(Functions functions){ this.mFunctions = functions;} @Override public void onAttach(Activity activity) { super.onAttach(activity);//呼叫activity进行回调方法的设置 if(activity instanceof BaseActivity){ mBaseActivity = (BaseActivity)activity;mBaseActivity.setFunctionsForFragment(getId());} } }

MainActivity对应的MainFragment

    public class MainFragment extends BaseFragment {/** * 没有参数没有返回值的函数 */ public static final String FUNCTION_NO_PARAM_NO_RESULT = "FUNCTION_NO_PARAM_NO_RESULT"; /** * 有参数没有返回值的函数 */public static final String FUNCTION_HAS_PARAM_NO_RESULT = "FUNCTION_HAS_PARAM_NO_RESULT"; @Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mBut1 = (Button) getView().findViewById(R.id.click1); mBut3 = (Button) getView().findViewById(R.id.click3);mBut1.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) { try {//调用无参无返回值的方法 mFunctions.invokeFunc(FUNCTION_NO_PARAM_NO_RESULT);       } catch (FunctionException e) {e.printStackTrace(); } } }); mBut3.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) {try { //调用有参无返回值的方法 mFunctions.invokeFunc(FUNCTION_HAS_PARAM_NO_RESULT, 100); } catch (FunctionException e) {e.printStackTrace(); }}}); }

看到这您是不是觉得已经结束了,当然是没有了,因为还有2个问题没解决。方法返回值和方法接收多个参数的问题。

方法返回值的问题

上代码:代码地址

    /** * 有返回值,没有参数的方法* @param <Result> */ public static abstract class FunctionWithResult<Result> extends Function{public FunctionWithResult(String functionName) { super(functionName);} public abstract Result function(); } /** * 带有参数和返回值的 方法 * @param <Result> * @param <Param> */public static abstract class FunctionWithParamAndResult<Result,Param> extends Function{ public FunctionWithParamAndResult(String functionName) { super(functionName); } public abstract Result function(Param data);}

FunctionWithResult无参数有返回值的方法类
FunctionWithParamAndResult 有参数也有返回值的方法类
在Functions类中定义添加和调用这2种方法类的 相应方法。

其次是方法含有多个参数的问题

在解决此问题时我想了很多办法(比如怎样引入多个泛型,但最终以失败告终,希望有看了这篇文章的朋友可以多提下宝贵意见)。然后我就想到了用Bundle来解决多参数的问题,把多个参数放到Bundle中,但是在往Bundle中塞入数据时得有一个对应的key值,生成key值以及记住key值(记住key值是为了从Bundle中取数据)是一个繁琐的事。同时Bundle不能传递非序列化对象。所以就封装了一个FunctionParams类解决以上问题,请看类的实现: 代码地址

  /** * 函数的参数,当函数的参数涉及到多个值时,可以用此类,* 此类使用规则:存参数与取参数的顺序必须一致,* 比如存参数顺序是new *FunctionParamsBuilder().putString("a").putString("b").putInt(100); *取的顺序也是: functionParams.getString(),   *functionParams.getString(), functionParams.getInt(); */public static class FunctionParams { private Bundle mParams = new Bundle(1); private int mIndex = -1; private Map mObjectParams = new HashMap(1); FunctionParams(Bundle mParams,Map mObjectParams){ this.mParams = mParams; this.mObjectParams = mObjectParams;} public <Param> Param getObject(Class<Param> p){ if(mObjectParams == null){ return null; } return p.cast(mObjectParams.get((mIndex++) + "")); } /** * 获取int值 * @return */ public int getInt(){if(mParams != null){ return mParams.getInt((mIndex++) + ""); } return 0; } /** * 获取int值 * @param defalut * @return */ public int getInt(int defalut){ if(mParams != null){ return mParams.getInt((mIndex++) + ""); }return defalut;} /** * 获取字符串 * @param defalut * @return */public String getString(String defalut){ if(mParams != null){ return mParams.getString((mIndex++) + ""); } return defalut; } /** * 获取字符串 * @return */ public String getString(){ if(mParams != null){return mParams.getString((mIndex++) + ""); } return null; } /** * 获取Boolean值 * @return 默认返回false */public boolean getBoolean(){ if(mParams != null){ return mParams.getBoolean((mIndex++) + ""); } return false;}/** * 该类用来创建函数参数 */public static class FunctionParamsBuilder{private Bundle mParams ;private int mIndex = -1;private Map mObjectParams = new HashMap(1); public FunctionParamsBuilder(){ } public FunctionParamsBuilder putInt(int value){if(mParams == null){ mParams = new Bundle(2);} mParams.putInt((mIndex++) + "", value); return this; } public FunctionParamsBuilder putString(String value){ if(mParams == null){ mParams = new Bundle(2);} mParams.putString((mIndex++) + "", value); return this; }public FunctionParamsBuilder putBoolean(boolean value){ if(mParams == null){ mParams = new Bundle(2); } mParams.putBoolean((mIndex++) + "", value); return this;} public FunctionParamsBuilder putObject(Object value){ if(mObjectParams == null){ mObjectParams = new HashMap(1); } mObjectParams.put((mIndex++) + "", value);return this;}public FunctionParams create(){FunctionParams instance = new FunctionParams(mParams,mObjectParams); return instance; }} 
}

FunctionParams封装了取参数的功能,比如:

   public <Param> Param getObject(Class<Param> p){ if(mObjectParams == null){ return null; }return p.cast(mObjectParams.get((mIndex++) + ""));}

取对象参数的功能,不需要传人key值,只需要传人需要即将取出来的类的Class实例即可

FunctionParamsBuilder类,看它的名字就知道是用了设计模式里的Builder(构建)模式。该类是用来存放参数的,当所有的参数都存放完毕后调用create()方法创建一个FunctionParams对象事物都是有两面性的,有缺点就有优点,只不过是在某些场合下优点大于缺点,还是反之。
FunctionParams解决了以上提到的Bundle传递多参数种种不便的问题,但同时FunctionParams也有一个缺点就是存参数的顺序与取参数的顺序一定要一致,比如:

    //存的顺序 new       FunctionParamsBuilder().putString("1").putInt(2).putBoolean(true).create(); //取的顺序 functionParams.getString(); functionParams.getInt(); functionParams.getBoolean();

但是这种缺点函数的定义来看也不是缺点。

Activity与Fragment之间的通信是通过Functions的,即把变化的部分封装在Functions是类中,Functions起一个桥梁作用。

此方案优点:

  • Fragment与Activity的耦合性几乎没有
  • 性能也好(没用反射)
  • 可以从Activity获取返回数据
  • 扩展性好(新增加的成对的Activity与Fragment之间的通信只需做以下几步:
    1.新增加Activity只需要覆盖BaseActivity中的 setFunctionsForFragment(int fragmentId) 方法,把相应的回调函数加入。
    2.相应的Fragment定义函数key值即可)

问题:大家关于传递多参数是否有更好的见解?有的话加我qq:704451290,或者发我邮箱704451290@qq.com

简单总结为以下几点:

  • Fragment的使命
  • Activity与Fragment之间通信的解决方案(handler,广播,EventBus,接口)的优缺点。
  • 我自己关于Activity与Fragment之间通信的解决方案(Functions),其实解决的主要是Fragment调用Activity的方案。

    希望大家能多提宝贵意见,多交流。代码地址

Android

这篇关于Android:Activity 与 Fragment 通信 (99%) 完美解决方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

SpringSecurity显示用户账号已被锁定的原因及解决方案

《SpringSecurity显示用户账号已被锁定的原因及解决方案》SpringSecurity中用户账号被锁定问题源于UserDetails接口方法返回值错误,解决方案是修正isAccountNon... 目录SpringSecurity显示用户账号已被锁定的解决方案1.问题出现前的工作2.问题出现原因各

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis