本文演示如何使用依赖注入的方式实现组件化,本文假设你对什么是组件化已有必定认识,而且使用过 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 模式