安卓组件化的搭建和基本功能的实现

  • 1.组件化是什么?
  • 1.1 了解组件化:
  • 1.2 组件化的基本结构:
  • 1.3 组件化的优点:
  • 2.组件化框架的搭建:
  • 2.1 第一步:搭建基础层
  • 2.1.1 创建config.gradle
  • 2.1.2 建立一个library模块作为基础层
  • 2.1.3 所有模块都要中都要添加基础层模块的依赖
  • 2.2 第二步:搭建组件层。
  • 2.2.1 为组件层模块创建不同的Manifest表单
  • 2.3 第三步:搭建app层
  • 2.4 检验搭建成果
  • 3. 组件间的activity等界面跳转
  • 3.1 ARouter介绍
  • 3.2 使用ARouter的准备
  • 3.3 ARouter的使用
  • 4. 组件之间的数据传递之(接口+实现)
  • 4.1 (接口+实现)的大概思路
  • 4.2 (接口+实现)的实操
  • 5. 组件之间的数据传递之(EventBus)
  • 5.1 什么是EventBus?
  • 5.2 使用EventBus的准备工作
  • 5.3 EventBus的基本使用
  • 5.3.1 订阅事件
  • 5.3.2 发布事件
  • 5.4 EventBus的进阶使用
  • 5.4.1 EventBus的线程模式
  • 5.4.2 EventBus的黏性事件
  • 6.Applicaion的动态配置
  • 6.1 建立一个BaseApplication
  • 6.2 修改主Module的application类
  • 6.3 修改Login组件的application类
  • 6.4 运用反射获取其他组件的application进行初始化
  • 7.主项目使用各个组件的类的方法
  • 7.1 反射的方法
  • 7.2 接口+实现的方法
  • 7.3 路由的实现方法


1.组件化是什么?

1.1 了解组件化:

  • 在没有接触组件化之前,我们写安卓项目的过程中,所有布局,逻辑功能的都是在app模块下实现。这种方式写一些小项目是很方便的,但是有些真正有用处的手机软件都是由团队来协作完成的,由于功能的繁杂,如果多个开发人员都在app一个模块中进行功能开发,代码就会显得很臃肿,所以为了更好的更有效率的分工,组件化功能的实现必不可少。

1.2 组件化的基本结构:

  • 组件化大致分为3个结构:基础层组件层应用层。下面我们具体说一说着三个结构。
  1. 基础层:基础层,就是由我们手动在这里去包含所有项目中想用到的基本库的依赖,然后在其他所有组件中依赖于这个Base基础层,就可以在项目的任何地方应用到我们添加的依赖。这样不仅只需要开发一套依赖库的代码,还解耦了基础功能和业务功能的耦合,在基础库变更时更加容易操作。
  2. 组件层:组件层可以理解为业务层,比如一个聊天软件项目,其中分为登录,聊天,查询等功能,组件层就是把这些功能进行一个一个的分离出来,每一个功能对应一个特定的组件。
  3. 应用层:应用层就是app,在未接触组件化之前,我们所有的逻辑,布局都在app里面写,而组件化中,app可以形容为一个空壳,它其中依赖包含了我们创建的其他组件,它会根据设定按照需要引用不同的模块。

1.3 组件化的优点:

  • 经过刚刚的介绍,我们对组件化有了大致的认识。想必他的有点我们也清楚了,就是方便多人开发,互不影响,自己开发的组件可以单独运行,不受外界限制,不至于因为其他模块出现问题而导致自己模块不能运行的情况。另外,在一个很庞大的项目需要修改时,也可以很方便的找到要修改的模块,不必要去在庞大的代码中寻找。

2.组件化框架的搭建:

  • 在说搭建组件化之前,先介绍一下AndroidStudio开发Android项目时常用到的两种插件。
  • application插件:如果一个模块被声明为aoolication,那么它会成为一个apk文件,是可以直接安装运行的项目。
  • library插件:被声明为library的模块,它会成为一个aar文件,不可以单独运行。

2.1 第一步:搭建基础层

2.1.1 创建config.gradle

