Android 组件化○

这篇具有很好参考价值的文章主要介绍了Android 组件化○。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.组件化

组件化是指解耦复杂系统时,将多个功能模板拆分、重组的过程。在Android工程表现上就是把app按照其业务的不同,划分为不同的Module。

组件化架构的目的就是让每个业务模块变得相对独立,各个组件在组件模式下可以独立开发调试,集成模式下又可以集成到“app壳工程”中,从而得到一个具有完整功能的APP。

以美团外卖app为例:

Android 组件化○

组件化结构中,每一个组件都可以是一个APP,可以单独修改调试,而不影响总项目。

①app壳:负责管理各个业务组件和打包APK,没有具体的业务功能;

②业务组件层:最上层的业务,每个组件表示一条完整的业务线,彼此之间相互独立;

③功能/基础组件层:支撑上层业务组件运行的基础业务服务;

④基础库:包含了各种开源库以及和业务无关的一个自研工具库。

在组件化的层次中,下层的组件可以被上层的一个或多个模块调用,比如上图中的登录分享组件可以被美食组件调用,也可以被外卖组件调用。

注意模块化、组件化、插件化的区别:模块化粒度要高一些,以业务划分;组件化粒度更低,更注重基础功能的重用;插件化注重的是运行时动态的加载。

 文章来源地址https://www.toymoban.com/news/detail-419609.html

组件化的优势:

①各个组件专注自身功能的实现,模块中代码高度聚合,只负责一项任务,也就是常说的单一职责原则;

②各组件高度解耦,各业务研发可以互不干扰、提升协作效率;

③业务组件可进行拔插,灵活多变。即实现了功能重用, 某一块的功能在另外的组件化项目中使用,只需要单独依赖这一模块即可;

④业务组件之间将不再直接引用和依赖,各个业务模块组件更加独立,降低耦合;

⑤加快编译速度,提高开发效率

 

2.搭建组件化框架

以登录和个人中心两个功能组件为例:

①新建module

新建登录login模块,并且在登录模块里新建一个activity。login和app都有一个绿点证明创建成功。

Android 组件化○

同理,创建个人中心member模块。

每个模块目前都可以独立运行。

Android 组件化○

②统一Gradle版本号

每个模块都是一个application,所以每个模块都会有一个build.gradle,各个模块里面的配置不同,因此需要重新统一Gradle。

在主模块创建config.gradle:

Android 组件化○

在config.gradle里添加一些版本号:

ext {

    android = [

        compileSdkVersion :30,

        buildToolsVersion: "30.0.2",

        applicationId :"activitytest.com.example.moduletest",

        minSdkVersion: 29,

        targetSdkVersion :30,

        versionCode :1,

        versionName :"1.0",

    ]

    androidxDeps = [

        "appcompat": 'androidx.appcompat:appcompat:1.1.0',

        "material": 'com.google.android.material:material:1.1.0',

        "constaraintlayout": 'androidx.constraintlayout:constraintlayout:1.1.3',

    ]

    commonDeps = [

        "arouter_api" : 'com.alibaba:arouter-api:1.5.1',

        "glide" : 'com.github.bumptech.glide:glide:4.11.0'

    ]

    annotationDeps = [

        "arouter_compiler" : 'com.alibaba:arouter-compiler:1.5.1'

    ]

    retrofitDeps = [

        "retrofit" : 'com.squareup.retrofit2:retrofit:2.9.0',

       "converter" : 'com.squareup.retrofit2:converter-gson:2.9.0',

        "rxjava" : 'io.reactivex.rxjava2:rxjava:2.2.20',

        "rxandroid" : 'io.reactivex.rxjava2:rxandroid:2.1.1',

        "adapter" : 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'

    ]

    androidxLibs = androidxDeps.values()

    commonLibs = commonDeps.values()

    annotationLibs = annotationDeps.values()

    retrofitLibs = retrofitDeps.values()

}

然后在主模块的build.gradle(最外层)里添加:

apply from: "config.gradle"

Android 组件化○

然后在app和各模块中引用这些版本号。

引用格式如下:

compileSdkVersion rootProject.ext.android["compileSdkVersion"]

buildToolsVersion rootProject.ext.android.buildToolsVersion

引用前:

Android 组件化○

 引用后:

Android 组件化○

使用同样的方法还可以统一依赖库,方法是在config.gradle里添加要依赖的库,然后在各个模块中添加依赖:

implementation rootProject.ext.dependencies.publicImplementation

③创建基础库

和新建module一样,这里需要新建一个library,命名为Baselibs:

Android 组件化○

同样需要统一版本号,由于这是一个library模块,所以它不需要applicationId:

