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(){       
          @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是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無參數有返回值的方法類
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,或者發我郵箱[email protected]

簡單總結爲以下幾點:

  • Fragment的使命
  • Activity與Fragment之間通信的解決方案(handler,廣播,EventBus,接口)的優缺點。
  • 我自己關於Activity與Fragment之間通信的解決方案(Functions),其實解決的主要是Fragment調用Activity的方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章