组件架构 组件搭建_组件化

  • 在这里面,我们写上所有依赖库,以及项目中sdk等等的版本号,然后直接让buil.gradle去apply它,之后有什么更改就可以直接在这里面改,比如版本号升级等问题。以下是我在其中添加的代码:
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()
}
  • 在这里我写了一些版本号,support库,路由,图片加载等常用的基础库,最后面的这4行,是对前面的一个封装,在其他地方调用这4行,相当于调用了我在这里写的所有代码。这里的具体知识请看使用config.gradle统一管理项目的依赖库
  • 之后在项目目录下的build.gradle中apply操作。

2.1.2 建立一个library模块作为基础层

  1. 点击file->new->new module,选择library module模块。我在这里命名为Baselibs。
  2. 在模块的build.gradle中,添加我们刚刚写的依赖,注意这里运用关键字api来添加,因为这样做,别的模块继承当前模块时,不必再单独写依赖了。
dependencies {

    api rootProject.ext.androidxLibs
    api rootProject.ext.commonLibs
    api rootProject.ext.annotationLibs
    testImplementation 'junit:junit:4.+'
    api 'org.greenrobot:eventbus:3.1.1'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2.1.3 所有模块都要中都要添加基础层模块的依赖

  • 这里以app层为例:在app模块中的build.gradle中添加如下。
dependencies {
    implementation project(":Baselibs")
}

就这样,基础层就建立完成了。

2.2 第二步:搭建组件层。

  • 由于组件层根据集成开发和组件开发两种不同的情况,要在application和library之间来回切换。所以我们还是先创建一个模块,这里我建立一个login模块,就在login的build.gradle中,我们要动态的规定是application还是library。
  • 由于项目目录下的gradle.properties中的变量,可以在build.gradle中引用,所以在gradle.preperties中设置一个isModule变量:
# true时为组件化模式开发,false时为集成模式开发
isModule = false
  • 然后在组件中的build.gradle中根据isModule的值来进行设置:
if(isModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}
  • 不要忘记添加基础库的依赖:
dependencies {
    implementation project(":Baselibs")
 }

2.2.1 为组件层模块创建不同的Manifest表单

  • 因为一个项目中只能有一个application,所以我们要在组件开发时,令login模块应用存在application的表单,在集成开发时,令login模块应用不存在application的Manifest表单。
  1. 在login/src/main目录下创建module文件,在这里面创建一个Manifest表单,书写集成开发情况下的表单。
  • login/src/main/module目录下的:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="activitytest.com.example.login">

    <application
        android:allowBackup="true">
        <activity android:name=".Login"></activity>
    </application>

</manifest>
  • login/src/main目录下的:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="activitytest.com.example.login">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ModuleTest">
        <activity android:name=".Login">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 这里的区别就不说了。
  1. 一个项目里只有一个application,那么也就是说一个项目中也只有一个applicationId,applicationId和表单的选择都是在build.gradle中设置的,所以我们还要根据isModule的值,来设置组件中是否存在applicationId,以及应用哪一个Maniest.xml。
defaultConfig {

    if(isModule.toBoolean()){
        applicationId "activitytest.com.example.login"
    }
 }
sourceSets{
    main{
        if(isModule.toBoolean()){
            manifest.srcFile 'src/main/AndroidManifest.xml'
        }else{
            manifest.srcFile 'src/main/module/AndroidManifest.xml'
        }
    }
}
  • 按照如上所述方法,我又创建了一个share组件和一个mine组件,之后的内容会用到。

2.3 第三步:搭建app层

  • 这一步就比较简单,只需要在app模块的build.gradle中根据isModule的值来决定是否添加其他组件层的模块就好了。
dependencies {
    implementation project(":Baselibs")

    if(!isModule.toBoolean()){
        implementation project(":login")
        implementation project(":share")
        implementation project(":mine")
    }
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2.4 检验搭建成果

  • 当isModule的值设置为false时,代表此时为集成开发,只有app组件可以运行,其他子业务组件都不可以单独运行。
  • 组件架构 组件搭建_组件化_02

  • 当isModule的值为true时,代表此时为组件开发,所有组件都可以单独运行,这时当你运行每一个组件的时候,主页面会显示你运行组件的页面。
  • 组件架构 组件搭建_android_03

  • 注:每次修改isModule的值和修改build.gradle时,都要点击一下sync。
  • 这样一个简单的组件化框架就搭建完了。但是组件之间也要有通信交流的功能,这些知识请往下看。

3. 组件间的activity等界面跳转
  • 在我们没有应用组件化开发之前,界面跳转我们主要用显式Intent和隐式Intent两种方法。但是组件化开发中,是不允许组件层的模块横向依赖的,所以不可以直接访问彼此的类,就不能用显式Intent来跳转,而用隐式Intent跳转还需要通过 AndroidManifest 集中管理,在协作开发就比较麻烦。所以在这里介绍一个界面跳转的新方法,使用 Alibaba 开源的 ARouter 来实现。

3.1 ARouter介绍

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

3.2 使用ARouter的准备

  1. 要使用 ARouter 进行界面跳转,需要我们的组件对 Arouter 添加依赖,因为所有的组件都依赖了基础层Baselibs模块,Baselibs模块又依赖了我们写的config.gradle,所以我们在config.gradle模块中添加 ARouter 的依赖即可。
  2. 组件架构 组件搭建_组件架构_04


  3. 除了添加ARouter的依赖,我们还要添加注释处理器依赖,这个不同于ARouter依赖,这个需要在每一个应用的组件中都添加一次,不仅添加这个,还要在每个应用的组件的build.gradle的defaultConfig下配置ARouter。
dependencies {
    implementation project(":Baselibs")
    annotationProcessor rootProject.ext.annotationLibs		//注释处理器

    if(!isModule.toBoolean()){
        implementation project(":login")
        implementation project(":share")
        implementation project(":mine")
    }
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
defaultConfig {
    javaCompileOptions{				//配置ARouter
        annotationProcessorOptions{
            includeCompileClasspath false
            arguments = [AROUTER_MODULE_NAME:project.getName()]
        }
    }
}
  1. 在使用之前,我们还要在app层的application中初始化ARouter,为了让项目刚刚启动就初始化,所以我们在application中的oncreate方法中初始化。
public class MainApplication extends BaseApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        //ARouter后台有ILogger接口,定义了一些输出日志
        if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
            ARouter.openLog();     // 打印日志
            ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(this); // 尽可能早,推荐在Application中初始化ARouter

        init(this);
        initover(this);
    }

    private boolean isDebug() {
        return BuildConfig.DEBUG;
    }
 }
  • 这样我们就准备好了ARouter的配置,接下来就可以开始用了。

3.3 ARouter的使用

  1. 在我们即将要跳转的activity,service或者fragment等等上面声明添加注解@Route(path = “/xx/xx”),这里path是一个跳转的路径,/xx/xx是一个二级目录。注意:不可以出现路径相同的情况,不同的组件第一级目录不可以相同,同一组件的一级目录可以相同。
  2. 接着在想要跳转的时候,写下面这一行代码就可以跳转对应路径的活动ARouter.getInstance().build("/xx/xx").navigation();build里面填的是path地址,后面还可以添加后缀withInteger,withString等等传递数据,然后再navigation就可以跳转了。
  • 这里我们在app层设置两个跳转按钮,在login和share组件中声明注解,接受跳转。
login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/login/login1").navigation();
    }
});
share.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/share/share1").navigation();
    }
});
@Route(path = "/login/login1")
public class Login extends AppCompatActivity {

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

    }
}


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

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

