专栏名称: 非非白
目录
相关文章推荐
黑龙江政务  ·  第九届亚冬会规模史上空前 开幕式亮点抢先看 ·  11 小时前  
飞鲸投研  ·  绑定DeepSeek,科大讯飞,势不可挡! ·  14 小时前  
飞鲸投研  ·  绑定DeepSeek,科大讯飞,势不可挡! ·  14 小时前  
光明日报  ·  亚冬会火炬传递启动! ·  3 天前  
51好读  ›  专栏  ›  非非白

依赖注入实现组件化

非非白  · 掘金  ·  · 2018-04-03 08:49

正文

依赖注入实现组件化

本文演示如何使用依赖注入的方式实现组件化,本文假设你对什么是组件化已有一定认识,并且使用过 dagger2。

本文所说的组件均是指业务组件,包括有 UI 的业务组件和 UI 无关的业务组件,业务所依赖的基础类库均是指类库,包括第三方的和公司内部的。

为了方便演示,本文所有的组件都放在同一个项目工程中,在同一个 git 仓库中,实际的场景可能是项目独有的组件放在同一个工程目录中,以及在同一个 git 仓库中,可以被多个项目共享的业务模块放在单独的工程目录以及 git 仓库中。

本文配套的示范项目: android-modularization

组件划分

组件化的一个前提是划分组件,如图,在我们 demo 中,有一个 app 主工程,另有五个模块工程,它们之间的依赖关系如下:

app 主工程负责组装组件,它需要知道每一个组件

common-ui 是基础的 UI 组件,它不依赖其它任何组件

business-a-ui 和 business-b-ui 是 UI 模块,它们都依赖于 common-ui,但彼此之间互不依赖

common-api 是抽象的 UI 无关的业务组件,它不依赖其它任何组件

business-c 是具体的 UI 无关的业务组件,它依赖 common-api

bisiness-a-ui 组件依赖 common-api,也就是说,UI 组件可以依赖纯业务组件

所有的组件都不依赖主工程

依赖是指我们在模块工程的 build.gradle 文件中有这样的代码

implementation project(':common-ui')

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






请到「今天看啥」查看全文