Android 组件化○

同样可以把它写进config.gradle里:

other:[path:':Baselibs']

然后在每个模块去调用:

implementation project(rootProject.ext.dependencies.other)

注意:当本地库为单独所用,就可以不写入config.gradle,直接调用即可:

implementation project(':Baselibs')

但有时因为gradle版本问题,可能无法依赖到这些公共库,因为在config.gradle里是以数组形式定义的,这时可以用for-each循环的方法将其依次导入config.gradle里:

dependencies = [

          ......

        other:[':Baselibs']

    ]

其他模块的build.gradle:

dependencies {

......

    rootProject.ext.dependencies.other.each{

        implementation project(it)

    }

}

④组件模式和集成模式转换

在主模块gradle.properties里添加布尔类型选项:

Android 组件化○

在各个模块的build.gradle里添加更改语句:

if(is_Module.toBoolean()){

    apply plugin: 'com.android.application'

}else{

    apply plugin: 'com.android.library'

}

每个模块的applicationId也需要处理:

if(is_Module.toBoolean()){

    applicationId "activitytest.com.example.login"

}

Android 组件化○

将is_Module设为false时,再次运行编译器,模块都不能单独运行了:

Android 组件化○

在app模块中添加判断依赖就可以在集成模式下将各模块添加到app主模块中:

// 每加入一个新的模块,就需要在下面对应的添加一行

    if (is_Module.toBoolean())]) {

        implementation project(path:':login')

        implementation project(path:':member')

    }

⑤AndroidManifest的切换

为了单独开发加载不同的AndroidManifest这里需要重新区分下。

在组件模块的main文件里新建manifest文件夹:

Android 组件化○

并且重写一个AndroidManifest.xml文件。集成模式下业务组件的表单是绝对不能拥有自己的Application和launch的Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有。在这个表单中只声明应用的主题,而且这个主题还是跟app壳工程中的主题是一致的:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.example.login">

    <application

      android:theme="@style/Theme.MoudleTest">

        <activity android:name=".LoginActivity">

        </activity>

    </application>

</manifest>

并且还要使其在不同的模式下加载不同的AndroidManifest,只需在各模块的build.gradle里添加更改语句:

sourceSets {

    main {

        if (is_Module.toBoolean()) {

            manifest.srcFile 'src/main/AndroidManifest.xml'

        } else {

            manifest.srcFile 'src/main/mainfest/AndroidManifest.xml'

        }

    }

}

⑥业务Application切换

每个模块在运行时都会有自己的application,而在组件化开发过程中,主模块只能有一个application,但在单独运行时又需要自己的application,所以需要配置一下。

在业务模块添加新文件夹命名module:

Android 组件化○

 在里面新建一个application文件:

Android 组件化○

然后在build.gradle文件里配置module文件夹,使其在单独运行时能够运行单独的application。

在配置manifest的语句中添加java.srcDir 'src/main/module':

sourceSets {

    main {

        if (is_Module.toBoolean()) {

            manifest.srcFile 'src/main/AndroidManifest.xml'

            java.srcDir 'src/main/module'

        } else {

            manifest.srcFile 'src/main/manifest/AndroidManifest.xml'

        }

    }

}

同时在basic基础层内新建application,用于加载一些数据的初始化:

public class BaseApplication extends Application {

    @Override

    public void onCreate() {

        super.onCreate();

        Log.e("fff","baseapplication");

    }

}

在业务模块module里重写该模块的application:

public class LoginApplication extends BaseApplication {

    @Override

    public void onCreate() {

        super.onCreate();

    }

}

至此,组件化框架搭建结束。

 

2.ARouter实现组件之间的跳转

组件化有一个限制,就是禁止横向依赖,也就是说在同一层级下的多个模块是禁止相互依赖的。这样做是为了解耦,防止同一层级的模块高度耦合,同时防止两个模块循环依赖导致编译错误。

由于禁止横向依赖,导致组件化架构面临一个问题:同一层级中模块的通信和跳转不能使用常规的intent显示跳转了。这时候能解决问题的方式有使用intent隐式调用(每个activity都需要注册并添加intent-filter)、EventBus(消息多,代码乱)、广播(重)、Binder(重)等。最常用的就是ARouter框架。

还是以美团app为例,美团app就是以组件化方式构建的,首页的“美食”和“外卖”可以看作是业务组件层的两个不同模块。在app首页可以点击“美食”或“外卖”分别进入到不同页面的首页,而且在“美食”首页也能直接跳转到“外卖”首页。美团的跳转使用的就是ARouter。