运行结果:

组件架构 组件搭建_组件化_05

  • 点击跳转到登陆
  • 组件架构 组件搭建_xml_06

  • 点击跳转到分享
  • 组件架构 组件搭建_xml_07

4. 组件之间的数据传递之(接口+实现)
  • 由于主项目与组件,组件与组件之间都是不可以直接使用类的相互引用来进行数据传递的。组件间数据交互还有很多其他的方式,比如 EventBus,广播,数据持久化等方式,但是往往这些方式的交互会不那么直观,所以对通过 Service 这种形式可以实现的交互,我们最好通过这接口+实现这种方式进行。

4.1 (接口+实现)的大概思路

  • 比如这里我们在share组件中要获取到登录的相关信息:由于所有组件都依赖Baselibs组件,所以在Baselibs组件中创建一个接口和ServiceFactroy,然后在login组件中创建类实现这个接口,然后把这个类上传到ServiceFactroy,然后在share组件中从ServiceFactroy中获取这个实现类对象。

4.2 (接口+实现)的实操

  1. 创建接口供login组件实现:
public interface LoginService {
     boolean isLogin();
     String getPassword();
}
  1. 创建ServiceFactroy来管理login组件传递上来的接口实现类,并设置get,set方法:
public class ServiceFactory {
    private LoginService loginService;
    private ServiceFactory(){

    }
    public static ServiceFactory getInstance(){
        return Inner.serviceFactory;
    }
    private static class Inner{
        private static ServiceFactory serviceFactory = new ServiceFactory();
    }

