解決元件化中 ModuleApplication 共存問題

段淺淺兒發表於2018-09-07

一個美好的設想

元件化的目的是為了業務解耦,每個業務模組需要不同的功能,例如車輛詳情模組需要第三方分享,城市定位模組需要百度地位等。有些特殊功能的初始化需要在 Application 中去做,但是這些功能並非全部業務元件都用到的東西,放到 BaseApplication 不合適。

因此,我想這樣操作:

  • 模組共有的初始化,放入BaseApplication 中。
  • 模組自身的特殊功能初始化,放在自己的 Application。

設想是美好的,但實現前需要先思考一個問題:

多 Module 專案開發的時候,app module 和 library module 的 都有不同的自定義 Application ,可以共存並且自動合併嗎?

答案是 No。

為什麼不可以?

首先,自定義 Application 需要宣告在 AndroidManifest.xml 中。其次,每個 Module 都有該清單檔案,但是最終的 APK 檔案只能包含一個。因此,在構建應用時,Gradle 構建會將所有清單檔案合併到一個封裝到 APK 的清單檔案中。

合併的優先順序是:

App Module > Library Module

合併的規則:

解決元件化中 ModuleApplication 共存問題

結合我們的情況,是值 A 合併值 B,會產生衝突錯誤,如下是我的親身試法:

Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed : Attribute application@name value=(com.baseres.BaseApplication) from AndroidManifest.xml:8:9-51
  	is also present at [:carcomponent] AndroidManifest.xml:14:9-55 value=(com.carcomponent.CarApplication).
  	Suggestion: add 'tools:replace="android:name"' to <application> element at AndroidManifest.xml:7:5-24:19 to override.

複製程式碼

錯誤資訊中給出瞭解決建議,在高優先順序的 App Module 中使用 tools:replace="android:name",但這樣做是直接用值 A 替換了值 B,並非我們想要的結果。

除了上面報錯的方法,另外再推薦給大家一個方法,開啟 App Module 的 AndroidManifest.xml 檔案,選擇下方 Merged Manifest 選項卡,可以看到預合併結果。

解決元件化中 ModuleApplication 共存問題

以上我們就明白為什麼不同的 Application 不能共存的原因。那還有其他方法去實現美好設想嗎?

這次的答案是 Yes !

強烈感謝反射的存在

回想需求來源,是需要在 Application 建立時期,實現模組的特殊功能初始化,初始化時間和初始化內容 是確定無誤,問題核心是如何連線兩者。直接連線方式實踐失敗,只能採用間接方式。一提到間接,於是想起來了反射。

  • 初始化內容,假設現在寫在 ModuleA 的 A 類,ModuleB 的 B 類中的 init() 方法中。
  • 最終 Application 的 onCreate 中通過反射拿到 A 類 和 B 類,呼叫各自的 init() 方法。

以上就已經解決我們的問題,但是為了 A 類 和 B 類 中的初始化方法名稱保持一致,最好使用介面強制規範。建立介面 IComponentApplication,其中定義好方法簽名,讓 A 類和 B 類都實現它。如此,介面應放在 基礎庫 BaseRes 中,反射呼叫內容放在 BaseApplication 最為合適。

所有的思維邏輯演變成程式碼是這樣紙的:


Module A:

private class A implements IComponentApplication{

	public void init(Application application){
	   // ModuleA的初始化
	}
	
}

Module B:

private class B implements IComponentApplication{

	public void init(Application application){
	   // ModuleB的初始化
	}
	
}


BaseRes 中:

public interface IComponentApplication {

	void init(Application application);

}

public class MyApplication extends Application {
	private static final String[] MODULESLIST =
	              {"com.moduleA.A",
	              "com.moduleA.B"};
	
	@Override
	public void onCreate() {
		super.onCreate();
	
		//Module類的APP初始化
		modulesApplicationInit();
	}
	
	private void modulesApplicationInit(){
		 for (String moduleImpl : MODULESLIST){
		     try {
		         Class<?> clazz = Class.forName(moduleImpl);
		         Object obj = clazz.newInstance();
		         if (obj instanceof IComponentApplication){
		             ((IComponentApplication) obj).init(BaseApplication.getInstance());
		         }
		     } catch (ClassNotFoundException e) {
		         e.printStackTrace();
		     } catch (IllegalAccessException e) {
		         e.printStackTrace();
		     } catch (InstantiationException e) {
		         e.printStackTrace();
		     }
		  }
	}
}

複製程式碼

最後有福利

福利是元件化系列部落格所說的方案,寫出來了一個Demo,傳送門在這裡 你們可能需要的元件化 Demo。說是福利,大概是我的臉太大,技術淺薄,歡迎指正和交流。

另外,關於 AndroidManifest.xml 的合併,詳細瞭解可以看這裡合併多個清單檔案 Google 官方文件,需要自備交通工具。

最後是我的元件化系列文章,內容簡單易懂,希望對你能有所幫助:

是什麼讓我開始了元件化?

解決元件化中 ModuleApplication 共存問題

下篇文章見。

歡迎關注博主的微信公眾號,快快加入哦,期待與你一起成長!

歡迎關注博主的微信公眾號,快快加入,期待與你一起成長!

相關文章