{"id":19269319,"url":"https://github.com/listenzz/android-modularization","last_synced_at":"2025-04-21T20:32:34.845Z","repository":{"id":95812873,"uuid":"127848792","full_name":"listenzz/android-modularization","owner":"listenzz","description":"An android modularization demo with dagger2","archived":false,"fork":false,"pushed_at":"2018-04-04T02:34:06.000Z","size":1571,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T15:56:24.082Z","etag":null,"topics":["android","component","modularization"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/listenzz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-03T04:02:04.000Z","updated_at":"2025-01-01T13:00:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"f39dba21-d2ef-48d8-8956-8d41e06a1766","html_url":"https://github.com/listenzz/android-modularization","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/listenzz%2Fandroid-modularization","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/listenzz%2Fandroid-modularization/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/listenzz%2Fandroid-modularization/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/listenzz%2Fandroid-modularization/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/listenzz","download_url":"https://codeload.github.com/listenzz/android-modularization/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250128347,"owners_count":21379493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","component","modularization"],"created_at":"2024-11-09T20:19:30.947Z","updated_at":"2025-04-21T20:32:34.832Z","avatar_url":"https://github.com/listenzz.png","language":"Java","readme":"# 依赖注入实现组件化\nAn android modularization demo with dagger2\n\n本文演示如何使用依赖注入的方式实现组件化，本文假设你对什么是组件化已有一定认识，并且使用过 dagger2。\n\n本文所说的组件均是指业务组件，包括有 UI 的业务组件和 UI 无关的业务组件，业务所依赖的基础类库均是指类库，包括第三方的和公司内部的。\n\n为了方便演示，本文所有的组件都放在同一个项目工程中，在同一个 git 仓库中，实际的场景可能是项目独有的组件放在同一个工程目录中，以及在同一个 git 仓库中，可以被多个项目共享的业务模块放在单独的工程目录以及 git 仓库中。\n\n本文配套的示范项目:[android-modularization](https://github.com/listenzz/android-modularization)\n\n## 组件划分\n\n![](./screenshot/app-structure.jpg)\n\n组件化的一个前提是划分组件，如图，在我们 demo 中，有一个 app 主工程，另有五个模块工程，它们之间的依赖关系如下：\n\n![](./screenshot/component-dependency.png)\n\napp 主工程负责组装组件，它需要知道每一个组件\n\ncommon-ui 是基础的 UI 组件，它不依赖其它任何组件\n\nbusiness-a-ui 和 business-b-ui 是 UI 模块，它们都依赖于 common-ui，但彼此之间互不依赖\n\ncommon-api 是抽象的 UI 无关的业务组件，它不依赖其它任何组件\n\nbusiness-c 是具体的 UI 无关的业务组件，它依赖 common-api\n\nbisiness-a-ui 组件依赖 common-api，也就是说，UI 组件可以依赖纯业务组件\n\n所有的组件都不依赖主工程\n\n依赖是指我们在模块工程的 build.gradle 文件中有这样的代码\n\n```groovy\nimplementation project(':common-ui')\n```\n\n## UI 组件\n\nUI 组件是指有 UI 的业务组件\n\n不管做什么样的 UI 界面，都离不开 Activity 或者 Fragment，想要从一个界面跳到另外一个界面，也只需要知道另外一个界面所属的 Activity 或者 Fragment 即可。\n\n通常，我们不会直接使用 Android Framework 为我们提供的 Activity、Fragment，而是搞一个基类，比如 BaseActivity，BaseFragment。\n\n本文采用单 Activity 架构方式为大家演示如何使用依赖注入实现组件化。单 Activity 架构不是组件化必须的，这纯粹是出于个人偏好。单 Activity 机构是指整个项目中基本只有一个 Activity，其余界面全是 Fragment。\n\n本文使用的单 Activity 架构类库是 [AndroidNavigation](https://github.com/listenzz/AndroidNavigation)，它很好地解决了 fragment 嵌套，跳转等 fragment 相关问题，同时解决了状态栏相关问题。\n\n先来看看我们都有哪些 UI 组件\n\n![component-ui](./screenshot/component-ui.png)\n\n我们在 common-ui 中定义了两个基类: BaseActivity 和 BaseFragment\n\n```java\n// common-ui/BaseActivity.java\n// AwesomeActivity 是 AndroidNavigation 中的类\n// HasSupportFragmentInjector 接口是 dagger2 的，用于依赖注入\npublic abstract class BaseActivity extends AwesomeActivity implements HasSupportFragmentInjector {\n    @Inject\n    DispatchingAndroidInjector\u003cFragment\u003e supportFragmentInjector;\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        // activity 注入需要这一行\n        AndroidInjection.inject(this);\n        super.onCreate(savedInstanceState);\n    }\n    \n    @Override\n    public AndroidInjector\u003cFragment\u003e supportFragmentInjector() {\n        return supportFragmentInjector;\n    }\n}\n```\n\n```java\n// common-ui/BaseFragment.java\n// AwesomeFragment 是 AndroidNavigation 中的类\npublic abstract class BaseFragment extends AwesomeFragment {\n    @Override\n    public void onAttach(Context context) {\n        // fragment 注入需要这一行\n        AndroidSupportInjection.inject(this);\n        super.onAttach(context);\n    }\n}\n```\n\n同时还定义了一个接口，用于创建跨组件跳转的 Fragment。\n\n```java\n// common-ui/UIComponentFactory.java\npublic interface UIComponentFactory {\n    BaseFragment createFragment(String moduleName);\n}\n```\n\n\u003e 因为我们是单 Activity 架构，同时为了简单演示，所以这里只演示 Fragment 跳转。实际工程中，即使是单 Activity 架构，除了 MainActivity，也会有少量的 Activity，比如 WebViewActivity，它可能运行在另外一个进程以避免某些问题，像这种情况这里就不作演示了。\n\n**上面我们说到，business-a-ui 和 business-b-ui 彼此之间互不依赖，但如果 business-a-ui 中的页面想要跳到 business-b-ui 中的页面，怎么办呢？**让我们从 UI 依赖的角度来看\n\n![ui-dependency](./screenshot/ui-dependency.jpg)\n\n图中，实心箭头表示依赖，空心箭头表示实现。\n\n我们的 app 主工程，business-a-ui，business-b-ui 都依赖 common-ui\n\napp 主工程实现了定义在 common-ui 中的 UIComponentFactory 这个接口。\n\n具体流程如下：\n\n明确 app 主工程依赖\n\n```groovy\n// app/build.gradle\ndependencies {\n    implementation project(':common-ui')\n    implementation project(':business-a-ui')\n    implementation project(':business-b-ui')\n}\n```\n\napp 主工程定义一个类来注册每个 UI 组件需要向外暴露的模块\n\n```java\n// app/UIComponentRegistry.java\n@Singleton\npublic class UIComponentRegistry {\n    @Inject\n    public UIComponentRegistry() {\n        Log.w(\"Dagger\", \"UIComponentRegistry\");\n    }\n\n    private HashMap\u003cString, Class\u003c? extends BaseFragment\u003e\u003e uiModules = new HashMap\u003c\u003e();\n\n    public void registerModule(String moduleName, Class\u003c? extends BaseFragment\u003e clazz) {\n        uiModules.put(moduleName, clazz);\n    }\n\n    public Class\u003c? extends BaseFragment\u003e moduleClassForName(String moduleName) {\n        return uiModules.get(moduleName);\n    }\n}\n```\n\n在应用启动时，注册模块\n\n```java\n// app/MainApplication.java\nimport me.listenzz.businessa.AFragment;\nimport me.listenzz.businessb.EFragment;\nimport me.listenzz.businessb.FFragment;\n\npublic class MainApplication extends Application {\n    @Inject\n    UIComponentRegistry uiComponentRegistry;\n    \n    @Override\n    public void onCreate() {\n        super.onCreate();\n        \n        // business-a-ui 组件一共有 AFragment,BFragment,CFragment 三个 fragment\n        // 在这里，仅注册一个 fragment 作为入口\n        uiComponentRegistry.registerModule(\"A\", AFragment.class);\n        \n        // business-b-ui 有两个入口\n        uiComponentRegistry.registerModule(\"E\", EFragment.class);\n        uiComponentRegistry.registerModule(\"F\", FFragment.class);\n    }\n}\n```\n\napp 主工程实现定义在 common-ui 中的 UIComponentFactory 这个接口\n\n```java\n// app/UIComponentFactoryImpl.java\npublic class UIComponentFactoryImpl implements UIComponentFactory {\n    private UIComponentRegistry uiComponentRegistry;\n\n    @Inject\n    public UIComponentFactoryImpl(UIComponentRegistry uiComponentRegistry) {\n        this.uiComponentRegistry = uiComponentRegistry;\n    }\n\n    @Override\n    public BaseFragment createFragment(String moduleName) {\n        Class\u003c? extends BaseFragment\u003e fragmentClass = uiComponentRegistry.moduleClassForName(moduleName);\n        if (fragmentClass == null) {\n            // DEBUG 环境下崩溃，Release 环境下可返回 404 页面\n            throw new IllegalArgumentException(\"未能找到名为 \" + moduleName + \" 的模块，你是否忘了注册？\");\n        }\n        BaseFragment fragment = null;\n        try {\n            fragment = fragmentClass.newInstance();\n        } catch (Exception e) {\n            // ignore\n        }\n        return fragment;\n    }\n}\n```\n\n在 dagger 模块中声明实现和接口的关系\n\n```java\n// app/AppModule.java\n@Module\npublic abstract class AppModule {\n    @Binds\n    @Singleton\n    abstract UIComponentFactory uiComponentFactory(UIComponentFactoryImpl uiComponentFactory);\n}\n```\n\n如果 AFragment(business-a-ui) 想要跳到 EFragment(business-b-ui)\n\n```java\n// business-a-ui/AFragment.java\npublic class AFragment extends BaseFragment {\n    @Inject\n    UIComponentFactory uiComponentFactory;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n        View root = inflater.inflate(R.layout.a_fragment_a, container, false);\n        root.findViewById(R.id.to_b_e).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // 模块间跳转，需要通过工厂方法来获取目标页面\n                BaseFragment fragment = uiComponentFactory.createFragment(\"E\");\n                getNavigationFragment().pushFragment(fragment);\n            }\n        });\n        return root;\n    }\n}\n\n```\n\n在这个过程中，business-a-ui 不知道 business-b-ui，也根本无法知道 \"E\" 对应的是哪个类，是如何实现的，是原生界面？是 RN 界面？总之，除了知道对方遵从 BaseFragment 外，一无所知。\n\n此外 business-a-ui 也对 UIComponentFactory 是如何实现的一无所知。app 主工程实现了 UIComponentFactory，但 business-a-ui 并不依赖主工程。\n\n### 小结\n\nUI 组件无需对外提供业务接口，它们只需要注册入口模块即可\n\nUI 组件的基类是特殊的接口，它有很多实现，我们通过工厂方法返回合适的实现\n\n## 业务组件\n\n业务组件是指 UI 无关的业务组件。\n\n和 UI 组件不同的是，系统并没有为我们的业务提供基类。因为我们的业务是唯一的独特的，我们需要自定义接口\n\n如果需要依赖业务组件，那么依赖接口，而不是实现\n\n当我们获取一个 UI 模块时，我们得到的是基类的引用\n\n当我们获取一个业务模块时，我们得到的是一个接口的引用\n\n**实际面向的都是抽象**\n\n\n![business-dependency](./screenshot/business-dependency.jpg)\n\n业务组件不像 UI 组件那样需要注册，但它们需要定义接口\n\n我们在 common-api 中，定义了一个业务接口，以及相关的一个 PO\n\n```java\n// common-api/Account.java\n// PO\npublic class Account {\n    public Account(String username, String type) {\n        this.username = username;\n        this.type = type;\n    }\n\n    public String username;\n    public String type;\n}\n```\n\n```java\n// common-api/AccountManager.java\npublic interface AccountManager {\n    Account login(String username, String password);\n    void invalidate();\n}\n```\n\nbusiness-c 依赖 common-api 并实现了 AccountManager\n\n```java\n// business-c/AccountManagerImpl.java\npublic class AccountManagerImpl implements AccountManager {\n    @Inject\n    public AccountManagerImpl() {\n    }\n\n    @Override\n    public Account login(String username, String password) {\n        return new Account(username, \"password\");\n    }\n\n    @Override\n    public void invalidate() {\n        //...\n    }\n}\n\n```\n\nbusiness-a-ui 依赖 common-api 并且想要使用 AccountManager, 可 AccountManager 只是个接口，怎么办呢？\n\n还是需要主工程来组装\n\nbusiness-c 先定义一个 dagger 模块，把实现和接口绑在一起\n\n```java\n// business-c/CModule.java\n@Module\npublic abstract class CModule {\n    @Binds\n    @Singleton\n    abstract AccountManager provideAccountManager(AccountManagerImpl accountManager);\n}\n```\n\napp 主工程把该模块加入到依赖图中 \n\n```java\n// app/AppComponent.java\n@Singleton\n@Component(modules = {\n        AndroidSupportInjectionModule.class,\n        AppModule.class,\n        CModule.class, // 来自 business-c\n})\npublic interface AppComponent extends AndroidInjector\u003cMainApplication\u003e {\n    @Component.Builder\n    abstract class Builder extends AndroidInjector.Builder\u003cMainApplication\u003e {\n    }\n}\n```\n\nbusiness-a-ui 在代码中声明依赖\n\n```java\n// business-a-ui/AFragment.java\npublic class AFragment extends BaseFragment {\n    @Inject\n    AccountManager accountManager;\n    \n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        setTitle(\"A 模块 A 页面\");\n        Account account =  accountManager.login(\"listenzz\", \"123456\");\n        textView.setText(\"用户名：\" +account.username + \"\\n\" + \"登录方式：\" + account.type);\n    }\n}\n```\n\n就这样，business-a-ui 用上了 AccountManager，但它对 business-c 一无所知。我们随时可以用 business-d 来取代 business-c，而对 business-a-ui 毫无影响。\n\n## 业务组件如何调起 UI 组件\n\n业务组件是指 UI 无关的业务组件\n\nUI组件是指有 UI 的业务组件\n\n有些时候，有些业务组件是没有 UI 的，它们可能运行在后台，当某些事件发生时，可能需要调起 UI 界面以通知用户。有两种方式来处理这种业务组件需要调起 UI 组件的情况。一种是采用订阅／发布机制，具体的实现有 EventBus，LocalBroadcast 等等，另一种是使用代理(delegate)。\n\nDelegate pattern, 就是遇着这事，我不知道怎么办，于是我找了个代理，将此事委派与他。\n\n来看 PPT\n\n![business-call-ui](./screenshot/business-call-ui.jpg)\n\n下面我们就来演示如何使用代理来实现业务组件调起 UI 组件\n\n**假设 business-c 这个 UI 无关的组件在登录已经过期无效的情况下，需要通知 UI 层，如何是好呢？**\n\n首先在 common-api 中定义一个接口\n\n```java\n// common-api/AccountManagerDelegate.java\npublic interface AccountManagerDelegate {\n    void onInvalidation();\n}\n```\n\n我们来看 common-api 和 business-c 中都有哪些类\n\n\n![component-nonui](./screenshot/component-nonui.png)\n\n\n当 AccountManagerDelegate 和 AccountManager 放在一起时，已经表明了设计意图，有经验的工程师会知道在实现 AccountManager 的过程中，需要依赖 AccountManagerDelegate。\n\nbusiness-c 在实现 AccountManager 时声明依赖 AccountManagerDelegate，并调用其中的方法\n\n```java\n// business-c/AccountManagerImpl.java\npublic class AccountManagerImpl implements AccountManager {\n    private AccountManagerDelegate delegate;\n\n    @Inject\n    public AccountManagerImpl(AccountManagerDelegate delegate) {\n        this.delegate = delegate;\n    }\n\n    @Override\n    public void invalidate() {\n        this.delegate.onInvalidation();\n    }\n}\n```\n\napp 主工程实现这一代理接口，跳到登录界面。\n\n```java\n// app/AccountManagerDelegateImpl.java\n@Singleton\npublic class AccountManagerDelegateImpl implements AccountManagerDelegate {\n    private MainApplication application;\n\n    @Inject\n    public AccountManagerDelegateImpl(MainApplication application) {\n        this.application = application;\n    }\n\n    @Override\n    public void onInvalidation() {\n        Log.w(\"Dagger\", \"onInvalidation\");\n        if (application.mainActivity != null) {\n            NavigationFragment navigationFragment = new NavigationFragment();\n            navigationFragment.setRootFragment(new LoginFragment());\n            application.mainActivity.presentFragment(navigationFragment);\n        } else {\n            // do something\n        }\n    }\n}\n```\n\n\u003e 实际开发中，登录界面不是由 app 主工程亲自实现的，而是由其它 UI 组件实现，主工程在实现这一代理的过程中依赖其它 UI 组件即可。\n\napp 主工程将这一实现和接口绑定\n\n```java\n// app/AppModule.java\n@Module\npublic abstract class AppModule {\n    @Binds\n    @Singleton\n    abstract AccountManagerDelegate accountManagerDelegate(AccountManagerDelegateImpl delegate);\n}\n```\n\n就这样，当 AccountManager 的 invalidate 方法被调用时，就会唤起一个 UI 界面，但是在这个过程中业务模块却没有依赖 UI 模块。\n\n## Dagger 帮助\n\n如果某个组件声明了依赖而又没有组装对应的实现，那么是编译不过去的，所以不用担心只有接口没有实现的情况。\n\n如果在使用 dagger 的过程中编译不过去，可以把 MainApplication 中创建 AppComponent 的代码先注释掉，然后再次编译，你会得到正确的提示\n\n```java\npublic class MainApplication extends Application implements HasActivityInjector, HasSupportFragmentInjector {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // AppComponent.Builder builder = DaggerAppComponent.builder();\n        // builder.seedInstance(this);\n        // builder.build().inject(this);\n    }\n}\n```\n\n## 总结\n\n主工程负责组装其它组件工程\n\nUI 组件互不依赖，想要跳转，通过工厂方法和模块名创建目标页面实例\n\n业务组件分离接口和实现\n\nUI 组件可以依赖业务组件，但反过来不行\n\nUI 组件不能直接依赖业务组件的实现，而应依赖其接口\n\nUI 组件内部可以有自己独立的业务类和 PO，但不对外公开\n\n业务组件如果需要调起 UI，可以通过事件或代理的方式\n\n组件内部可以有自己的分层结构，比如某 UI 组件使用 MVVM 模式\n\n[示例项目源码](https://github.com/listenzz/android-modularization)\n\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flistenzz%2Fandroid-modularization","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flistenzz%2Fandroid-modularization","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flistenzz%2Fandroid-modularization/lists"}