如何優雅的實現自己的Android元件化改造?

yifei8發表於2019-02-15

本篇文章的主要目的:

  • 幫助正在對專案進行元件化改造或者想建立元件化專案架構的小夥伴,更好的認識元件化本質。
  • 目前元件化的框架眾多,說的天花亂墜的,其本質來說其實都差不多,閱讀本文以後,讀者甚至可以摒棄這些開源框架,根據自己的專案特點,輕鬆構建自己的元件化框架。
  • 幫助想學習和了解元件化框架,並嘗試動手寫自己的開源框架的小夥伴們

什麼是元件化?

在平時的開發過程中,隨著專案需求的增加,app支援功能越來越多,如果沒有元件化的思想,程式碼會越來越臃腫。導致的問題也會越來越明顯,比如一個很小的需求變化,可能會導致程式碼的“牽一髮動全身”問題,甚至會出現很多隱藏的bug。還會導致維護成本越來越高,團隊協作低效,問題邊界不清晰。

於是,很多團隊開始有了程式碼解耦的想法,但是面對如此複雜的專案,又不敢輕易變更其中的程式碼結構,如何順利的解耦就成了很多團隊難以入手的問題。甚至由於業務的不斷迭代,導致程式碼解耦的問題遙遙無期。

當然,作為一位合格的App架構師,遇到再難的問題也會迎難而上。於是便開始對APP整體專案結構進行分析,制定解耦方案。通常情況下,解耦的最佳思路就是根據業務邊界對程式碼結構進行劃分。比如說某APP裡面包含了IM、直播、內容展示等等業務場景,於是從架構的角度來說,整個APP的架構應該是如下圖所示:

圖1

這種架構思路上很清晰,對應到我們Android程式碼結構,就是根據這些業務邊界,拆分成不同的module,module之間沒有直接的引用和依賴,程式碼完全解耦。作為團隊開發成員也有很清晰的業務邊界,程式碼維護成本大大降低,開發效率也會明顯提高,應該是一個很不錯的方案。

所謂的元件化其實就是根據業務邊界對程式碼進行解耦,不同的業務對應不同的module,這些module相互獨立,可以獨自作為一個app進行開發並獨立執行,合併時可以打包到一個整體的app中,實現完整的app功能。

如何優雅的進行元件化?

那麼問題來了,以上的架構的確是非常不錯的選擇,但是實際的業務中,很難有個清晰的邊界,並且業務與業務直接總會有銜接的地方。如果使用以上的架構,那麼這些不同的module之間又該如何進行呼叫呢?

在我們Android系統中,程式是一個獨立程式,每個程式都具有自己的虛擬機器 (VM),應用程式碼是在與其他應用隔離的環境中執行,程式直接的通訊主要是基於Binder機制。為什麼要提Binder,首先Binder是Android系統的中非常重要的實現機制,而我們元件化程式碼耦合的問題也可以借鑑其實現原理。接下來我簡單的介紹一下Binder機制,先總體看一下Binder架構圖:

Binder程式間通訊機制.png

可以看出Binder是一個典型的CS架構,程式間的通訊基於ServiceManager這個大管家,Client程式從ServiceManager中獲取Server程式的一個遠端代理,進行通訊。為了讓大家更直接的理解,我從程式碼層面上來簡單描述一下這個過程。比如我們啟動一個Activity時,需要ActivityManagerService(AMS)這個服務來進行管理,而AMS執行在SystemServer中,那麼如何獲取這AMS呢,我們從原始碼來分析(以下原始碼android-28中):

public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
         @Override
         protected IActivityManager create() {
         //通過ServiceManager.getService獲取到AMS的代理IActivityManager
         final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
         final IActivityManager am = IActivityManager.Stub.asInterface(b);
         return am;
         }
};
複製程式碼

app中通過ServiceManager.getService獲取到遠端程式中服務的代理,只需要指定服務的name就可以。

Binder機制就不展開講解,如果想了解更多的同學,可以加入到ARetrofit的QQ群中@我進行交流。接下來我們再回到“如何優雅的進行元件化”這個問題上。

在瞭解Binder機制後,對於我們元件化過程中解耦程式碼如何通訊,其實也可以採用類似的機制。當然我們的module之間並沒有跨程式,不會有跨程式通訊的問題。

我們可以將圖1中的每一個module想象成一個服務,那麼module之間的通訊其實就類似於服務之間的通訊。而服務之間的通訊其實也不需要直接進行類的引用,只需要拿到另一個module的服務代理,通過這個代理進行通訊。這裡又出了一個新的問題,如何實現提供代理服務的代理管家呢,其實這也類似於Binder裡面的ServiceManager,用以下這張架構圖來說明:

module通訊架構