ARouter是一个用于帮助Android App进行组件化改造的框架 ,支持模块间的路由、通信、解耦。它可以实现组件间的路由功能。路由是指从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。这里可以体现出路由跳转的特点,非常适合组件化解耦。

ARouter实现跳转的原理是在ARouter内部维护了一个路由表,路由表里的数据来自于各个模块的页面信息,每个模块负责向路由框架中注册自己的信息,这样需要页面跳转的时候只需要输入要跳转的页面信息,路由框架就会从路由表中去寻址找到该页面,然后就可以使用startActivity来跳转了。

要使用ARouter需要添加Arouter依赖,由于所有的组件都依赖了Baselibs模块,所以只在Baselibs模块中添加ARouter依赖即可,其它组件共同依赖的库也最好都放到Baselibs中统一依赖。

注意,arouter-compiler依赖需要所有使用到ARouter的模块和组件都单独添加,不然无法在apt中生成索引文件,也就无法跳转成功。并且在每一个使用到ARouter的模块和组件的build.gradle文件中,其android{}中的javaCompileOptions中也需要添加特定配置。

 

3.组件之间的数据传递

由于主项目与组件、组件与组件之间是不可以直接使用类的相互引用来进行数据传递的,那么在开发过程中如果有组件间的数据传递时,需要采用 [接口 + 实现] 的方式来解决。

在Baselibs基础库里定义组件可以对外提供访问自身数据的抽象方法的Service,并且提供一个 ServiceFactory,每个组件中都要提供一个类来实现自己对应的Service中的抽象方法。在组件加载后需要创建一个实现类对象,然后将实现了Service类的对象添加到ServiceFactory中。这样在不同组件交互时就可以通过ServiceFactory获取想要调用的组件的接口实现,然后调用其中的特定方法就可以实现组件间的数据传递与方法调用。

当然,ServiceFactory中也会提供所有的Service的空实现,在组件单独调试或部分集成调试时避免出现由于实现类对象为空引起的空指针异常。

下面就按照这个方法来解决组件间数据传递与方法相互调用这个问题,以个人中心组件调用登录组件中的方法来获取登录状态这个场景为例。

①定义接口

在service文件夹中定义接口,LoginService接口中定义了Login组件向外提供的数据传递的接口方法,EmptyService是service中定义的接口的空实现,ServiceFactory用于接收组件中实现的接口对象的注册以及向外提供特定组件的接口实现。

Android 组件化○

LoginService.java:

public interface LoginService {

    boolean isLogin(); //是否已经登录

    String getPassword(); //获取登录用户的密码

}

EmptyService.java:

public class EmptyService implements LoginService {

    @Override

    public boolean isLogin() {

        return false;

    }

    @Override

    public String getPassword() {

        return null;

    }

}

ServiceFactory.java:

public class ServiceFactory {

    private LoginService loginService;

   //private禁止外部创建ServiceFactory对象

    private ServiceFactory() {

    }

    //通过静态内部类方式实现ServiceFactory单例

    public static ServiceFactory getInstance() {

        return Inner.serviceFactory;

    }

    private static class Inner {

        private static ServiceFactory serviceFactory = new ServiceFactory();

    }

    //接收Login组件实现的Service实例

    public void setLoginService(LoginService loginService) {

        this.loginService = loginService;

    }

       //返回Login组件的Service实例

    public LoginService getLoginService(){

        if(loginService == null){

            return new EmptyService();

        }else{

            return loginService;

        }

    }

}

②实现接口

在login模块:

public class AccountService implements LoginService {

    private boolean login;

    private String password;

    public AccountService(boolean login, String password) {

        this.login = login;

        this.password = password;

    }

    @Override

    public boolean isLogin() {

        return login;

    }

    @Override

    public String getPassword() {

        return password;

    }

}

新建一个Util类用来存储登录数据:

public class LoginUtil {

    static boolean isLogin = false;

    static String password = null;

}

实现一下登录操作:

loginBtn.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

        LoginUtil.isLogin = true;

        LoginUtil.password = "admin";

        ServiceFactory.getInstance( ).setLoginService(new AccountService( LoginUtil.isLogin, LoginUtil.password));

    }

});

在login模块的application里定义ServiceFactory类:

public class LoginApplication extends Application {

    @Override

    public void onCreate() {

        super.onCreate();

        ServiceFactory.getInstance( ).setLoginService(new AccountService( LoginUtil.isLogin,LoginUtil.password));

    }

}

在个人中心模块获取登录信息:

if(ServiceFactory.getInstance().getLoginService().isLogin()){

    Toast.makeText(ShareActivity.this,"分享成功!",Toast.LENGTH_SHORT).show();

 }else{

    Toast.makeText(ShareActivity.this,"分享失败,请先登录!", Toast.LENGTH_SHORT).show();

}

一个项目时只能有一个Application,Login作为组件时主模块的Application类会初始化,而Login组件中的Applicaiton不会初始化。确实是存在这个问题的,这里先将Service的注册放到其活动里,接下来解决Login作为组件时Appliaciton不会初始化的问题。

 

5.组件Application的动态切换

现在的问题是:在主模块中有Application的情况下,组件在集中调试时其Applicaiton不会初始化,而组件的Service在ServiceFactory的注册又必须放到组件初始化的地方。

为了解决这个问题可以将组件的Service类强引用到主Module的Application中进行初始化,这就必须要求主模块可以直接访问组件中的类。而我们又不想在开发过程中主模块能访问组件中的类,这里可以通过反射来实现组件Application的初始化。

①定义抽象类BaseApplication继承Application

在Baselibs基础库模块:

public abstract class BaseApplication extends Application {

    public abstract void initModuleApp(Application application); //Application初始化

    public abstract void initModuleData( Application application); //所有Application初始化后的自定义操作

}

②所有组件的Application都继承BaseApplication

以Login模块为例:

public class LoginApplication extends BaseApplication{

    @Override

    public void onCreate() {

        super.onCreate();

        initModuleApp(this);

        initModuleData(this);

    }

    @Override

    public void initModuleApp(Application application) {

        ServiceFactory.getInstance( ).setLoginService(new AccountService( LoginUtil.isLogin,LoginUtil.password));

    }

    @Override

    public void initModuleData(Application application) {

    }

}

③定义AppConfig类

在Baselibs模块定义一个静态的String数组,将需要初始化的组件的Application的完整类名放入到这个数组中。

public class AppConfig {

    private static final String LoginApp = "com.example.login.LoginApplication";

    public static String[] moduleApps = {

            LoginApp

    };

}

④主模块application实现两个初始化方法

// 主Module的Applicaiton

public class MainApplication extends BaseApp {

    @Override

    public void onCreate() {

        super.onCreate();      

        // 初始化组件Application

        initModuleApp(this);       

        // ……其他操作       

        // 所有Application初始化后的操作

        initModuleData(this);       

    }

    @Override

    public void initModuleApp(Application application) {

        for (String moduleApp : AppConfig.moduleApps) {

            try {

                Class clazz = Class.forName( moduleApp);

                BaseApp baseApp = (BaseApp) clazz.newInstance();

                baseApp.initModuleApp(this);

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

   @Override

    public void initModuleData(Application application) {

        for (String moduleApp : AppConfig.moduleApps) {

            try {

                Class clazz = Class.forName( moduleApp);

                BaseApp baseApp = (BaseApp) clazz.newInstance();

                baseApp.initModuleData(this);

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

通过反射完成了组件Application的初始化操作,也实现了组件与化中的解耦需求。

 

6.主模块使用其他组件的Fragment

开发中使用Fragment一般都是直接通过访问具体 Fragment类的方式实现Fragment的实例化,但是现在为了实现模块与组件间的解耦,在移除组件时会由于引用的Fragment不存在而编译失败。

这里介绍两种方法

①ARouter--采用ARouter直接调用

fragment = (Fragment) ARouter.getInstance( ).build("/login/fragment").navigation();

②反射

以Login模块为例,假如在该模块创建一个用户界面,命名为UserFragment。

首先,在 Login组件中创建UserFragment,然后在LoginService接口中添加newUserFragment方法返回一个Fragment,在Login组件中的 AccountService和Baselibs中LoginService的空实现类中实现这个方法,然后在主模块中通过 ServiceFactory获取LoginService的实现类对象,调用其newUserFragment即可获取到UserFragment的实例。

// Baselibs模块的LoginService 

public interface LoginService {

    //其他代码...

    Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag);

}

// Login组件中的AccountService

public class AccountService implements LoginService {

    @Override

    public Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag) {

        FragmentTransaction transaction = manager.beginTransaction();

      //创建UserFragment实例,并添加到Activity中

        Fragment userFragment = new UserFragment();

        transaction.add(containerId, userFragment, tag);

        transaction.commit();

        return userFragment;

    }

}

// 主模块的FragmentActivity

public class FragmentActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_fragment); 

        // 通过组件提供的Service实现Fragment的实例化

        ServiceFactory.getInstance().getAccountSe rvice().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, "");

    }

}

 

 

到了这里,关于Android 组件化○的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android彻底组件化—UI跳转升级改造