    public void setLoginService(LoginService loginService){
        this.loginService = loginService;
    }
    public LoginService getLoginService(){
        if(loginService == null){
            return new EmptyService();
        }else{
            return loginService;
        }
    }
}

这里通过静态内部类方式实现 ServiceFactory 的单例,由于ServiceFactroy中包含着登录信息这种重要的唯一的信息,所以全局只可以有一个ServiceFactroy对象,所以要实现它的单例创建。

  1. 由于login组件可能并没传递过来一个实现类,share就调用get方法,为了防止异常,我们还要创建一个服务的空实现,当login并未上传实现类时,get返回这个空实现。
public class EmptyService implements LoginService{
    @Override
    public boolean isLogin() {
        return false;
    }

    @Override
    public String getPassword() {
        return null;
    }
}
  1. 接下来就是在login组件中,实现这个LoginService接口,并在activity中点击登录按钮后,会存储登陆状态和用户信息,并上传到这个接口实现类,再将这个接口实现类上传到ServiceFactroy。这里我们还创建了一个LoginUtil类作为一个传递桥梁来存储这些数据。
  • 首先是实现接口:
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;
    }
}
  • 存储数据的工具类:
public class LoginUtil {
    static boolean isLogin = false;
    static String password = null;
}
  • 在activity中点击登录按钮的实现操作:
login = (Button)findViewById(R.id.login_text);
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));
    }
});
  • 因为我们并不知道什么时候其他的组件就会在ServiceFactroy中获得这个类,所以我们上传这个接口实现类要在项目刚刚开始的时候,所以就在login的application中实现上传。
public class LoginApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
     }
}
  1. 最后一步就是在share组件中获取登录信息了。这里我们在分享组件中添加一个分享按钮,点击后,会根据登录信息的是否登录来提示你分享是否成功。
share = (Button)findViewById(R.id.share_text);
share.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(ServiceFactory.getInstance().getLoginService().isLogin()){
            Toast.makeText(Share.this,"分享成功!",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(Share.this,"分享失败,请先登录!",Toast.LENGTH_SHORT).show();
        }
    }
});

注意: 在这里出现了一个问题,就是我们想在项目最开始运行的时候,就要在login登陆组件中上传一个实现类到ServiceFactroy中,并且将这个命令写到了login组件的application中,但是一个项目只可以有一个application,也就是说Login组件中的application不会初始化,所以我们就涉及到了一个Application的动态配置。下面目录6中会说如何操作。

5. 组件之间的数据传递之(EventBus)

5.1 什么是EventBus?

  • EventBus是一种用于Android的发布/订阅事件总线。它有很多优点:简化应用组件间的通信;解耦事件的发送者和接收者;避免复杂和容易出错的依赖和生命周期的问题;很快,专门为高性能优化过等等。
  • EventBus采取的是订阅者/发布者的模式,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。

5.2 使用EventBus的准备工作

  1. 要使用EventBus,第一项准备工作就是添加EventBus的依赖。我们这里直接在Baselibs中添加。
dependencies {
    api 'org.greenrobot:eventbus:3.1.1'
}
  1. 刚才说过,EventBus采用的是订阅者/发布者模式,也就是说,我们要先建立一个事件,然后再想发送相关信息的组件创建发布者,然后在要接收信息的组件创建订阅者并且获取信息。所以第二步就是创建事件,随便创建一个类,在其中写出要传递的信息,在加入get/set方法即可。
public class EventMessage {

    String account;