上圖中IM module註冊自己的IM服務到ServiceManager中,直播 module想要和IM module只需要獲取IM的服務代理就可以進行操作。註冊的過程,對應到實際的程式碼中,其實就是中ServiceManager這個管家的Module中宣告自己的服務介面,並在自己的module中實現這一介面。其他module只需要在執行時拿到介面實現類的例項物件就可以完成這一過程了,這一部分其實可以參考ARetrofit README中 四 高階用法中登入服務介面的宣告與註冊過程就可以瞭解。

當然,這篇文章不僅僅讓大家瞭解別人已經開源好的框架,其實類似的框架很多,如ARetrofit 、ARouter、CC等開源,無關star量,這都是開源早晚和推廣的問題,其實本質上都是基於以上的原理,區別就是上層的封裝的問題,最終呈現的API是否簡潔,是否符合自己的要求,能否直觀的進行程式碼維護。

這裡我將帶著大家動手一起實現自己的ServiceManager管家。

第一步,我們在ServiceManager中註冊不同module的服務介面,如下:

public interface ILoginManager {
    void login();
    User getUser();
}
複製程式碼

第二步,在Login Module中實現該介面,如下:

@Inject //需要自動注入服務的宣告
public class LoginManagerService implement ILoginManager {
    @Override
    void login(Activity activity) {
       Intent intent = new Intent(activity, LoginActivity.class);
       activity.startActivity(intent);
    }

    @Override
    User getUser(CallBack callback) {
        //...網路或者  或者  本地資料庫 等回去非同步返回或者同步返回結果
        
    }
}
複製程式碼

第三步, 在直播 Module中跨Module獲取ILoginManager服務例項物件,這裡需要通過Android Studio自動注入框架AInject,完成跨Module自動注入流程,可參考AInject用法,具體實現如下:

 public class ServiceManager implements InjectContract {

  private static class ServiceManager {
      private static final ServiceManager instance = new ServiceManager();
  }

  static ServiceManager getInstance() {
      return InstanceHolder.instance;
  }
  
  /**
    * @Fixme 這裡建議使用實現LRU演算法的列表儲存
    */
  List<Object> services = new ArrayList();

 /**
   *
   * "@Inject" 註解標示的class 最終都會注入到該"@IMethod"註解標示過的方法中
   *  注:"@IMethod"註解標示過的方法將由編譯器自動注入實現程式碼,注入最終的程式碼如下如:
   *
   * @IMethod
   * public void iMethodName() {
   *       injectClass("injectClassName1")
   *       injectClass("injectClassName2")
   *       injectClass("injectClassName3")
   *       injectClass("injectClassName4")
   * }
   *
   * 使用者可以在該方法中通過反射完成自己的業務需求
   * @param className class name
   */
  @IMethod
  public void startInject() {
     

  }

  @Override
  public void injectClass(String serviceClassName) {
       services.clear()
       services.add(className);
  }

 /**
  * 獲取登入服務,可在任意Module直接獲取該服務的例項化物件
  */
  public static ILoginManager getILoginManager() {
        if (getInstance().service.size() == 0) {
               getInstance().startInject();
        }
        for (String className: getInstance().services) {
           try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof ILoginManager) { 
                     return obj;
                } else {
                     obj = null;
                }
            } catch (Exception e) {
              e.printStackTrace();
           }    
        }
        return null;
  }
}
複製程式碼

其實就是這麼簡單,一個元件化的框架就完成了。

看到這裡的小夥伴們,大概已經理解了如何進行解耦Module直接的通訊工作了吧。想想平時有沒有遇到其他關於高耦合的程式碼需要解耦的,都可以參考這種思路哦。

元件化並不需要一蹴而就的

前面教大家如何進行元件化,已經如何實現元件化,其實還忽略了一個非常重要的問題,就是如何對現有的專案進行元件化。現有的專案一般都已經累計了很多程式碼量,如果一次性根據業務進行拆解處理,解耦合顯然是不合實際的。那麼到底該怎麼做呢?

其實有了以上自定義的元件化框架(當然推薦建議使用作者新開源的ARetrofit框架,API使用非常簡潔),其實元件化並不是一個版本就需要完成的。元件化的第一步當然還是根據業務邊界來架構自己的APP框架,不同的Module中宣告好自己的服務。而元件化的工作可以拆分到不同的迭代版本中,對於新增的業務明確到對應的業務Module中開發,對應老的業務程式碼如果耦合度比較高的,不建議直接修改邏輯,可以先將這部分程式碼耦合的地方抽象到服務介面中,通過服務呼叫來實現呼叫過程。並在未來的版本中逐步進行剝離解耦最終實現真正的程式碼隔離,以服務的形式完成業務間的交集部分。

元件化最佳實踐

小結

前面講了很多,我們再回到主題,相信大家對於“如何實現自己的Android元件化改造”應該有了很清晰的步驟和流程來吧。此外僅代表個人的一些拙見,如果意見或者建議歡迎進ARetrofit QQ群賜教。

相關文章