    (2)host对应的是share。在组件化框架中,每个组件对应一个唯一的host,例如分享组件的host就是share,读书组件的host是reader等等。 host是路由分发的第一级,根据host可以定位到每个组件。 host还可以对所有的路由URL进行一个分组,只有调用到该分组的路由的时候,组内的路由

    2024年04月16日
    浏览(37)
  • Android AGP8.1.0组件化初探

    前面两篇完成了从AGP4.2到 AGP8.1.0的升级,本文是由于有哥们留言说在AGP8.0中使用ARouter组件化有问题,于是趁休息时间尝试了一下,写了几个demo,发现都没有问题,跳转和传值都是正常的,这里我也是直接从groovy转换成version-catalogs的依赖方式,由于之前升级过,所以这次很顺

    2024年02月10日
    浏览(45)
  • Android学习之路(22) 从模块化到组件化

    Android 应用项目 , 都存在一个应用模块 ( Application Module ) , 在 build.gradle 构建脚本中 , 第一个插件配置 com.android.application , 表明 该 Module 编译打包后的输出是 APK 安装包 ; 该项目可以直接运行 ; 如果在 build.gradle 配置的是 com.android.library 插件 , 那么 编译 Module 打包后输出的是 a

    2024年01月22日
    浏览(49)
  • Android学习之路(23)组件化框架ARouter的使用

    支持直接解析标准URL进行跳转,并自动注入参数到目标页面中 支持多模块工程使用 支持添加多个拦截器,自定义拦截顺序 支持依赖注入,可单独作为依赖注入框架使用 支持InstantRun 支持MultiDex (Google方案) 映射关系按组分类、多级管理,按需初始化 支持用户指定全局降级与局

    2024年01月22日
    浏览(82)
  • 现代化 Android 开发:组件化与模块化的抉择

    作者:古哥E下 项目初始的时候,一般都是使用一个分层架构,接入各种框架,然后就写业务代码。但如果项目慢慢变大,那就会出现很多项目管理的问题,诸如: 1.代码复用与抽象问题 2.编译速度问题 3.版本迭代速度问题 所以组件化、模块化、动态化、插件化、跨平台等各

    2024年02月11日
    浏览(59)
  • Android组件化方案及组件消息总线modular-event实战,渣本Android开发小伙如何一步步成为架构师

    美团外卖团队开发的一款Android路由框架,基于组件化的设计思路。主要提供路由、ServiceLoader两大功能。之前美团技术博客也发表过一篇WMRouter的介绍:《WMRouter:美团外卖Android开源路由框架》。WMRouter提供了实现组件化的两大基础设施框架:路由和组件间接口调用。支持和文

    2024年04月22日
    浏览(51)
  • Android技术栈(二)组件化改造,目前最稳定和高效的UI适配方案

    .build(PR.navi.navi) .navigation(); 而 Activity 则不需要,它会立即显示 ARouter.getInstance() .build(PR.navi.navi) //还可以设置参数,ARouter会帮你存在Bundle中 .withString(“pathId”,UUID.randomUUID().toString()) //Activity 或 Context .navigation(this); navi 模块是典型的业务逻辑模块,这里你可导入一些只有这个模块才

    2024年03月24日
    浏览(45)
  • 一篇读懂 Android 开发中模块化、组件化、插件化和热修复

    网上关于 “Android 开发\\\" 的文章很多,我本人学习 Android 开发的过程也借鉴了网上先辈们的文章;但大多数文章都从底层的细枝末节开始讲述,由下而上给人一种这门技术“博大精深”望而生畏的感觉;而我写这篇文章的初衷就是由上而下,希望别人在阅读的过程中能够觉得

    2023年04月08日
    浏览(40)
  • 大型Android项目架构:基于组件化+模块化+Kotlin+协程+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端

    前言:苟有恒,何必三更眠五更起;最无益,莫过一日曝十日寒。 之前一直想写个 WanAndroid 项目来巩固自己对 Kotlin+Jetpack+协程 等知识的学习,但是一直没有时间。这里重新行动起来,从项目搭建到完成前前后后用了两个月时间,平常时间比较少,基本上都是只能利用零碎的

    2024年02月09日
    浏览(54)
  • Vue组件化开发--公共组件的封装

    目录 为什么要封装组件 应用场景 vue自己封装组件(局部、全局)  Vue组件的三要素 ①全局组件 1)方式:  2)示例: ②局部组件 1)方式: 2)示例: 命名规范:(注意) 脚手架vue-cli中的组件 父传子(props) 通过 $on 传递父组件方法 $parent获取父组件然后使用父组件中的

    2024年02月05日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包