    public EventMessage(String account) {
        this.account = account;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

由于这个事件可能在每个组件都会发布或接收,为了让其他所有组件都可以获取到这个类的实例,所以就直接在Baselibs组件中main/java下创建一个文件EventBus,在其中写定义的事件类。

组件架构 组件搭建_android_08

  • 准备工作就这些就可以了。

5.3 EventBus的基本使用

5.3.1 订阅事件

  1. 首先我们要在准备订阅事件的组件中注册订阅者。具体就是在activity的onCreate中注册,在onDestroy中注销。调用EventBus.getDefault().register()和EventBus.getDefault().unregister()方法。
  • 这里我又建立了一个Mine模块,和分享模块类似,只不过这里展示用户的个人信息,为了区分EventBus和接口+实现的两种之间的区别。所以我们在Mine模块中注册。
@Route(path = "/mine/mine1")
public class Mine extends AppCompatActivity {

    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mine);
        textView = (TextView)findViewById(R.id.message);
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}
  1. 接下来订阅者需要定义事件处理方法(也称为订阅者方法)。当发布对应类型的事件时,该方法将被调用。EventBus使用 @Subscribe 注解来定义订阅者方法。方法名可以是任意合法的方法名,参数类型为订阅事件的类型。
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void showEventMessage(EventMessage message){
    textView.setText(message.getAccount());
}

至于threadMode和sticky我们一会单独说。

5.3.2 发布事件

  1. 在需要的地方发布事件,所有订阅了该类型事件并已注册的订阅者将在任何时候收到该事件。这里在login组件中运用了EventBus.getDefault().post()方法来发布一个事件。
login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LoginUtil.isLogin = true;
        LoginUtil.password = "admin";
        EventBus.getDefault().post(new EventMessage(LoginUtil.password));           //发送EventBus
    }
});
  • 到这里EventBus的简单使用就说完了。接下来是Eventbus的几个进阶用法。

5.4 EventBus的进阶使用

5.4.1 EventBus的线程模式

  • 在刚才创建订阅方法时,注释中出现了threadMode,这就是线程模式。EventBus支持订阅者方法在不同于发布事件所在线程的线程中被调用。你可以使用线程模式来指定调用订阅者方法的线程。EventBus总共支持5种线程模式:
  1. ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。这是 默认的线程模式。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。这种线程模式意味着最少的性能开销,因为它避免了线程的切换。因此,对于不要求是主线程并且耗时很短的简单任务推荐使用该模式。使用该模式的订阅者方法应该快速返回,以避免阻塞发布事件的线程,这可能是主线程。
  2. ThreadMode.MAIN订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。如果发布事件的线程是主线程,那么该模式的订阅者方法将被直接调用。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
  3. ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。因此,可以在该模式的订阅者方法中直接更新UI界面。事件将先进入队列然后才发送给订阅者,所以发布事件的调用将立即返回。这使得事件的处理保持严格的串行顺序。使用该模式的订阅者方法必须快速返回,以避免阻塞主线程。
  4. ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。如果发布事件的线程不是主线程,那么订阅者方法将直接在该线程中被调用。如果发布事件的线程是主线程,那么将使用一个单独的后台线程,该线程将按顺序发送所有的事件。使用该模式的订阅者方法应该快速返回,以避免阻塞后台线程。
  5. ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。因此,发布事件的调用将立即返回。如果订阅者方法的执行需要一些时间,例如网络访问,那么就应该使用该模式。避免触发大量的长时间运行的订阅者方法,以限制并发线程的数量。EventBus使用了一个线程池来有效地重用已经完成调用订阅者方法的线程。

5.4.2 EventBus的黏性事件

  • 在EventBus中,如果先发布了事件,然后有订阅者订阅了该事件,那么除非再次发布该事件,否则订阅者将永远接收不到该事件。此时,可以使用粘性事件。发布一个粘性事件之后,EventBus将在内存中缓存该粘性事件。当有订阅者订阅了该粘性事件,订阅者将接收到该事件。
  1. 黏性事件的订阅方法的注释上要写上sticky = true,然后发布黏性事件也有所不同,发布黏性事件用的不是EventBus.getDefault().post()方法,而是EventBus.getDefault().postSticky()方法。
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)		//订阅
public void showEventMessage(EventMessage message){
}

