preface

Recently I have been thinking about whether there is a better solution to solve the problem: The best way to communicate between an Activity and a Fragment on Android is to make the Fragment reusable, have good performance (no reflection), and maintain the code. There is no need to define the interface between each pair of activities and fragments.

Let’s talk a little bit about Javascript, and some people might ask: Aren’t we talking about Java for Android? We’re talking about JavaScript. Because it’s where my solution came from, so if you’re not interested, you can skip it. Recently in learning javascript this language, at the same time their own Android (Java) development also has more than 5 years, so in the process of learning JS, will be inertial to compare the two.

Javascript is a “loose” and “loose” (broad) language compared to the rigor of Java. Here’s an explanation of why the word “Bohemian” is used:

[explanation] uninhibited [fang dang bu jī] Unrestrained, undisciplined, unrestrained.

Because I think this word can fully reflect the characteristics of JS weak type. To assign a value to a variable, write:

var a = 1;Copy the code

You can also write:

 var b = '123'; 
var o = new Object();Copy the code

You could even write:

var fun = new function(){};
 fun1 = new function(){};Copy the code

We can assign any type of value to a variable, and we can declare a variable without the var keyword.

“Informality” mainly reflects the broader, simpler syntax of JavaScript: for example:

Function Max (a,b){return a > b? a:b; */ function print(){var len = arguments.length; for(var i = 0; i < len; i++){ console.log(arguments[i]); Int Max (int a, int b){return a> b? a:b; } /* * Pass any number of arguments */ void print(Object... args){ for (int i = 0; i < args.length; i++){ System.out.println(args[i]); }}Copy the code

The above code shows that JavaScript does not have the same strict rules when declaring functions as Java does, and that the syntax is more relaxed and simpler (no bad words for Java here).

Inspired by the point

There is an important point in JavaScript (everything is an object) that functions are not included, and functions can be arguments to another function, such as:

Var array = [1,2, 'hello', 'no',31,15]; var array = [1,2, 'hello', 'no',31,15]; // Array each method receives a function testarray. each(function(value){typeof value == 'number'? alert( value *10 ):null; });Copy the code

When I saw the use of JavaScript functions above, I was amazed. Why can’t I use them for reference to solve the communication problem between Activities and fragments in Android?

The mission of fragments

Let’s talk about why fragments appear first. This will help us solve the communication between activities and fragments. The creation of a new thing is always to solve the problems existing in the old thing, Fragment is the product of android3.0, before android3.0 to solve the adaptation of mobile phones and tablets is a headache, those who are familiar with ActivityGroup, Performance issues such as switching between activities wrapped in ActivityGroup should be well understood. Hence the Fragment was born. Personal summary of Fragment’s mission:

  • Solve the adaptation of mobile phones, tablets and other devices
  • Resolve performance issues when switching between multiple activities
  • Modularity, because modularity leads to reuse benefits

The use of fragments

Fragments can be wrapped in multiple different activities. An Activity can be wrapped in multiple fragments at the same time. An Activity is a large container that can manage multiple fragments. There are dependencies between all activities and fragments.

Communication scheme between Activity and Fragment

As mentioned above, activities and fragments are dependent on each other, so they are bound to be involved in communication, and reference between objects is bound to be involved in solving communication problems. Because fragments have an important mission: modularity to improve reusability. To do this, fragments must be highly cohesive and poorly coupled.

Handler, broadcast, EvnetBus, interface, etc. (there may be other solutions, please share more), let’s talk about these solutions.

Handler:

On the first code

Public class MainActivity extends FragmentActivity{// Declare a Handler public Handler mHandler = new Handler(){@override public void handleMessage(Message msg) { super.handleMessage(msg); . Corresponding handling code}}... } public class MainFragment extends Fragment{private handler mHandler; @Override public void onAttach(Activity activity) { super.onAttach(activity); If (activity instance MainActivity){mHandler = ((MainActivity)activity).mHandler; }}... Corresponding processing code}Copy the code

Disadvantages of the scheme:

  • Fragments are coupled to specific activities, which is not conducive to Fragment reuse
  • Bad for maintenance. If you want to delete the corresponding Activity, you have to change the Fragment
  • Unable to get data returned from the Activity
  • The use of handler is very uncomfortable for me.

Broadcast scheme:

The specific code will not write, say the disadvantages of the scheme:

  • Using radio to solve this problem is a bit overkill. I feel that the intention of radio is to use one-to-many, and the receiver is unknown
  • Broadcast performance will definitely be poor (don’t tell me performance is not an issue, it is a big issue for mobile phones)
  • There are limitations to propagating data (serialization interface must be implemented). Think of these disadvantages for a moment, but let’s brainstorm some other disadvantages.

EventBus plan:

The specific use of EventBus can be searched by yourself, and personal views on this solution are as follows:

  • EventBus is implemented with reflection mechanism, there will be performance issues (don’t tell me that performance is not a problem, performance is a big problem for mobile phones)
  • EventBus is difficult to maintain code
  • Unable to get data returned from the Activity

Interface scheme

I think this scheme is the most easy to think of, the most used one, the specific code:

Public class MainActivity extends FragmentActivity implements FragmentListener{public class MainActivity implements FragmentActivity implements FragmentListener{ @override public void toH5Page(){ } ... } public class MainFragment extends Fragment{public FragmentListener mListener; //MainFragment open interface public static interface FragmentListener{// jump toH5Page void toH5Page(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); If (Activity Instance FragmentListener){mListener = ((FragmentListener) Activity); }}... Other processing code omitted}Copy the code

This solution should be able to achieve both reuse and good maintainability, and the performance is also great. However, the only regret is that if the project becomes too large, the number of activities and fragments will increase, and defining the interface for each pair of activities and fragments can be a headache (including naming the interface, and implementing the corresponding Activity for the newly defined interface). The corresponding Fragment has to be cast). For a better solution, see the section below.

Big drawing also

An oft-mentioned concept in design patterns is to encapsulate change, and inspired by the fact that javascript functions can be function objects as parameters, I came up with the following idea: code address

/** * + Created by niuxiaowei on 2016/1/20. /** * + Created by niuxiaowei on 2016/1/20. Public class Functions {private HashMap mFunctionWithParam; private HashMap mFunctionWithParam; / / no parameters and return values of collection methods, the same key value for the method name private HashMap mFunctionNoParamAndResult; Public static abstract class Function{public String functionName; // Public static abstract class Function{public String functionname; public Function(String functionName){ this.mFunctionName = functionName; }} /** * public static abstract class FunctionWithParam 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(); } / add parameters function * * * * @ param function {@ link com. Niu. Myapp. Myapp. The util. Functions provides the 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 added with a return value function function {@ link com. Niu. Myapp. Myapp. The util. Functions provides the 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; } /** * from the function name, * @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(" no such function "); }} /** * invokeFunc(String funcName, param) public void invokeFunc(String funcName, param param)throws FunctionException{ FunctionWithParam f = null; if(mFunctionWithParam ! = null){ f = mFunctionWithParam.get(funcName); if(f ! = null){ f.function(param); }}}}Copy the code

Design idea:

1. Use a class to simulate a Function in Javascript

Function is one of these, it’s a base class, every Functioon instance has a mFuncName and since Functioon is a method (or Function) it has parameters and no parameters FunctionWithParam is a subclass of Function, it stands for method class with parameters, FunctionNoParamAndResult is a subclass of Function and represents a method class with no parameters and no return value

2. A class that can hold multiple methods (or functions)

Functions classes are such, and there are four main methods for Functions:

  • AddFunction (FunctionNoParamAndResult function) adds a method class with no parameters and no return value
  • AddFunction (FunctionWithParam function) adds a method class with parameters and no return value
  • InvokeFunc (String funcName) invokes a method based on funcName
  • InvokeFunc (String funcName,Param Param) invokes a method class with arguments and no return value according to funcName

Examples:The code address

Every app has a BaseActivity (BaseActivity)

Public abstract class BaseActivity extends FragmentActivity {/** * sets functions for fragments, Public void setFunctionsForFragment(int fragmentId){}}Copy the code

One of the activities:

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); // start adding functions fragment.setfunctions (new functions ().addfunction (new) Functions.FunctionNoParamAndResult(MainFragment.FUNCTION_NO_PARAM_NO_RESULT) { @Override public void function() { Toast.maketext (mainactivity.this, "succeeded in calling method with no arguments and no return value ", toast.length_long).show(); } }). addFunction(new Functions.FunctionWithParam(MainFragment.FUNCTION_HAS_PARAM_NO_RESULT) { @Override public void Function (Integer o) {toast.maketext (mainactivity. this, "+ o, toast.length_long).show(); }})); }}}Copy the code

Every app will have a BaseFragment

public abstract class BaseFragment extends Fragment { protected BaseActivity mBaseActivity; /** * protected Functions; /** * activity calls this method to setFunctions * @param Functions */ public void setFunctions(Functions){this.mfunctions = functions; } @Override public void onAttach(Activity activity) { super.onAttach(activity); If (Activity instanceof BaseActivity){mBaseActivity = (BaseActivity)activity; mBaseActivity.setFunctionsForFragment(getId()); }}}Copy the code

MainActivity Indicates the Corresponding MainFragment

Public class MainFragment extends BaseFragment {/** * A function with no parameters and no return value */ 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"; @Override public 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 () {@override public void onClick(View v) {try {// Call a method with no arguments and no return value mFunctions.invokeFunc( FUNCTION_NO_PARAM_NO_RESULT); } catch (FunctionException e) { e.printStackTrace(); }}}); Mbut3.setonclicklistener (new view.onClickListener () {@override public void onClick(View v) {try {// call a method with arguments and no return value mFunctions.invokeFunc( FUNCTION_HAS_PARAM_NO_RESULT, 100); } catch (FunctionException e) { e.printStackTrace(); }}}); }Copy the code

Do you think it is over? Of course it is not, because there are still two problems to be solved. Problems with methods returning values and methods receiving multiple parameters.

Method return value problem

Code: code address

/** * returns a value, * @param */ public static abstract class FunctionWithResult extends Function{public FunctionWithResult(String) functionName) { super(functionName); } public abstract Result function(); } / * * * with the method of parameters and return values * @ @ param param * * / public static abstract class FunctionWithParamAndResult extends the Function { public FunctionWithParamAndResult(String functionName) { super(functionName); } public abstract Result function(Param data); }Copy the code

FunctionWithResult parameterless method returns a value of type FunctionWithParamAndResult parameters and return values defined classes in Functions provides the method of adding and call the corresponding method of these two methods.

The second problem is that the method has multiple parameters

I tried a lot of things to solve this problem (like how to introduce multiple generics, but it didn’t work out. I hope anyone who read this article can give me some valuable advice). Then I came up with the idea of using the Bundle to solve the problem of multiple parameters. I put multiple parameters into the Bundle, but there must be a corresponding key value when inserting data into the Bundle. Generating the key value and remembering the key value (remember the key value is to fetch data from the Bundle) is a tedious task. At the same time, the Bundle cannot pass unserialized objects. So a FunctionParams class is encapsulated to solve these problems, see the implementation of the class: code address

/** * this class can be used when a function argument involves more than one value. The order of saving parameters and retrieving parameters must be the same, * for example, the order of saving parameters is new *FunctionParamsBuilder().putString("a").putString("b").putint (100); * The order is also: 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 getObject(Class p){ if(mObjectParams == null){ return null; } return p.cast(mObjectParams.get((mIndex++) + "")); } @return */ public int getInt(){if(mParams! = null){ return mParams.getInt((mIndex++) + ""); } return 0; } @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; } 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; }}}Copy the code

