本文演示如何使用依賴注入的方式實現組件化,本文假設你對什麼是組件化已有必定認識,而且使用過 dagger2。java
本文所說的組件均是指業務組件,包括有 UI 的業務組件和 UI 無關的業務組件,業務所依賴的基礎類庫均是指類庫,包括第三方的和公司內部的。android
爲了方便演示,本文全部的組件都放在同一個項目工程中,在同一個 git 倉庫中,實際的場景多是項目獨有的組件放在同一個工程目錄中,以及在同一個 git 倉庫中,能夠被多個項目共享的業務模塊放在單獨的工程目錄以及 git 倉庫中。git
本文配套的示範項目:android-modularizationgithub
組件化的一個前提是劃分組件,如圖,在咱們 demo 中,有一個 app 主工程,另有五個模塊工程,它們之間的依賴關係以下:api
app 主工程負責組裝組件,它須要知道每個組件架構
common-ui 是基礎的 UI 組件,它不依賴其它任何組件app
business-a-ui 和 business-b-ui 是 UI 模塊,它們都依賴於 common-ui,但彼此之間互不依賴ide
common-api 是抽象的 UI 無關的業務組件,它不依賴其它任何組件組件化
business-c 是具體的 UI 無關的業務組件,它依賴 common-apigradle
bisiness-a-ui 組件依賴 common-api,也就是說,UI 組件能夠依賴純業務組件
全部的組件都不依賴主工程
依賴是指咱們在模塊工程的 build.gradle 文件中有這樣的代碼
implementation project(':common-ui')
複製代碼
UI 組件是指有 UI 的業務組件
無論作什麼樣的 UI 界面,都離不開 Activity 或者 Fragment,想要從一個界面跳到另一個界面,也只須要知道另一個界面所屬的 Activity 或者 Fragment 便可。
一般,咱們不會直接使用 Android Framework 爲咱們提供的 Activity、Fragment,而是搞一個基類,好比 BaseActivity,BaseFragment。
本文采用單 Activity 架構方式爲你們演示如何使用依賴注入實現組件化。單 Activity 架構不是組件化必須的,這純粹是出於我的偏好。單 Activity 機構是指整個項目中基本只有一個 Activity,其他界面全是 Fragment。
本文使用的單 Activity 架構類庫是 AndroidNavigation,它很好地解決了 fragment 嵌套,跳轉等 fragment 相關問題,同時解決了狀態欄相關問題。
先來看看咱們都有哪些 UI 組件
咱們在 common-ui 中定義了兩個基類: BaseActivity 和 BaseFragment
// common-ui/BaseActivity.java
// AwesomeActivity 是 AndroidNavigation 中的類
// HasSupportFragmentInjector 接口是 dagger2 的,用於依賴注入
public abstract class BaseActivity extends AwesomeActivity implements HasSupportFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> supportFragmentInjector;
@Override
protected void onCreate(Bundle savedInstanceState) {
// activity 注入須要這一行
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return supportFragmentInjector;
}
}
複製代碼
// common-ui/BaseFragment.java
// AwesomeFragment 是 AndroidNavigation 中的類
public abstract class BaseFragment extends AwesomeFragment {
@Override
public void onAttach(Context context) {
// fragment 注入須要這一行
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
}
複製代碼
同時還定義了一個接口,用於建立跨組件跳轉的 Fragment。
// common-ui/UIComponentFactory.java
public interface UIComponentFactory {
BaseFragment createFragment(String moduleName);
}
複製代碼
由於咱們是單 Activity 架構,同時爲了簡單演示,因此這裏只演示 Fragment 跳轉。實際工程中,即便是單 Activity 架構,除了 MainActivity,也會有少許的 Activity,好比 WebViewActivity,它可能運行在另一個進程以免某些問題,像這種狀況這裏就不做演示了。
上面咱們說到,business-a-ui 和 business-b-ui 彼此之間互不依賴,但若是 business-a-ui 中的頁面想要跳到 business-b-ui 中的頁面,怎麼辦呢? 讓咱們從 UI 依賴的角度來看
圖中,實心箭頭表示依賴,空心箭頭表示實現。
咱們的 app 主工程,business-a-ui,business-b-ui 都依賴 common-ui
app 主工程實現了定義在 common-ui 中的 UIComponentFactory 這個接口。
具體流程以下:
明確 app 主工程依賴
// app/build.gradle
dependencies {
implementation project(':common-ui')
implementation project(':business-a-ui')
implementation project(':business-b-ui')
}
複製代碼
app 主工程定義一個類來註冊每一個 UI 組件須要向外暴露的模塊
// app/UIComponentRegistry.java
@Singleton
public class UIComponentRegistry {
@Inject
public UIComponentRegistry() {
Log.w("Dagger", "UIComponentRegistry");
}
private HashMap<String, Class<? extends BaseFragment>> uiModules = new HashMap<>();
public void registerModule(String moduleName, Class<? extends BaseFragment> clazz) {
uiModules.put(moduleName, clazz);
}
public Class<? extends BaseFragment> moduleClassForName(String moduleName) {
return uiModules.get(moduleName);
}
}
複製代碼
在應用啓動時,註冊模塊
// app/MainApplication.java
import me.listenzz.businessa.AFragment;
import me.listenzz.businessb.EFragment;
import me.listenzz.businessb.FFragment;
public class MainApplication extends Application {
@Inject
UIComponentRegistry uiComponentRegistry;
@Override
public void onCreate() {
super.onCreate();
// business-a-ui 組件一共有 AFragment,BFragment,CFragment 三個 fragment
// 在這裏,僅註冊一個 fragment 做爲入口
uiComponentRegistry.registerModule("A", AFragment.class);
// business-b-ui 有兩個入口
uiComponentRegistry.registerModule("E", EFragment.class);
uiComponentRegistry.registerModule("F", FFragment.class);
}
}
複製代碼
app 主工程實現定義在 common-ui 中的 UIComponentFactory 這個接口
// app/UIComponentFactoryImpl.java
public class UIComponentFactoryImpl implements UIComponentFactory {
private UIComponentRegistry uiComponentRegistry;
@Inject
public UIComponentFactoryImpl(UIComponentRegistry uiComponentRegistry) {
this.uiComponentRegistry = uiComponentRegistry;
}
@Override
public BaseFragment createFragment(String moduleName) {
Class<? extends BaseFragment> fragmentClass = uiComponentRegistry.moduleClassForName(moduleName);
if (fragmentClass == null) {
// DEBUG 環境下崩潰,Release 環境下可返回 404 頁面
throw new IllegalArgumentException("未能找到名爲 " + moduleName + " 的模塊,你是否忘了註冊?");
}
BaseFragment fragment = null;
try {
fragment = fragmentClass.newInstance();
} catch (Exception e) {
// ignore
}
return fragment;
}
}
複製代碼
在 dagger 模塊中聲明實現和接口的關係
// app/AppModule.java
@Module
public abstract class AppModule {
@Binds
@Singleton
abstract UIComponentFactory uiComponentFactory(UIComponentFactoryImpl uiComponentFactory);
}
複製代碼
若是 AFragment(business-a-ui) 想要跳到 EFragment(business-b-ui)
// business-a-ui/AFragment.java
public class AFragment extends BaseFragment {
@Inject
UIComponentFactory uiComponentFactory;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.a_fragment_a, container, false);
root.findViewById(R.id.to_b_e).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 模塊間跳轉,須要經過工廠方法來獲取目標頁面
BaseFragment fragment = uiComponentFactory.createFragment("E");
getNavigationFragment().pushFragment(fragment);
}
});
return root;
}
}
複製代碼
在這個過程當中,business-a-ui 不知道 business-b-ui,也根本沒法知道 "E" 對應的是哪一個類,是如何實現的,是原生界面?是 RN 界面?總之,除了知道對方聽從 BaseFragment 外,一無所知。
此外 business-a-ui 也對 UIComponentFactory 是如何實現的一無所知。app 主工程實現了 UIComponentFactory,但 business-a-ui 並不依賴主工程。
UI 組件無需對外提供業務接口,它們只須要註冊入口模塊便可
UI 組件的基類是特殊的接口,它有不少實現,咱們經過工廠方法返回合適的實現
業務組件是指 UI 無關的業務組件。
和 UI 組件不一樣的是,系統並無爲咱們的業務提供基類。由於咱們的業務是惟一的獨特的,咱們須要自定義接口
若是須要依賴業務組件,那麼依賴接口,而不是實現
當咱們獲取一個 UI 模塊時,咱們獲得的是基類的引用
當咱們獲取一個業務模塊時,咱們獲得的是一個接口的引用
實際面向的都是抽象
業務組件不像 UI 組件那樣須要註冊,但它們須要定義接口
咱們在 common-api 中,定義了一個業務接口,以及相關的一個 PO
// common-api/Account.java
// PO
public class Account {
public Account(String username, String type) {
this.username = username;
this.type = type;
}
public String username;
public String type;
}
複製代碼
// common-api/AccountManager.java
public interface AccountManager {
Account login(String username, String password);
void invalidate();
}
複製代碼
business-c 依賴 common-api 並實現了 AccountManager
// business-c/AccountManagerImpl.java
public class AccountManagerImpl implements AccountManager {
@Inject
public AccountManagerImpl() {
}
@Override
public Account login(String username, String password) {
return new Account(username, "password");
}
@Override
public void invalidate() {
//...
}
}
複製代碼
business-a-ui 依賴 common-api 而且想要使用 AccountManager, 可 AccountManager 只是個接口,怎麼辦呢?
仍是須要主工程來組裝
business-c 先定義一個 dagger 模塊,把實現和接口綁在一塊兒
// business-c/CModule.java
@Module
public abstract class CModule {
@Binds
@Singleton
abstract AccountManager provideAccountManager(AccountManagerImpl accountManager);
}
複製代碼
app 主工程把該模塊加入到依賴圖中
// app/AppComponent.java
@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
CModule.class, // 來自 business-c
})
public interface AppComponent extends AndroidInjector<MainApplication> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MainApplication> {
}
}
複製代碼
business-a-ui 在代碼中聲明依賴
// business-a-ui/AFragment.java
public class AFragment extends BaseFragment {
@Inject
AccountManager accountManager;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setTitle("A 模塊 A 頁面");
Account account = accountManager.login("listenzz", "123456");
textView.setText("用戶名:" +account.username + "\n" + "登陸方式:" + account.type);
}
}
複製代碼
就這樣,business-a-ui 用上了 AccountManager,但它對 business-c 一無所知。咱們隨時能夠用 business-d 來取代 business-c,而對 business-a-ui 毫無影響。
業務組件是指 UI 無關的業務組件
UI組件是指有 UI 的業務組件
有些時候,有些業務組件是沒有 UI 的,它們可能運行在後臺,當某些事件發生時,可能須要調起 UI 界面以通知用戶。有兩種方式來處理這種業務組件須要調起 UI 組件的狀況。一種是採用訂閱/發佈機制,具體的實現有 EventBus,LocalBroadcast 等等,另外一種是使用代理(delegate)。
Delegate pattern, 就是遇着這事,我不知道怎麼辦,因而我找了個代理,將此事委派與他。
來看 PPT
下面咱們就來演示如何使用代理來實現業務組件調起 UI 組件
假設 business-c 這個 UI 無關的組件在登陸已通過期無效的狀況下,須要通知 UI 層,如何是好呢?
首先在 common-api 中定義一個接口
// common-api/AccountManagerDelegate.java
public interface AccountManagerDelegate {
void onInvalidation();
}
複製代碼
咱們來看 common-api 和 business-c 中都有哪些類
當 AccountManagerDelegate 和 AccountManager 放在一塊兒時,已經代表了設計意圖,有經驗的工程師會知道在實現 AccountManager 的過程當中,須要依賴 AccountManagerDelegate。
business-c 在實現 AccountManager 時聲明依賴 AccountManagerDelegate,並調用其中的方法
// business-c/AccountManagerImpl.java
public class AccountManagerImpl implements AccountManager {
private AccountManagerDelegate delegate;
@Inject
public AccountManagerImpl(AccountManagerDelegate delegate) {
this.delegate = delegate;
}
@Override
public void invalidate() {
this.delegate.onInvalidation();
}
}
複製代碼
app 主工程實現這一代理接口,跳到登陸界面。
// app/AccountManagerDelegateImpl.java
@Singleton
public class AccountManagerDelegateImpl implements AccountManagerDelegate {
private MainApplication application;
@Inject
public AccountManagerDelegateImpl(MainApplication application) {
this.application = application;
}
@Override
public void onInvalidation() {
Log.w("Dagger", "onInvalidation");
if (application.mainActivity != null) {
NavigationFragment navigationFragment = new NavigationFragment();
navigationFragment.setRootFragment(new LoginFragment());
application.mainActivity.presentFragment(navigationFragment);
} else {
// do something
}
}
}
複製代碼
實際開發中,登陸界面不是由 app 主工程親自實現的,而是由其它 UI 組件實現,主工程在實現這一代理的過程當中依賴其它 UI 組件便可。
app 主工程將這一實現和接口綁定
// app/AppModule.java
@Module
public abstract class AppModule {
@Binds
@Singleton
abstract AccountManagerDelegate accountManagerDelegate(AccountManagerDelegateImpl delegate);
}
複製代碼
就這樣,當 AccountManager 的 invalidate 方法被調用時,就會喚起一個 UI 界面,可是在這個過程當中業務模塊卻沒有依賴 UI 模塊。
若是某個組件聲明瞭依賴而又沒有組裝對應的實現,那麼是編譯不過去的,因此不用擔憂只有接口沒有實現的狀況。
若是在使用 dagger 的過程當中編譯不過去,能夠把 MainApplication 中建立 AppComponent 的代碼先註釋掉,而後再次編譯,你會獲得正確的提示
public class MainApplication extends Application implements HasActivityInjector, HasSupportFragmentInjector {
@Override
public void onCreate() {
super.onCreate();
// AppComponent.Builder builder = DaggerAppComponent.builder();
// builder.seedInstance(this);
// builder.build().inject(this);
}
}
複製代碼
主工程負責組裝其它組件工程
UI 組件互不依賴,想要跳轉,經過工廠方法和模塊名建立目標頁面實例
業務組件分離接口和實現
UI 組件能夠依賴業務組件,但反過來不行
UI 組件不能直接依賴業務組件的實現,而應依賴其接口
UI 組件內部能夠有本身獨立的業務類和 PO,但不對外公開
業務組件若是須要調起 UI,能夠經過事件或代理的方式
組件內部能夠有本身的分層結構,好比某 UI 組件使用 MVVM 模式