1. 程式人生 > >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(){       
          @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也有一個缺點就是存引數的順序與取引數的順序一定要一致,比