精华内容
下载资源
问答
  • android组件化
    千次阅读
    2022-03-16 21:28:27

    为什么需要组件化?

    组件化顾名思义就是将代码按功能或者业务划分成一个个组件,小的项目一般不需要,只有当项目大到一定程度,代码量足够多的时候我们就需要用到组件化。组件化总体来说有下面几个收益点:

    • 代码隔离:不同的功能收敛到不同的组件里,对一个功能(组件)的修改不会影响到另一个组件,减少开发中误伤别人或者被别人误伤的概率

    • 提高开发效率:隔离开来,每个组件可以单独运行调试,不至于你的一个bug影响我几天

    • 提高协作效率:代码划分清晰,人员自然也可以清晰划分,每个组件有对应的owner,没有三不管地带;每个组件只暴露自己需要对外暴露的方法,其他组件不需理解该组件的具体实现,需要就调用就好

    • 可复用性:对于一些通用的功能抽象成一个组件,便于复用

    怎样组件化?

    将不同功能的代码收敛到各自独立的module,做好代码隔离,A业务不能直接引用到B业务module里的代码,也就限制了B的改动会对A造成的影响。module之间的依赖关系分层级,顶层依赖底层,底层不能依赖顶层,这样可以让顶层的改动影响限制在自己module,其他顶层module因为没有引用到改动的module所以不会受影响。

     

    如图,不同的组件已经划分成不同的module,代码层面已经完全隔离开了,就是说组件A不能直接引用到组件B,这时候,如果A要用到B的能力,要么让整个组件A依赖组件B,implementation project(':B'),但是如果B又要用到A的能力,又依赖A,继续implementation project(':A'),那这两个组件互相依赖,A的改动会影响到B,B的改动也会影响A,跟我们原来的预期背道而驰。所以我们需要一个通信框架,组件只暴露必要的方法供其他组件调用,且组件间不能互相依赖。

    A功能拆成两个组件,A-api表示A组件对外暴露的能力,这里只写接口;A-impl作为A组件能力的实现,代码逻辑写在这里;实现一个base组件,impl层依赖base层,base层依赖api层,base里面通过反射创建和缓存impl的实例,存储一个servicemap,key为接口,value为对应反射创建的impl(service实例),其他的B、C组件同理,这样A,B,C等组件不会互相依赖,但都可以通过依赖base,通过接口方法调用到对应的组件能力。比如A要用到B的能力,可以拿到B-api的接口,到base层取对应的实例,base层通过反射创建B-impl里对接口的实现并缓存起来,返回给A,这样,A就可以调用到B-api里暴露的方法。

    中转组件base和反射创建是去除双向依赖的关键。

    可以看到,只要维护好各个组件间的依赖关系,可以做到,对顶层组件的修改不会影响到其他组件,对底层组件的修改只影响依赖它的组件。

    代码划分清晰,人员协作清晰,这就是组件化带来的效果。

    Q&&A

    组件化后如何独立调试?

    独立调试本质上就是你的组件不再是一个library,而是一个application。

    想要灵活切换可以在gradle.properties配置变量,build.gradle判断变量去执行不同逻辑

    核心改动点:

    • 切换library为application:在build.gradle修改apply plugin: 'com.android.library'为apply plugin: 'com.android.application',可判断变量动态切换

    • 配置入口activity:application是需要入口activity的,需要在AndroidManifest.xml配置好,可判断变量再根据sourceset指定不同的AndroidManifest.xml

    • 配置application信息:因为已经作为一个application了,需要配置android{}块里的applicationId、compileSdkVersion等

    sync后你会发现你的组件已经被Android studio识别为app了,选择后点击运行即可

    更多相关内容
  • 本文主要介绍我们的组件化方案,希望对从事Android组件化开发的同学能有所启发。近年来,为什么这么多团队要进行组件化实践呢?组件化究竟能给我们的工程、代码带来什么好处?我们认为组件化能够带来两个最大的好处...
  • android组件化demo

    2018-12-10 20:47:42
    模块组件和通用组件,可分为业务组件和UI组件等等,比如下拉刷新可作为通用ui组件,供多个项目调用,登录模块或者网络模块,可作为通用业务组件存在,供多个项目使用。 在项目开发中,讲项目拆分为不同的组件,开发...
  • android多人开发中,项目工程日趋复杂,组件化是一个可实践的方式之一.
  • Android组件化实现.pdf

    2019-08-29 02:19:38
    "手机淘宝架构工程师查郁在2018云栖大会·深圳峰会中做了题为《Android组件化实现》的分享,就Atlas的演进、特性以及Atlas工程期、运行期、运维期等方面的内容做了深入的分析。
  • Android组件化原理

    千次阅读 2022-01-07 14:33:49
    Android组件化原理什么是组件化?为什么使用组件化?一步步搭建组件化组件化开发要注意的几点问题1.新建模块2.统一Gradle版本号3.创建基础库4.组件模式和集成模式转换5.AndroidManifest的切换 什么是组件化? 一个...

    什么是组件化?

    一个大型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));
        }
    }
    

    在分享模块获取登录信息

    share = (Button)findViewById(R.id.share);
            share.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    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 不会初始化的问题。

    组件Application的动态切换

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

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

    1.定义抽象类 BaseApplication 继承 Application

    Baselibs基础库模块

    public abstract class BaseApplication extends Application {
        /**
         * Application 初始化
         */
        public abstract void initModuleApp(Application application);
    
        /**
         * 所有 Application 初始化后的自定义操作
         */
        public abstract void initModuleData(Application application);              //其他需要调用的方法
    }
    

    2.所有的组件的 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) {
    
        }
    }
    

    3.定义 AppConfig 类

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

    public class AppConfig {
        private static final String LoginApp = "com.example.login.LoginApplication";
    
        public static String[] moduleApps = {
                LoginApp
        };
    }
    

    4.主模块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 (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException 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 (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

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

    主模块使用其他组件的 Fragment

    我们在开发过程中经常使用 Fragment。一般情况下,我们都是直接通过访问具体 Fragment 类的方式实现 Fragment 的实例化,但是现在为了实现模块与组件间的解耦,在移除组件时不会由于引用的 Fragment 不存在而编译失败,我们就不能模块中直接访问组件的 Fragment 类。
    这里介绍两种方法

    1.ARouter

    这里可以采用ARouter直接调用

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

    2.反射

    我们还是以Login模块为例,假如在该模块创建一个用户界面,命名为UserFragment
    首先,在 Login组件中创建 UserFragment,然后在 LoginService 接口中添加newUserFragment方法返回一个Fragment,在Login组件中的 AccountServiceBaselibsLoginService 的空实现类中实现这个方法,然后在主模块中通过 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 AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_fragment);
            
            // 通过组件提供的 Service 实现 Fragment 的实例化
            ServiceFactory.getInstance().getAccountService().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, "");
        }
    }
    
    展开全文
  • Android组件化大牛视频

    2018-11-07 10:19:54
    组件化视频教程,老司机带你轻松上路
  • Android组件化开发

    千次阅读 2021-02-02 18:24:22
    所谓组件化,就是将整个庞大的项目以业务逻辑进行拆分成多个模块,并且各个模块之间相互独立,相互解耦,每一个模块可以单独进行开发调试,各个模块调试完,以library的形式依赖在壳App中组合成一个完整的项目。...

    一.什么是组件化开发

    所谓组件化,就是将整个庞大的项目以业务逻辑进行拆分成多个模块,并且各个模块之间相互独立,相互解耦,每一个模块可以单独进行开发调试,各个模块调试完,以library的形式依赖在壳App中组合成一个完整的项目。

    二.为什么要采用组件化

    随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。

    单一工程:原先采用的架构

     

    从图中我们可以看出,单一工程的话,所有的业务逻辑都整合再同一个app中,业务逻辑之间也相互依赖,所谓的你中有我,我中有你,相互依赖,无法分离。单一工程也存在以下几个问题:

    1、实际业务变化非常快,但是单一工程的业务模块耦合度太高,牵一发而动全身;

    2、对工程所做的任何修改都必须要编译整个工程;

    3、功能测试和系统测试每次都要进行;

    4、团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,并且在开发过程中,任何一位成员没办法专注于自己的功能点,影响开发效率;

    5、不能灵活的对业务模块进行配置和组装;

    因此,为了满足各个业务模块的迭代而彼此不受影响,更好的解决上面这种让人头疼的依赖关系,我们就需要整改app架构。

    组件化工程:整改后的架构

    几个名词解释:

    集成模式:所有的业务组件被“app壳工程”依赖,组成一个完整的APP;

    组件模式:可以独立开发业务组件,每一个业务组件就是一个APP;

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

    业务组件:根据公司具体业务而独立形成一个的工程;

    功能组件:提供开发APP的某些基础功能,例如打印日志、树状图等;

    BaseModule组件:属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;

    Android APP组件化架构的目标是告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为arr包集成到“app壳工程”中,组成一个完整功能的APP;
           从组件化工程模型中可以看到,业务组件之间是独立的,没有关联的,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整业务功能的APP应用,但是在组件开发模式下,业务组件又变成了一个个application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。

    这是组件化工程模型下的业务关系,业务之间将不再直接引用和依赖,而是通过“路由”这样一个中转站间接产生联系,而Android中的路由实际就是对URL Scheme的封装;

    组件化开发可以带来以下优势:

    1、加快业务迭代速度,各个业务模块组件更加独立,不再出现业务耦合情况;

    2、稳定的公共模块采用依赖库方式,提供给各个业务线使用,减少重复开发和维护工作量;

    3、迭代频繁的业务模块采用组件方式,各业务研发可以互不干扰、提升协作效率,并控制产品质量;

    4、为新业务随时集成提供了基础,所有业务可上可下,灵活多变;

    5、降低团队成员熟悉项目的成本,降低项目的维护难度;

    6、加快编译速度,提高开发效率;

    7、控制代码权限,将代码的权限细分到更小的粒度;

    三、组件化具体实现

    前言:需要先确定业务模块的划分,定义一些全局的变量,来控制模块不同模式的切换。

    前期准备:

    1)组件模式和集成模式的转换

    AndroidStudio中的Module主要有两种属性,分别为:

    1、application属性,可以独立运行的应用程序,就是一个app

    apply plugin: ‘com.android.application’

     

    2.library属性,不可以单独运行,一般是依赖的库文件

    apply plugin: ‘com.android.library’

    Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;

    但是我们如何让组件在这两种模式之间自动转换呢?总不能每次需要转换模式的时候去每个业务组件的 Gralde 文件中去手动把 Application 改成 library 吧?如果我们的项目只有两三个组件那么这个办法肯定是可行的,手动去改一遍也用不了多久,但是在大型项目中我们可能会有十几个业务组件,再去手动改一遍必定费时费力,这时候就需要程序员发挥下懒的本质了。

    试想,我们经常在写代码的时候定义静态常量,那么定义静态常量的目的什么呢?当一个常量需要被好几处代码引用的时候,把这个常量定义为静态常量的好处是当这个常量的值需要改变时我们只需要改变静态常量的值,其他引用了这个静态常量的地方都会被改变,做到了一次改变,到处生效;根据这个思想,那么我们就可以在我们的代码中的某处定义一个决定业务组件属性的常量,然后让所有业务组件的build.gradle都引用这个常量,这样当我们改变了常量值的时候,所有引用了这个常量值的业务组件就会根据值的变化改变自己的属性;可是问题来了?静态常量是用Java代码定义的,而改变组件属性是需要在Gradle中定义的,Gradle能做到吗?

    Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否):

    # 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
    #不能用;结束
    isModule = true

    然后我们在每一个业务组件的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到 isModule 是个String类型的值,而我们需要的是Boolean值,代码如下:在模块build.gradle中

    if(isModule.toBoolean()){//如果是组件模式---可以单独运行
        apply plugin: 'com.android.application'
    }else {//集成模式,不能单独运行
        apply plugin: 'com.android.library'
    }

    这样我们第一个问题就解决了,当然了 每次改变isModule的值后,都要同步项目才能生效;

    2)组件之间AndroidManifest合并问题

    在 AndroidStudio 中每一个组件都会有对应的 AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的 AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要 launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的 AndroidManifest.xml 都要合并到“app壳工程”中,要是每一个业务组件都有自己的 Application 和 launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和 launch 的Activity呢?

    但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。

    上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的 build.gradle 中指定表单的路径,代码如下:

    这样在不同的开发模式下就会读取到不同的 AndroidManifest.xml ,然后我们需要修改这两个表单的内容以为我们不同的开发模式服务。

    首先是集成开发模式下的 AndroidManifest.xml,前面我们说过集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有,下面是一份标准的集成开发模式下业务组件的 AndroidManifest.xml:

    我在这个表单中只声明了应用的主题,而且这个主题还是跟app壳工程中的主题是一致的,都引用了BaseModule组件中的资源文件,在这里声明主题是为了方便这个业务组件中有使用默认主题的Activity时就不用再给Activity单独声明theme了。

    然后是组件开发模式下的表单文件:

    组件模式下的业务组件表单就是一个Android项目普通的AndroidManifest.xml,这里就不在过多介绍了。

    3)全局Context的获取及组件数据初始化

    当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。

    但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。

    在 组件化工程模型图中,功能组件集合中有一个 BaseModule组件BaseModule有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖BaseModule组件,BaseModule组件就像是万丈高楼的地基,而业务组件就是在 BaseModule组件这个地基上搭建起来我们的APP的,BaseModule组件会专门在一个章节中讲解,这里只讲 BaseModule组件中的一个功能,在BaseModule组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类

    BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;

    这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的 Application 吗,怎么还让他们继承 BaseApplication 呢?其实我前面说的是业务组件不能在集成模式下拥有自己的 Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,其实业务组件在组件开发模式下必须要有自己的 Application 类,一方面是为了让 BaseApplication 被实例化从而获取 Context,还有一个作用是,业务组件自己的 Application 可以在组件开发模式下初始化一些数据,例如在组件开发模式下,A组件没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在A组件这个 APP 启动后就应该已经登录了,这时候组件自己的 Application 类就有了用武之地,我们在组件的 Application的 onCreate 方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理A组件中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库

    但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的 Application 仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在 AndroidManifest 合并问题小节中介绍了如何在不同开发模式下让 Gradle 识别组件表单的路径,这个方法也同样适用于Java代码;

    我们在Java文件夹下创建一个 debug 文件夹,用于存放不会在业务组件中引用的类,例如上图中的 NewsApplication ,你甚至可以在 debug 文件夹中创建一个Activity,然后组件表单中声明启动这个Activity,在这个Activity中不用setContentView,只需要在启动你的目标Activity的时候传递参数就行,这样就就可以解决组件模式下某些Activity需要getIntent数据而没有办法拿到的情况.

    接下来在业务组件的 build.gradle 中,根据 isModule 是否是集成模式将 debug 这个 Java代码文件夹排除:

    4)library依赖问题

    在介绍这一节的时候,先说一个问题,在组件化工程模型图中,多媒体组件和BaseModule组件都依赖了日志组件,而A业务组件有同时依赖了多媒体组件和BaseModule组件,这时候就会有人问,你这样搞岂不是日志组件要被重复依赖了,而且BaseModule组件也被每一个业务组件依赖了,这样不出问题吗?

    其实大家完全没有必要担心这个问题,如果真有重复依赖的问题,在你编译打包的时候就会报错,如果你还是不相信的话可以反编译下最后打包出来的APP,看看里面的代码你就知道了。组件只是我们在代码开发阶段中为了方便叫的一个术语,在组件被打包进APP的时候是没有这个概念的,这些组件最后都会被打包成arr包,然后被app壳工程所依赖,在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了

    但是虽然组件是不会重复了,但是我们还是要考虑另一个情况,我们在build.gradle中compile的第三方库,例如AndroidSupport库经常会被一些开源的控件所依赖,而我们自己一定也会compile AndroidSupport库 ,这就会造成第三方包和我们自己的包存在重复加载,解决办法就是找出那个多出来的库,并将多出来的库给排除掉,而且Gradle也是支持这样做的,分别有两种方式:根据组件名排除或者根据包名排除,下面以排除support-v4库为例:

    dependencies {
    
    compile fileTree(dir: 'libs', include: ['*.jar'])
    
    compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
    
    exclude module: 'support-v4'//根据组件名排除
    
    exclude group: 'android.support.v4'//根据包名排除
    
    }
    
    }

    library重复依赖的问题算是都解决了,但是我们在开发项目的时候会依赖很多开源库,而这些库每个组件都需要用到,要是每个组件都去依赖一遍也是很麻烦的,尤其是给这些库升级的时候,为了方便我们统一管理第三方库,我们将给给整个工程提供统一的依赖第三方库的入口,前面介绍的BaseModule库的作用之一就是统一依赖开源库,因为其他业务组件都依赖了BaseModule库,所以这些业务组件也就间接依赖了BaseModule所依赖的开源库。

    dependencies {
    
    compile fileTree(dir: 'libs', include: ['*.jar'])
    
    //Android Support
    
    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    
    compile "com.android.support:design:$rootProject.supportLibraryVersion"
    
    compile "com.android.support:percent:$rootProject.supportLibraryVersion"
    
    //网络请求相关
    
    compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
    
    compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
    
    compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
    
    //稳定的
    
    compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
    
    compile "com.orhanobut:logger:$rootProject.loggerVersion"
    
    compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
    
    compile "com.google.code.gson:gson:$rootProject.gsonVersion"
    
    compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
    
    
    compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
    
    compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
    
    
    //router
    
    compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    
    }

    然后将所有的具体版本号提取出来,放到根目录的build.gradle文件中,统一管理

    5)组件之间调用和通信

    在组件化开发的时候,组件之间是没有依赖关系我们不能在使用显示调用来跳转页面了因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。

    本文主要采用阿里的开元框架ARouter路由来实现组建之间的调用和通信。ARouter的详细使用请前往https://mp.csdn.net/console/editor/html/104569796

    ARouter各个模块的gradle配置

    • 因为路由跳转是子模块都需要用到的,所以我们在BaseModule 模块build.gradle中引入
        defaultConfig {
            //ARouter添加
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
            }
    ....
    }
    
    dependencies {
    api "com.alibaba:arouter-api:$rootProject.ext.arouterApiVersion"//跳转路由
    
    }
    
    $rootProject.ext.arouterApiVersion == 1.3.1
    • 然后在各业务模块的build.gradle文件中导入
        defaultConfig {
            //ARouter添加
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
            }
    ....
    }
    
    dependencies {
        annotationProcessor "com.alibaba:arouter-compiler:$rootProject.ext.arouterCompilerVersion"
    }
    
    $rootProject.ext.arouterCompilerVersion == 1.1.4

    组件之间的调用解决后,另外需要解决的就是组件之间的通信,例如A业务组件中有消息列表,而用户在B组件中操作某个事件后会产生一条新消息,需要通知A组件刷新消息列表,这样业务场景需求可以使用Android广播来解决,也可以使用第三方的事件总线来实现,比如RXBus。

    ARouter的具体使用:

    初始化

    官方建议我们在Application里面进行ARouter初始化,于是乎就有了以下代码:

    @Override
    public void onCreate() {
        super.onCreate();        
        if (LogUtils.isDebuggable()){//下面2行必须在init前面
            ARouter.openDebug();//开启调试模式
            ARouter.openLog();//打印日志
        }
        ARouter.init(BaseApplication.this);
    }
    @Override
    public void onTerminate() {
       super.onTerminate();
       //ARouter销毁
       ARouter.getInstance().destroy();
    }

    然后别忘记了在清单文件里面配置自定义的Application和我们的Activity。

    项目依赖导入和初始化就已经完成了,下面就开始正式的功能使用以及简单的封装。

    开始使用:

    1)首先:在Activity/Fragment类上面写上 Route path 注解。

          注意:这里的路径需要注意的是至少需要有两级,/xx/xx

    2)然后:在Activity/Fragment类里面进入Arouter 注入,也就是:ARouter.getInstance().inject(this);

    3)接着:目标的Activity类上面需要声明Route path 注解,以此对应

     ARouter.getInstance().build(ConstanceURl.Home_Main).navigation();

    传递参数

    UrUtils2 urUtils2 = new UrUtils2();
    urUtils2.setName1("对象名字");
    urUtils2.setNum1(1);
    ARouter.getInstance().build(ConstanceURl.Find_Main)
           .withString("name","名字1")
           .withInt("num",2)
           .withObject("test",urUtils2).navigation();

    接受

    路径管理

    界面跳转动画

    直接调用withTransition,里面传入两个动画即可(R.anim.xxx)

    使用URI进行跳转

    ARouter框架也可以使用URI进行匹配跳转,代码也很少,只需匹配路径一致即可完成跳转:

    Fragment跳转

    Fragment的跳转也可以参照Activity跳转,第一步依旧是先写上类注释,然后是强转,代码如下

    ARouter如何实现类似startActivityForResult()?

    返回

    6)ButterKnife的引入

    在更目录build.gradle中引入依赖

    classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.1'

    在BaseModule添加build.gradle中引入依赖

    //ButterKnife
        api "com.jakewharton:butterknife:$rootProject.ext.butterknifeVersion"
        annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.ext.butterknifeCompilerVersion"
    
    $rootProject.ext.butterknifeVersion  == 10.2.1 
    $rootProject.ext.butterknifeCompilerVersion == 10.2.1 

    在每个业务模块build.gradle中引入依赖

    // 必须引入,否则会生成不了R2.class
    apply plugin: 'com.jakewharton.butterknife'
    
    
    annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.ext.butterknifeCompilerVersion"

     

    在每一个Module(业务模块,功能模块,Main模块,壳app模块)都要在build.gradle中添加java8的支持

    android{
        .....
         compileOptions{
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }

    使用:

    展开全文
  • Android组件化方案

    万次阅读 多人点赞 2017-02-15 19:01:52
    每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时。

    版权声明:本文为博主原创文章,欢迎大家转载!
    转载请标明出处: http://blog.csdn.net/guiying712/article/details/55213884 ,本文出自:【张华洋的博客】


    Android组件化项目地址:Android组件化项目AndroidModulePattern

    Android组件化之终极方案地址:http://blog.csdn.net/guiying712/article/details/78057120

    1、为什么要项目组件化

    随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。

    单一工程模型

    上图是目前比较普遍使用的Android APP技术架构,往往是在一个界面中存在大量的业务逻辑,而业务逻辑中充斥着各种网络请求、数据操作等行为,整个项目中也没有模块的概念,只有简单的以业务逻辑划分的文件夹,并且业务之间也是直接相互调用、高度耦合在一起的;

    单一工程模型下的业务关系

    上图单一工程模型下的业务关系,总的来说就是:你中有我,我中有你,相互依赖,无法分离。
    然而随着产品的迭代,业务越来越复杂,随之带来的是项目结构复杂度的极度增加,此时我们会面临如下几个问题:

    1、实际业务变化非常快,但是单一工程的业务模块耦合度太高,牵一发而动全身;
    2、对工程所做的任何修改都必须要编译整个工程;
    3、功能测试和系统测试每次都要进行;
    4、团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,并且在开发过程中,任何一位成员没办法专注于自己的功能点,影响开发效率;
    5、不能灵活的对业务模块进行配置和组装;

    为了满足各个业务模块的迭代而彼此不受影响,更好的解决上面这种让人头疼的依赖关系,就需要整改App的架构。


    2、如何组件化

    组件化工程模型

    上图是组件化工程模型,为了方便理解这张架构图,下面会列举一些组件化工程中用到的名词的含义:

    名词含义
    集成模式所有的业务组件被“app壳工程”依赖,组成一个完整的APP;
    组件模式可以独立开发业务组件,每一个业务组件就是一个APP;
    app壳工程负责管理各个业务组件,和打包apk,没有具体的业务功能;
    业务组件根据公司具体业务而独立形成一个的工程;
    功能组件提供开发APP的某些基础功能,例如打印日志、树状图等;
    Main组件属于业务组件,指定APP启动页面、主界面;
    Common组件属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;

    **
    Android APP组件化架构的目标是告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为arr包集成到“app壳工程”中,组成一个完整功能的APP;
    从组件化工程模型中可以看到,业务组件之间是独立的,没有关联的,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整业务功能的APP应用,但是在组件开发模式下,业务组件又变成了一个个application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。

    组件化工程下的业务关系

    这是组件化工程模型下的业务关系,业务之间将不再直接引用和依赖,而是通过“路由”这样一个中转站间接产生联系,而Android中的路由实际就是对URL Scheme的封装;
    如此规模大的架构整改需要付出更高的成本,还会涉及一些潜在的风险,但是整改后的架构能够带来很多好处:

    1、加快业务迭代速度,各个业务模块组件更加独立,不再出现业务耦合情况;
    2、稳定的公共模块采用依赖库方式,提供给各个业务线使用,减少重复开发和维护工作量;
    3、迭代频繁的业务模块采用组件方式,各业务研发可以互不干扰、提升协作效率,并控制产品质量;
    4、为新业务随时集成提供了基础,所有业务可上可下,灵活多变;
    5、降低团队成员熟悉项目的成本,降低项目的维护难度;
    6、加快编译速度,提高开发效率;
    7、控制代码权限,将代码的权限细分到更小的粒度;


    3、组件化实施流程

    1)组件模式和集成模式的转换

    Android Studio中的Module主要有两种属性,分别为:

    1、application属性,可以独立运行的Android程序,也就是我们的APP;

    apply plugin: ‘com.android.application’

    2、library属性,不可以独立运行,一般是Android程序依赖的库文件;

    apply plugin: ‘com.android.library’

    Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;

    但是我们如何让组件在这两种模式之间自动转换呢?总不能每次需要转换模式的时候去每个业务组件的 Gralde 文件中去手动把 Application 改成 library 吧?如果我们的项目只有两三个组件那么这个办法肯定是可行的,手动去改一遍也用不了多久,但是在大型项目中我们可能会有十几个业务组件,再去手动改一遍必定费时费力,这时候就需要程序员发挥下懒的本质了。

    试想,我们经常在写代码的时候定义静态常量,那么定义静态常量的目的什么呢?当一个常量需要被好几处代码引用的时候,把这个常量定义为静态常量的好处是当这个常量的值需要改变时我们只需要改变静态常量的值,其他引用了这个静态常量的地方都会被改变,做到了一次改变,到处生效;根据这个思想,那么我们就可以在我们的代码中的某处定义一个决定业务组件属性的常量,然后让所有业务组件的build.gradle都引用这个常量,这样当我们改变了常量值的时候,所有引用了这个常量值的业务组件就会根据值的变化改变自己的属性;可是问题来了?静态常量是用Java代码定义的,而改变组件属性是需要在Gradle中定义的,Gradle能做到吗?

    Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否)

    # 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
    isModule=false

    然后我们在业务组件的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到 isModule 是个String类型的值,而我们需要的是Boolean值,代码如下:

    if (isModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

    这样我们第一个问题就解决了,当然了 每次改变isModule的值后,都要同步项目才能生效;

    2)组件之间AndroidManifest合并问题

    在 AndroidStudio 中每一个组件都会有对应的 AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的 AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要 launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的 AndroidManifest.xml 都要合并到“app壳工程”中,要是每一个业务组件都有自己的 Application 和 launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和 launch 的Activity呢?

    但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。

    业务组件的目录结构

    上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的 build.gradle 中指定表单的路径,代码如下:

      sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }

    这样在不同的开发模式下就会读取到不同的 AndroidManifest.xml ,然后我们需要修改这两个表单的内容以为我们不同的开发模式服务。

    首先是集成开发模式下的 AndroidManifest.xml,前面我们说过集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有,下面是一份标准的集成开发模式下业务组件的 AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.guiying.girls">
    
        <application android:theme="@style/AppTheme">
            <activity
                android:name=".main.GirlsActivity"
                android:screenOrientation="portrait" />
            <activity
                android:name=".girl.GirlActivity"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar" />
        </application>
    
    </manifest>
    

    我在这个表单中只声明了应用的主题,而且这个主题还是跟app壳工程中的主题是一致的,都引用了common组件中的资源文件,在这里声明主题是为了方便这个业务组件中有使用默认主题的Activity时就不用再给Activity单独声明theme了。

    然后是组件开发模式下的表单文件:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.guiying.girls">
    
        <application
            android:name="debug.GirlsApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/girls_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".main.GirlsActivity"
                android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity
                android:name=".girl.GirlActivity"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar" />
        </application>
    
    </manifest>

    组件模式下的业务组件表单就是一个Android项目普通的AndroidManifest.xml,这里就不在过多介绍了。


    3)全局Context的获取及组件数据初始化

    当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。

    但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。

    在 组件化工程模型图中,功能组件集合中有一个 Common 组件, Common 有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖Common组件,Common 组件就像是万丈高楼的地基,而业务组件就是在 Common 组件这个地基上搭建起来我们的APP的,Common 组件会专门在一个章节中讲解,这里只讲 Common组件中的一个功能,在Common组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类

    BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;

    这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的 Application 吗,怎么还让他们继承 BaseApplication 呢?其实我前面说的是业务组件不能在集成模式下拥有自己的 Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,其实业务组件在组件开发模式下必须要有自己的 Application 类,一方面是为了让 BaseApplication 被实例化从而获取 Context,还有一个作用是,业务组件自己的 Application 可以在组件开发模式下初始化一些数据,例如在组件开发模式下,A组件没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在A组件这个 APP 启动后就应该已经登录了,这时候组件自己的 Application 类就有了用武之地,我们在组件的 Application的 onCreate 方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理A组件中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库

    但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的 Application 仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在 AndroidManifest 合并问题小节中介绍了如何在不同开发模式下让 Gradle 识别组件表单的路径,这个方法也同样适用于Java代码;

    业务组件的java目录结构

    我们在Java文件夹下创建一个 debug 文件夹,用于存放不会在业务组件中引用的类,例如上图中的 NewsApplication ,你甚至可以在 debug 文件夹中创建一个Activity,然后组件表单中声明启动这个Activity,在这个Activity中不用setContentView,只需要在启动你的目标Activity的时候传递参数就行,这样就就可以解决组件模式下某些Activity需要getIntent数据而没有办法拿到的情况,代码如下;

    public class LauncherActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            request();
            Intent intent = new Intent(this, TargetActivity.class);
            intent.putExtra("name", "avcd");
            intent.putExtra("syscode", "023e2e12ed");
            startActivity(intent);
            finish();
        }
    
        //申请读写权限
        private void request() {
            AndPermission.with(this)
                    .requestCode(110)
                    .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
                    .callback(this)
                    .start();
        }
    
    }

    接下来在业务组件的 build.gradle 中,根据 isModule 是否是集成模式将 debug 这个 Java代码文件夹排除:

    
        sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    //集成开发模式下排除debug文件夹中的所有Java文件
                    java {
                        exclude 'debug/**'
                    }
                }
            }
        }

    4)library依赖问题

    在介绍这一节的时候,先说一个问题,在组件化工程模型图中,多媒体组件和Common组件都依赖了日志组件,而A业务组件有同时依赖了多媒体组件和Common组件,这时候就会有人问,你这样搞岂不是日志组件要被重复依赖了,而且Common组件也被每一个业务组件依赖了,这样不出问题吗?

    其实大家完全没有必要担心这个问题,如果真有重复依赖的问题,在你编译打包的时候就会报错,如果你还是不相信的话可以反编译下最后打包出来的APP,看看里面的代码你就知道了。组件只是我们在代码开发阶段中为了方便叫的一个术语,在组件被打包进APP的时候是没有这个概念的,这些组件最后都会被打包成arr包,然后被app壳工程所依赖,在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了;

    但是虽然组件是不会重复了,但是我们还是要考虑另一个情况,我们在build.gradle中compile的第三方库,例如AndroidSupport库经常会被一些开源的控件所依赖,而我们自己一定也会compile AndroidSupport库 ,这就会造成第三方包和我们自己的包存在重复加载,解决办法就是找出那个多出来的库,并将多出来的库给排除掉,而且Gradle也是支持这样做的,分别有两种方式:根据组件名排除或者根据包名排除,下面以排除support-v4库为例:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
            exclude module: 'support-v4'//根据组件名排除
            exclude group: 'android.support.v4'//根据包名排除
        }
    }

    library重复依赖的问题算是都解决了,但是我们在开发项目的时候会依赖很多开源库,而这些库每个组件都需要用到,要是每个组件都去依赖一遍也是很麻烦的,尤其是给这些库升级的时候,为了方便我们统一管理第三方库,我们将给给整个工程提供统一的依赖第三方库的入口,前面介绍的Common库的作用之一就是统一依赖开源库,因为其他业务组件都依赖了Common库,所以这些业务组件也就间接依赖了Common所依赖的开源库。

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //Android Support
        compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
        compile "com.android.support:design:$rootProject.supportLibraryVersion"
        compile "com.android.support:percent:$rootProject.supportLibraryVersion"
        //网络请求相关
        compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
        compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
        compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
        //稳定的
        compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
        compile "com.orhanobut:logger:$rootProject.loggerVersion"
        compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
        compile "com.google.code.gson:gson:$rootProject.gsonVersion"
        compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
    
        compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
        compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
    
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }

    5)组件之间调用和通信

    在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。

    这里我将介绍开源库的“ActivityRouter” ,有兴趣的同学情直接去ActivityRouter的Github主页学习:ActivityRouter,ActivityRouter支持给Activity定义 URL,这样就可以通过 URL 跳转到Activity,并且支持从浏览器以及 APP 中跳入我们的Activity,而且还支持通过 url 调用方法。下面将介绍如何将ActivityRouter集成到组件化项目中以实现组件之间的调用;

    1、首先我们需要在 Common 组件中的 build.gradle 将ActivityRouter 依赖进来,方便我们在业务组件中调用:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }

    2、这一步我们需要先了解 APT这个概念,APT(Annotation Processing Tool)是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。在这里我们将在每一个业务组件的 build.gradle 都引入ActivityRouter 的 Annotation处理器,我们将会在声明组件和Url的时候使用,annotationProcessor是Android官方提供的Annotation处理器插件,代码如下:

    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
    }

    3、接下来需要在 app壳工程的 AndroidManifest.xml 配置,到这里ActivityRouter配置就算完成了:

     <!--声明整个应用程序的路由协议-->
            <activity
                android:name="com.github.mzule.activityrouter.router.RouterActivity"
                android:theme="@android:style/Theme.NoDisplay">
                <intent-filter>
                    <action android:name="android.intent.action.VIEW" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
    
                    <data android:scheme="@string/global_scheme" /> <!-- 改成自己的scheme -->
                </intent-filter>
            </activity>
            <!--发送崩溃日志界面-->

    4、接下来我们将声明项目中的业务组件,声明方法如下:

    @Module("girls")
    public class Girls {
    }

    在每一个业务组件的java文件的根目录下创建一个类,用 注解@Module 声明这个业务组件;
    然后在“app壳工程”的 应用Application 中使用 注解@Modules 管理我们声明的所有业务组件,方法如下:

    @Modules({"main", "girls", "news"})
    public class MyApplication extends BaseApplication {
    }

    到这里组件化项目中的所有业务组件就被声明和管理起来了,组件之间的也就可以互相调用了,当然前提是要给业务组件中的Activity定义 URL。

    5、例如我们给 Girls组件 中的 GirlsActivity 使用 注解@Router 定义一个 URL:“news”,方法如下:

    @Router("girls")
    public class GirlsActivity extends BaseActionBarActivity {
    
        private GirlsView mView;
        private GirlsContract.Presenter mPresenter;
    
        @Override
        protected int setTitleId() {
            return R.string.girls_activity_title;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mView = new GirlsView(this);
            setContentView(mView);
            mPresenter = new GirlsPresenter(mView);
            mPresenter.start();
        }
    }

    然后我们就可以在项目中的任何一个地方通过 URL地址 : module://girls, 调用 GirlsActivity,方法如下:

           Routers.open(MainActivity.this, "module://girls");

    组件之间的调用解决后,另外需要解决的就是组件之间的通信,例如A业务组件中有消息列表,而用户在B组件中操作某个事件后会产生一条新消息,需要通知A组件刷新消息列表,这样业务场景需求可以使用Android广播来解决,也可以使用第三方的事件总线来实现,比如EventBus


    6)组件之间资源名冲突

    因为我们拆分出了很多业务组件和功能组件,在把这些组件合并到“app壳工程”时候就有可能会出现资源名冲突问题,例如A组件和B组件都有一张叫做“ic_back”的图标,这时候在集成模式下打包APP就会编译出错,解决这个问题最简单的办法就是在项目中约定资源文件命名规约,比如强制使每个资源文件的名称以组件名开始,这个可以根据实际情况和开发人员制定规则。当然了万能的Gradle构建工具也提供了解决方法,通过在在组件的build.gradle中添加如下的代码:

        //设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
        //但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
        resourcePrefix "girls_"

    但是设置了这个属性后有个问题,所有的资源名必须以指定的字符串做前缀,否则会报错,而且resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名;所以我并不推荐使用这种方法来解决资源名冲突。


    4、组件化项目的工程类型

    在组件化工程模型中主要有:app壳工程、业务组件和功能组件3种类型,而业务组件中的Main组件和功能组件中的Common组件比较特殊,下面将分别介绍。

    1)app壳工程

    app壳工程是从名称来解释就是一个空壳工程,没有任何的业务代码,也不能有Activity,但它又必须被单独划分成一个组件,而不能融合到其他组件中,是因为它有如下几点重要功能:

    1、app壳工程中声明了我们Android应用的 Application,这个 Application 必须继承自 Common组件中的 BaseApplication(如果你无需实现自己的Application可以直接在表单声明BaseApplication),因为只有这样,在打包应用后才能让BaseApplication中的Context生效,当然你还可以在这个 Application中初始化我们工程中使用到的库文件,还可以在这里解决Android引用方法数不能超过 65535 的限制,对崩溃事件的捕获和发送也可以在这里声明。

    2、app壳工程的 AndroidManifest.xml 是我Android应用的根表单,应用的名称、图标以及是否支持备份等等属性都是在这份表单中配置的,其他组件中的表单最终在集成开发模式下都被合并到这份 AndroidManifest.xml 中。

    3、app壳工程的 build.gradle 是比较特殊的,app壳不管是在集成开发模式还是组件开发模式,它的属性始终都是:com.android.application,因为最终其他的组件都要被app壳工程所依赖,被打包进app壳工程中,这一点从组件化工程模型图中就能体现出来,所以app壳工程是不需要单独调试单独开发的。另外Android应用的打包签名,以及buildTypes和defaultConfig都需要在这里配置,而它的dependencies则需要根据isModule的值分别依赖不同的组件,在组件开发模式下app壳工程只需要依赖Common组件,或者为了防止报错也可以根据实际情况依赖其他功能组件,而在集成模式下app壳工程必须依赖所有在应用Application中声明的业务组件,并且不需要再依赖任何功能组件。

    下面是一份 app壳工程 的 build.gradle文件

    apply plugin: 'com.android.application'
    
    static def buildTime() {
        return new Date().format("yyyyMMdd");
    }
    
    android {
        signingConfigs {
            release {
                keyAlias 'guiying712'
                keyPassword 'guiying712'
                storeFile file('/mykey.jks')
                storePassword 'guiying712'
            }
        }
    
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
        defaultConfig {
            applicationId "com.guiying.androidmodulepattern"
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
            multiDexEnabled false
            //打包时间
            resValue "string", "build_time", buildTime()
        }
    
        buildTypes {
            release {
                //更改AndroidManifest.xml中预先定义好占位符信息
                //manifestPlaceholders = [app_icon: "@drawable/icon"]
                // 不显示Log
                buildConfigField "boolean", "LEO_DEBUG", "false"
                //是否zip对齐
                zipAlignEnabled true
                // 缩减resource文件
                shrinkResources true
                //Proguard
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                //签名
                signingConfig signingConfigs.release
            }
    
            debug {
                //给applicationId添加后缀“.debug”
                applicationIdSuffix ".debug"
                //manifestPlaceholders = [app_icon: "@drawable/launch_beta"]
                buildConfigField "boolean", "LOG_DEBUG", "true"
                zipAlignEnabled false
                shrinkResources false
                minifyEnabled false
                debuggable true
            }
        }
    
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
        if (isModule.toBoolean()) {
            compile project(':lib_common')
        } else {
            compile project(':module_main')
            compile project(':module_girls')
            compile project(':module_news')
        }
    }

    2)功能组件和Common组件

    功能组件是为了支撑业务组件的某些功能而独立划分出来的组件,功能实质上跟项目中引入的第三方库是一样的,功能组件的特征如下:

    1、功能组件的 AndroidManifest.xml 是一张空表,这张表中只有功能组件的包名;

    2、功能组件不管是在集成开发模式下还是组件开发模式下属性始终是: com.android.library,所以功能组件是不需要读取 gradle.properties 中的 isModule 值的;另外功能组件的 build.gradle 也无需设置 buildTypes ,只需要 dependencies 这个功能组件需要的jar包和开源库。

    下面是一份 普通 的功能组件的 build.gradle文件

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }

    Common组件除了有功能组件的普遍属性外,还具有其他功能

    1、Common组件的 AndroidManifest.xml 不是一张空表,这张表中声明了我们 Android应用用到的所有使用权限 uses-permission 和 uses-feature,放到这里是因为在组件开发模式下,所有业务组件就无需在自己的 AndroidManifest.xm 声明自己要用到的权限了。

    2、Common组件的 build.gradle 需要统一依赖业务组件中用到的 第三方依赖库和jar包,例如我们用到的ActivityRouter、Okhttp等等。

    3、Common组件中封装了Android应用的 Base类和网络请求工具、图片加载工具等等,公用的 widget控件也应该放在Common 组件中;业务组件中都用到的数据也应放于Common组件中,例如保存到 SharedPreferences 和 DataBase 中的登陆数据;

    4、Common组件的资源文件中需要放置项目公用的 Drawable、layout、sting、dimen、color和style 等等,另外项目中的 Activity 主题必须定义在 Common中,方便和 BaseActivity 配合保持整个Android应用的界面风格统一。

    下面是一份 Common功能组件的 build.gradle文件

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //Android Support
        compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
        compile "com.android.support:design:$rootProject.supportLibraryVersion"
        compile "com.android.support:percent:$rootProject.supportLibraryVersion"
        //网络请求相关
        compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
        compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
        compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
        //稳定的
        compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
        compile "com.orhanobut:logger:$rootProject.loggerVersion"
        compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
        compile "com.google.code.gson:gson:$rootProject.gsonVersion"
        compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
    
        compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
        compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
    
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }
    

    2)业务组件和Main组件

    业务组件就是根据业务逻辑的不同拆分出来的组件,业务组件的特征如下:

    1、业务组件中要有两张AndroidManifest.xml,分别对应组件开发模式和集成开发模式,这两张表的区别请查看 组件之间AndroidManifest合并问题 小节。

    2、业务组件在集成模式下是不能有自己的Application的,但在组件开发模式下又必须实现自己的Application并且要继承自Common组件的BaseApplication,并且这个Application不能被业务组件中的代码引用,因为它的功能就是为了使业务组件从BaseApplication中获取的全局Context生效,还有初始化数据之用。

    3、业务组件有debug文件夹,这个文件夹在集成模式下会从业务组件的代码中排除掉,所以debug文件夹中的类不能被业务组件强引用,例如组件模式下的 Application 就是置于这个文件夹中,还有组件模式下开发给目标 Activity 传递参数的用的 launch Activity 也应该置于 debug 文件夹中;

    4、业务组件必须在自己的 Java文件夹中创建业务组件声明类,以使 app壳工程 中的 应用Application能够引用,实现组件跳转,具体请查看 组件之间调用和通信 小节;

    5、业务组件必须在自己的 build.gradle 中根据 isModule 值的不同改变自己的属性,在组件模式下是:com.android.application,而在集成模式下com.android.library;同时还需要在build.gradle配置资源文件,如 指定不同开发模式下的AndroidManifest.xml文件路径,排除debug文件夹等;业务组件还必须在dependencies中依赖Common组件,并且引入ActivityRouter的注解处理器annotationProcessor,以及依赖其他用到的功能组件。

    下面是一份普通业务组件的 build.gradle文件

    if (isModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
        sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    //集成开发模式下排除debug文件夹中的所有Java文件
                    java {
                        exclude 'debug/**'
                    }
                }
            }
        }
    
        //设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
        //但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
        //resourcePrefix "girls_"
    
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
        compile project(':lib_common')
    }

    Main组件除了有业务组件的普遍属性外,还有一项重要功能

    1、Main组件集成模式下的AndroidManifest.xml是跟其他业务组件不一样的,Main组件的表单中声明了我们整个Android应用的launch Activity,这就是Main组件的独特之处;所以我建议SplashActivity、登陆Activity以及主界面都应属于Main组件,也就是说Android应用启动后要调用的页面应置于Main组件。

            <activity
                android:name=".splash.SplashActivity"
                android:launchMode="singleTop"
                android:screenOrientation="portrait"
                android:theme="@style/SplashTheme">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>

    5、组件化项目的混淆方案

    组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。

    之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被 Gradle 构建成了 release 类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的 build.gradle 文件中配置 buildTypes (构建类型)的原因。


    6、工程的build.gradle和gradle.properties文件

    1)组件化工程的build.gradle文件

    在组件化项目中因为每个组件的 build.gradle 都需要配置 compileSdkVersion、buildToolsVersion和defaultConfig 等的版本号,而且每个组件都需要用到 annotationProcessor,为了能够使组件化项目中的所有组件的 build.gradle 中的这些配置都能保持统一,并且也是为了方便修改版本号,我们统一在Android工程根目录下的build.gradle中定义这些版本号,当然为了方便管理Common组件中的第三方开源库的版本号,最好也在这里定义这些开源库的版本号,然后在各个组件的build.gradle中引用Android工程根目录下的build.gradle定义的版本号,组件化工程的 build.gradle 文件代码如下:

    buildscript {
        repositories {
            jcenter()
            mavenCentral()
        }
    
        dependencies {
            //classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
            //$localGradlePluginVersion是gradle.properties中的数据
            classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
        }
    }
    
    allprojects {
        repositories {
            jcenter()
            mavenCentral()
            //Add the JitPack repository
            maven { url "https://jitpack.io" }
            //支持arr包
            flatDir {
                dirs 'libs'
            }
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    // Define versions in a single place
    //时间:2017.2.13;每次修改版本号都要添加修改时间
    ext {
        // Sdk and tools
        //localBuildToolsVersion是gradle.properties中的数据
        buildToolsVersion = localBuildToolsVersion
        compileSdkVersion = 25
        minSdkVersion = 16
        targetSdkVersion = 25
        versionCode = 1
        versionName = "1.0"
        javaVersion = JavaVersion.VERSION_1_8
    
        // App dependencies version
        supportLibraryVersion = "25.3.1"
        retrofitVersion = "2.1.0"
        glideVersion = "3.7.0"
        loggerVersion = "1.15"
        eventbusVersion = "3.0.0"
        gsonVersion = "2.8.0"
        photoViewVersion = "2.0.0"
    
        //需检查升级版本
        annotationProcessor = "1.1.7"
        routerVersion = "1.2.2"
        easyRecyclerVersion = "4.4.0"
        cookieVersion = "v1.0.1"
        toastyVersion = "1.1.3"
    }
    

    2)组件化工程的gradle.properties文件

    在组件化实施流程中我们了解到gradle.properties有两个属性对我们非常有用:

    1、在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来,不管这个build.gradle是组件的还是整个项目工程的build.gradle;

    2、gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换;

    利用gradle.properties的属性不仅可以解决集成开发模式和组件开发模式的转换,而且还可以解决在多人协同开发Android项目的时候,因为开发团队成员的Android开发环境(开发环境指Android SDK和AndroidStudio)不一致而导致频繁改变线上项目的build.gradle配置。

    在每个Android组件的 build.gradle 中有一个属性:buildToolsVersion,表示构建工具的版本号,这个属性值对应 AndroidSDK 中的 Android SDK Build-tools,正常情况下 build.gradle 中的 buildToolsVersion 跟你电脑中 Android SDK Build-tools 的最新版本是一致的,比如现在 Android SDK Build-tools 的最新的版本是:25.0.3,那么我的Android项目中 build.gradle 中的 buildToolsVersion 版本号也是 25.0.3,但是一旦一个Android项目是由好几个人同时开发,总会出现每个人的开发环境 Android SDK Build-tools 是都是不一样的,并不是所有人都会经常升级更新 Android SDK,而且代码是保存到线上环境的(例如使用 SVN/Git 等工具),某个开发人员提交代码后线上Android项目中 build.gradle 中的 buildToolsVersion 也会被不断地改变。

    另外一个原因是因为Android工程的根目录下的 build.gradle 声明了 Android Gradle 构建工具,而这个工具也是有版本号的,而且 Gradle Build Tools 的版本号跟 AndroidStudio 版本号一致的,但是有些开发人员基本很久都不会升级自己的 AndroidStudio 版本,导致团队中每个开发人员的 Gradle Build Tools 的版本号也不一致。

    如果每次同步代码后这两个工具的版本号被改变了,开发人员可以自己手动改回来,并且不要把改动工具版本号的代码提交到线上环境,这样还可以勉强继续开发;但是很多公司都会使用持续集成工具(例如Jenkins)用于持续的软件版本发布,而Android出包是需要 Android SDK Build-tools 和 Gradle Build Tools 配合的,一旦提交到线上的版本跟持续集成工具所依赖的Android环境构建工具版本号不一致就会导致Android打包失败。

    为了解决上面问题就必须将Android项目中 build.gradle 中的 buildToolsVersion 和 GradleBuildTools 版本号从线上代码隔离出来,保证线上代码的 buildToolsVersion 和 Gradle Build Tools 版本号不会被人为改变。

    具体的实施流程大家可以查看我的这篇博文 AndroidStudio本地化配置gradle的buildToolsVersion和gradleBuildTools


    7、组件化项目Router的其他方案-ARouter

    在组件化项目中使用到的跨组件跳转库ActivityRouter可以使用阿里巴巴的开源路由项目:阿里巴巴ARouter

    ActivityRouter和ARouter的接入组件化项目的方式是一样的,ActivityRouter提供的功能目前ARouter也全部支持,但是ARouter还支持依赖注入解耦,页面、拦截器、服务等组件均会自动注册到框架。对于大家来说,没有最好的只有最适合的,大家可以根据自己的项目选择合适的Router。

    下面将介绍ARouter的基础使用方法,更多功能还需大家去Github自己学习;

    1、首先 ARouter 这个框架是需要初始化SDK的,所以你需要在“app壳工程”中的应用Application中加入下面的代码,注意:在 debug 模式下一定要 openDebug

        if (BuildConfig.DEBUG) {
                //一定要在ARouter.init之前调用openDebug
                ARouter.openDebug();
                ARouter.openLog();
           }
           ARouter.init(this);

    2、首先我们依然需要在 Common 组件中的 build.gradle 将ARouter 依赖进来,方便我们在业务组件中调用:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //router
        compile 'com.alibaba:arouter-api:1.2.1.1'
    }

    3、然后在每一个业务组件的 build.gradle 都引入ARouter 的 Annotation处理器,代码如下:

    
    android {
        defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
            arguments = [ moduleName : project.getName() ]
            }
        }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor 'com.alibaba:arouter-compiler:1.0.3'
    }

    4、由于ARouter支持自动注册到框架,所以我们不用像ActivityRouter那样在各个组件中声明组件,当然更不需要在Application中管理组件了。 我们给 Girls组件 中的 GirlsActivity 添加注解:@Route(path = “/girls/list”),需要注意的是这里的路径至少需要有两级,/xx/xx,之所以这样是因为ARouter使用了路径中第一段字符串(/*/)作为分组,比如像上面的”girls”,而分组这个概念就有点类似于ActivityRouter中的组件声明 @Module ,代码如下:

    @Route(path = "/girls/list")
    public class GirlsActivity extends BaseActionBarActivity {
    
        private GirlsView mView;
        private GirlsContract.Presenter mPresenter;
    
        @Override
        protected int setTitleId() {
            return R.string.girls_activity_title;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mView = new GirlsView(this);
            setContentView(mView);
            mPresenter = new GirlsPresenter(mView);
            mPresenter.start();
        }
    }

    然后我们就可以在项目中的任何一个地方通过 URL地址 : /girls/list, 调用 GirlsActivity,方法如下:

          ARouter.getInstance().build("/girls/list").navigation();

    8、结束语

    组件化相比于单一工程优势是显而易见的:

    1. 组件模式下可以加快编译速度,提高开发效率;
    2. 自由选择开发框架(MVC /MVP / MVVM /);
    3. 方便做单元测试;
    4. 代码架构更加清晰,降低项目的维护难度;
    5. 适合于团队开发;

    最后贴出Android组件化Demo地址:Android组件化项目AndroidModulePattern

    想要学习更多Android组件化知识,请查看 :Android组件化之终极方案


    感谢以下文章提供的帮助
    1. http://www.cnblogs.com/chenxibobo/p/6187954.html
    2. https://kymjs.com/code/2016/10/18/01/
    3. https://zhuanlan.zhihu.com/p/23388989
    4. https://zhuanlan.zhihu.com/p/23147164?refer=moduth

    感谢以下开源项目
    1. https://github.com/mzule/ActivityRouter
    2. https://github.com/alibaba/ARouter

    展开全文
  • Android 组件化架构-简谈

    千次阅读 2022-03-14 11:11:24
    了解组件化 在了解组件化之前,我们需要先了解模块化。 模块化就是将整体业务集合按照功能的不同,抽离到不同的模块中,这样做的好处就是在团队协助中能够较好的区分各自负责的功能模块、也能使得整个工程显得不是...
  • 一、模块化 与 组件化 模式控制、 二、applicationId 设置、 三、使用 sourceSets 配置组件模式下使用的清单文件、 四、组件模式 与 集成模式 切换示例、 五、完整的 Gradle 配置、 1、Project 层级的 build.gradle...
  • 项目发展到一定阶段时,随着需求的增加以及频繁地变更,项目会越来越大,代码变得越来越臃肿,耦合会越来越多,开发效率也会降低,这个时候我们就需要对旧项目进行重构即模块的拆分,官方的说法就是组件化组件化...
  • Android组件化通信(一)创建APT工程

    千次阅读 2022-01-04 12:34:59
    当我们项目进行组件化之后,那么难免会碰到一个问题,那就是组件之间的通信。例如下面的图,App里有两个组件(可以理解为两个Activity),视频录制组件(下面简称组件A)和视频播放组件(下面简称组件B)等,那么...
  • Android组件化

    千次阅读 2022-03-10 17:12:56
    Android组件化+composingBuild
  • Android组件化治理(一) 背景 随着项目的不断迭代与重构,项目的组件数量越来越多,这样会引发两个比较直接的问题: 编译速度慢 开发效率变低 编译速度 组件数量过大,开发体验不佳 添加新组建不方便 调试底层...
  • Android组件化二【跨Module调用方法】

    千次阅读 2021-05-27 06:07:19
    Android组件化二【跨Module调用方法】经过前一篇的文章,我们已经可以将Module单独运行了,这一篇的话,我们继续探索下组件化(其实是模块化)的内容。我们这次使用的是AppJoint的组件化方案,仍旧使用上篇文章的代码...
  • 0、没法做到代码权限管控,没法做到人员职责划分,每个人可以对任意的组件修改,显然会造成混乱 1、数据传递、方法调用:IProvider ,@Route 2、组件获取 App生命周期,有,但不灵活 3、获取fragment,@Route 方式二...
  • Android 组件化,从入门到不可自拔

    万次阅读 多人点赞 2019-04-19 02:54:33
    组件化能够显著提高Android项目开发效率,支持不同业务组件单独打包或者组合打包,可以说是Android开发者必备技能。 本文通过一个极其简单的实践案例,梳理了组件化的配置过程,并辅以全部源码,希望对还没有应用...
  • 讲这个方法之前,我默认大家都会 组件化 配置了,所以下面直接展示我测试的项目结构: 由于app和order/person都存在依赖关系,所以 app =》 MainActivity 跳转到 order =》OrderMainActivity 的方法如下: ...
  • Android 组件化跳转核心方法

    千次阅读 2022-03-09 15:34:00
    前名一个参数是应用程序的包名,后一个是这个应用程序的主Activity名 ... intent.setComponent(new ComponentName("com.droidnova.android.games.vortex", "com.droidnova.android.games.vortex.Vortex")); sta
  • Android组件化开发简单示例

    千次阅读 多人点赞 2020-11-21 15:01:52
    1、通过一个简单的android项目初始架构图来了解组件化,如下图: 打个生动的比喻,把我们的APP当成一个电脑主机,那么app外壳就是主机外壳,main组件就是主板,其他各个组件就类似于硬盘、网卡、显卡之类的东西,...
  • 记录Android组件化遇到的几个bug

    千次阅读 2022-04-20 23:04:38
    1,组件化的时候遇到找不到对应的BR 解决:业务组件中已经打开databinding开关,app壳工程中没有打开databinding开关导致找不到BR
  • Android组件化之组件通信

    千次阅读 2018-11-20 23:37:28
    本文是续上一篇Android组件化方案实践与思考文章一些思考,主要是针对组件间通信,比如: 每个组件如何初始化各自的数据 Activity间如何跳转、Fragment实例获取、如何调用普通类的函数 如何在一个组件中通知...
  • 文章目录 一、注解处理器 接收参数设置 二、注解处理器 生成路由表 Java 代码 三、博客资源 组件化系列博客 : 【Android 组件化】从模块化到组件化Android 组件化】使用 Gradle 实现组件化 ( Gradle 变量定义与...
  • 2、常规业务模块,该层的组件就是我们真正的业务组件了。我们通常按照功能模块来划分业务组件,例如注册登录、用户个人中心、APP的首页模块等。这里的每个业务组件都是一个小的APP,只需要修改一下对应的module的...
  • android组件化单独运行

    千次阅读 2021-11-09 17:22:56
    1、在 gradle.properties 中加上 isModule=true 2、在对应的 build.gradle 中加上 if(isModule.toBoolean()){ //表示可单独运行 apply plugin: 'com.android.application' ...3、在对应的 build.gradle 中的android{

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 182,162
精华内容 72,864
关键字:

android组件化