FunctionParams encapsulates the ability to fetch parameters, such as:

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

The function of fetching object parameters does not need to pass the key value, but only the Class instance of the Class to be fetched

The FunctionParamsBuilder class, as its name suggests, uses the Builder pattern from design mode. This class is used to store parameters, and when all the parameters are stored, the create() method is called to create a FunctionParams object. Everything has two sides. FunctionParams solves the above mentioned inconvenience of passing multiple arguments through bundles. However, FunctionParams also has the disadvantage that the order of storing parameters must be the same as the order of fetching parameters. For example:

New FunctionParamsBuilder().putString("1").putint (2).putBoolean(true).create(); Functionparams.getstring (); functionParams.getInt(); functionParams.getBoolean();Copy the code

But the definition of this disadvantage function is not a disadvantage either.

The communication between activities and fragments is through Functions, that is, by encapsulating the changing parts in Functions are classes, Functions act as Bridges.

Advantages of this scheme:

  • Fragment and Activity coupling is almost non-existent
  • Performance is good (no reflection)
  • The returned data can be retrieved from the Activity
  • Good scalability (The newly added pair of activities and Fragment communication only need to do the following steps: 1. To add an Activity, simply override the setFunctionsForFragment(int fragmentId) method in BaseActivity and add the corresponding callback function. Fragment = Fragment; Fragment = Fragment;

Question: Do you have a better idea about passing multiple parameters? If you have my qq: 704451290, or send my email [email protected]

Briefly summarized as the following points:

  • The mission of fragments
  • Advantages and disadvantages of solutions for communicating between activities and Fragments (handler, broadcast, EventBus, interface).
  • My own solutions for communication between activities and fragments (Functions) are mainly solutions for activities called by fragments.

    I hope you can make more valuable suggestions and exchanges. The code address