EventBus.getDefault().postSticky(new EventMessage(LoginUtil.password));           //发送黏性EventBus
  • 正好我们这个项目要符合最开始就获取到登录信息,所以要发布一个黏性事件才合适。
6.Applicaion的动态配置
  • 在上面我们说的组件之间信息相互传递的问题中,采用接口+实现的方法传递时,我们想让项目初始化时,在Login组件中发送出接口实现类,但是在一个项目中只有一个application,Login组件的application是不会初始化的。所以这时候login组件就不会发送接口实现类。
  • 解决方法:将组件的类强引用到主 Module 的 Application 中进行初始化,这就必须要求主模块可以直接访问组件中的类。而我们又不想在开发过程中主模块能访问组件中的类,这里可以通过反射来实现组件 Application 的初始化。也就是Application的动态配置。
  • ==思路:==建立一个BaseApplication继承Application,然后令其他的application类都继承自BaseApplication,在BaseApplication中写两个方法,一个是初始化,一个是初始化之后调用的函数,然后在app主模块里面利用反射,得到Login中的application类,然后调用其初始化方法即可。

6.1 建立一个BaseApplication

  • 由于Bselibs组件是其他组件都依赖的,并且要求继承BaseApplication的类必须重写方法,所以我们在Baselibs中创建抽象类BaseApplication继承Application。如下:
public abstract class BaseApplication extends Application {
    public abstract void init(Application application);         //初始当前组件调用的方法
    public abstract void initover(Application application);               //其他需要调用的方法
}

6.2 修改主Module的application类

  • 直接上代码
public class MainApplication extends BaseApplication {

    @Override
    public void onCreate() {
        super.onCreate();
        //ARouter后台有ILogger接口,定义了一些输出日志
        if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
            ARouter.openLog();     // 打印日志
            ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(this); // 尽可能早,推荐在Application中初始化ARouter

        init(this);
        initover(this);
    }

    private boolean isDebug() {
        return BuildConfig.DEBUG;
    }


    @Override
    public void init(Application application) {...}

    @Override
    public void initover(Application application) {...}
}

6.3 修改Login组件的application类

  • 直接代码
public class LoginApplication extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        init(this);
        initover(this);
    }


    @Override
    public void init(Application application) {
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
    }

    @Override
    public void initover(Application application) {

    }
}
  • 在这里我们完成了application的配置,接下来我们就可以用反射的方式在app主Module的application类中获取Login组件的application类,执行其方法。

6.4 运用反射获取其他组件的application进行初始化

  • 在app模块中的application中重写的两个方法,获取并进行初始化。
@Override
public void init(Application application) {
    for(String moduleApp : AppConfig.moduleApps){
        try{
            Class clazz = Class.forName(moduleApp);
            BaseApplication baseApplication = null;
            baseApplication = (BaseApplication) clazz.newInstance();
            baseApplication.init(this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

@Override
public void initover(Application application) {
    for(String moduleApp : AppConfig.moduleApps){
        try{
            Class clazz = Class.forName(moduleApp);
            BaseApplication baseApplication = null;
            baseApplication = (BaseApplication) clazz.newInstance();
            baseApplication.initover(this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • 这样操作之后,我们在项目开始的时候,Login组件就会发送出一个接口实现类到ServiceFactroy了。
7.主项目使用各个组件的类的方法
  • 这里介绍三个方法,一个是反射,一个是通过接口+实现的方法,另外一个就是路由的方法。

7.1 反射的方法

  • 和刚刚说的获取Login组件的application类是一样的,只不过这次我们利用反射获取碎片实例,将碎片加载到activity中,然后activity再将它放到合适的位置。

7.2 接口+实现的方法

  • 和之前的也一样,在ServiceFactroy中在创建一个碎片对象,然后添加一个构造函数,参数是碎片,在提供一个get和set方法就可以了。
  • 不要忘记,在Baselibs中在添加一个空实现,以免get方法调用时出现错误。

7.3 路由的实现方法

  • 简单明了一行代码。
mineFragment = (Fragment) ARouter.getInstance().build("/mine/fragment").navigation();
  • 就这样轻轻松松的获取到了组件碎片的实例。