Android组件化原理

Posted 西邮王嘉尔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android组件化原理相关的知识,希望对你有一定的参考价值。

什么是组件化?

一个大型APP版本一定会不断的迭代,APP里的功能也会随之增加,项目的业务也会变的越来越复杂,这样导致项目代码也变的越来越多,开发效率也会随之下降。并且单一工程下代码耦合严重,每修改一处代码后都要重新编译,非常耗时,单独修改的一个模块无法单独测试。

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

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

为什么使用组件化?

编译速度: 可以但需测试单一模块,极大提高了开发速度
超级解耦: 极度降低了模块间的耦合,便于后期的维护和更新
功能重用: 某一块的功能在另外的组件化项目中使用只需要单独依赖这一模块即可
便于团队开发: 组件化架构是团队开发必然会选择的一种开发方式,它能有效的使团队更好的协作

一步步搭建组件化

这里以演示为例,只设置登录这一个功能组件

组件化开发要注意的几点问题

  • 要注意包名和资源文件命名冲突问题
  • Gradle中的版本号的统一管理
  • 组件在AppIicationLibrary之间如何做到随意切换
  • AndroidManifest. xml文件的区分
  • Library不能在Gradle文件中有applicationId

这里以演示为例,只设置登录个人中心这两个功能组件

1.新建模块


并且在module里新建一个activity

到这里我们看到login和我们的app都在有一个绿点证明创建成功

个人中心member模块创建同理,并且每个模块目前都可以独立运行。

2.统一Gradle版本号

每一个模块都是一个application,所以每个模块都会有一个build.gradle,各个模块里面的配置不同,我们需要重新统一Gradle
在主模块创建config.gradle

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"


在各模块中去引用这些版本号
引用格式如下,两种写法均可

compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android.buildToolsVersion

引用前

引用后

并且使用同样的方法,我们还可以统一我们的依赖库在config.gradle里去添加我们要依赖的库,并在各个模块中去添加依赖

 implementation  rootProject.ext.dependencies.publicImplementation

也可以采用第二种写法

dependencies = [

            "appcompat"             : 'androidx.appcompat:appcompat:1.2.0',

            "material"               : 'com.google.android.material:material:1.2.1',
            "constraintLayout"       : 'androidx.constraintlayout:constraintlayout:2.0.4',//约束性布局

            //test
            "junit"                  : "junit:junit:4.13.1",
            "testExtJunit"           : 'androidx.test.ext:junit:1.1.2',//测试依赖,新建项目时会默认添加,一般不建议添加
            "espressoCore"           : 'androidx.test.espresso:espresso-core:3.3.0',//测试依赖,新建项目时会默认添加,一般不建议添加

    ]

添加依赖

dependencies 

    implementation rootProject.ext.dependencies.appcompat
    implementation  rootProject.ext.dependencies["constraintLayout"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["testExtJunit"]
    androidTestImplementation rootProject.ext.dependencies["espressoCore"]


3.创建基础库

和新建module一样,这里需要新建一个library我们把它命名为Baselibs

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

我们一样可以把它写进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)
    

4.组件模式和集成模式转换

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

在各个模块的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"
        

当我们将is_module改为false时,再次运行编译器我们的模块都不能单独运行了

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

// 每加入一个新的模块,就需要在下面对应的添加一行
    if (is_Module.toBoolean())]) 
        implementation project(path:':login')
        implementation project(path:':member')
    

5.AndroidManifest的切换

为了单独开发加载不同的AndroidManifest这里需要重新区分下。
在组件模块里的main文件里新建manifest文件夹

并且重写一个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'
            
        
    

6.*业务Application切换

每个模块在运行时都会有自己的application,而在组件化开发过程中,我们的主模块只能有一个application,但在单独运行时又需要自己的application这里就需要配置一下。
在业务模块添加新文件夹命名module

在里面建一个application文件

并且我们在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();
    

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

组件之间的跳转

