Android:Activity與Fragment通信(99%)完美解決方案
來自: http://www.jianshu.com/p/1b824e26105b
前言
最近一直在想著能否有一種更好的方案來解決: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]); } }</pre>上面的代碼說明了JavaScript在聲明函數時,不會有像java那么嚴格的規定,語法不拘小節,語法更簡單(這里沒有說java不好的意思)。
啟發點
JavaScript中有一個重要的點(萬事萬物皆對象),函數也不列外,并且函數可以作為另外一個函數的參數,如:
js代碼: //遍歷一個數組如果是它是數組,就把它乘以10再輸出 var array = [1,2, '你好' , '不' ,31,15];10 ):null; }) ;</pre>
//數組的each方法接收一個函數 testArray.each( function( value ){ typeof value == 'number' ? alert( value當我看到上面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(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); ...相應的處理代碼 } } ...相應的處理代碼 } public class MainFragment extends Fragment{ //保存Activity傳遞的handler private Handler mHandler; @Override public 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{ @override public 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<Param>是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); //開始添加functions fragment.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"; @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 { //調用無參無返回值的方法 mFunctions.invokeFunc( FUNCTION_NO_PARAM_NO_RESULT); } catch (FunctionException e) { e.printStackTrace(); } } }); mBut3.setOnClickListener(new View.OnClickListener() { @Override public 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<Result>無參數有返回值的方法類
FunctionWithParamAndResult<Result,Param> 有參數也有返回值的方法類
在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的方案。
希望大家能多提寶貴意見,多交流。代碼地址
</ul>