这里采用阿里巴巴的开源库ARouter来实现跳转功能,我会在以后的文章单独拿出一篇来一步步去解读Arouter源码,让我们自己去搭建一个自己的路由

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

由 github 上 ARouter 的介绍可以知道,它可以实现组件间的路由功能。路由是指从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。这里可以体现出路由跳转的特点,非常适合组件化解耦。

要使用 ARouter 进行界面跳转,需要我们的组件对 Arouter 添加依赖,因为所有的组件都依赖了 Baselibs模块,所以我们在 Baselibs 模块中添加 ARouter 的依赖即可。其它组件共同依赖的库也最好都放到 Baselibs中统一依赖。

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

1.添加依赖

Baselibs里的build.gradle添加依赖

dependencies 
    api 'com.alibaba:arouter-api:1.3.1'
    // arouter-compiler 的注解依赖需要所有使用 ARouter 的 module 都添加依赖
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'


// 所有使用到 ARouter 的组件和模块的 build.gradle
android 
    defaultConfig 
        ...
        javaCompileOptions 
            annotationProcessorOptions 
                arguments = [ moduleName : project.getName() ]
            
        
    

dependencies 
    ...
    implementation project (':base')
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'

主模块需要对跳转模块进行依赖

// 主项目的 build.gradle 需要添加对 login 组件和 share 组件的依赖
dependencies 
    // ... 其他
    implementation project(':login')
    implementation project(':share')

2.初始化ARouter

添加了对 ARouter 的依赖后,还需要在项目的 Application 中将 ARouter 初始化,我们这里将 ARouter 的初始化工作放到主模块Application 的 onCreate()方法中,在应用启动的同时将 ARouter 初始化。

public class MainApplication extends Application 
    @Override
    public void onCreate() 
        super.onCreate();

        // 初始化 ARouter
        if (isDebug())            
            // 这两行必须写在init之前,否则这些配置在init过程中将无效
            
            // 打印日志
            ARouter.openLog();     
            // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
            ARouter.openDebug();   
        
        
        // 初始化 ARouter
        ARouter.init(this);

    

    private boolean isDebug() 
        return BuildConfig.DEBUG;
    


3.添加跳转

这里我们在首页添加登录分享两个跳转页面。

login.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        ARouter.getInstance().build("/login/login").navigation();
    
);
share.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        ARouter.getInstance().build("/share/share").navigation();
    
);

然后,需要在登录和分享组件中分别添加 LoginActivityShareActivity ,然后分别为两个 Activity 添加注解 Route,其中path 是跳转的路径,这里的路径需要注意的是至少需要有两级,/xx/xx

@Route(path = "/login/login")
public class Login extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

    

@Route(path = "/share/share")
public class Share extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share);
  

这样就可以实现跳转了。

组件之间的数据传递

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

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

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

下面我们就按照这个方法来解决组件间数据传递与方法的相互调用这个问题,这里我们通过分享组件 中调用 登录组件 中的方法来获取登录状态是否登录这个场景来演示。

1.定义接口

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

LoginService

public interface LoginService 

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

    /**
     * 获取登录用户的 Password
     * @return
     */
    String getPassword();

EmptyService

public class EmptyService implements LoginService 
    @Override
    public boolean isLogin() 
        return false;
    

    @Override
    public String getPassword() 
        return null;
    

ServiceFactory

public class ServiceFactory 
    private LoginService loginService;
    private ServiceFactory()
 /**
     * 禁止外部创建 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;
        
    

2.实现接口

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;

实现一下登录操作

login = (Button)findViewById(R.id.login);
        login.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))以上是关于Android组件化原理的主要内容,如果未能解决你的问题,请参考以下文章

Android 组件化最佳实践 ARetrofit 原理

Android JetpackViewModel 组件原理剖析

Android JetpackLiveData 组件原理剖析

Android Jetpack Lifecycle 组件原理剖析

Android 组件化架构设计从原理到实战

Android 组件化架构设计从原理到实战!