项目地址 https://github.com/mqzhangw/JIMU
当时项目最初运行报错
java.lang.IllegalAccessError: tried to access method org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.
android studio 版本3.4.1中采用3.3版本的gradle 编译成功
#distributionUrl=https://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip
增加图片显示后app的运行效果
JIMU有两个工程jimu-core和jimu-sample-project
jimu-core 是jimu 核心功能库,有路由支持、注解、gradle构建优化,分为以下模块router-annotation 、router-anno-compiler 、componentlib 、build-gradle
jimu-sample-project 演示jimu使用
app是主项目,负责集成众多组件,控制组件的生命周期
reader和share是我们拆分的两个组件
componentservice中定义了所有的组件提供的服务
basicres定义了全局通用的theme和color等公共资源
basiclib中是公共的基础库,一些第三方的库(okhttp等)也统一交给basiclib来引入
为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix “xxx_”,从而固定每个组件的资源前缀。
Android Studio中,是可以看到多个application工程的,随便点击一个都是可以独立运行的,并且可以根据配置引入其他需要依赖的组件。
这背后的工作都由com.dd.comgradle插件来默默完成。以下是单独运行时的启动页面
readercomponent
com.luojilab.reader/com.luojilab.reader.runalone.ReaderTestActivity
sharecomponent
com.luojilab.share/com.luojilab.share.BookShareEditActivity
app
com.luojilab.androidcomponent/com.luojilab.componentdemo.MainActivity
在这里组件的交互专指组件之间的数据传输,在我们的方案中使用的是接口+实现的方式,组件之间完全面向接口编程。
在demo中我们让reader提供一个fragment给app使用来说明。首先reader组件在componentservice中定义自己的服务
public interface ReadBookService {
Fragment getReadBookFragment();
}
然后在自己的组件工程中,提供具体的实现类ReadBookServiceImpl:
public class ReadBookServiceImpl implements ReadBookService {
@Override
public Fragment getReadBookFragment() {
return new ReaderFragment();
}
}
提供了具体的实现类之后,需要在组件加载的时候把实现类注册到Router中,具体的代码在ReaderAppLike中,ReaderAppLike相当于组件的application类,这里定义了onCreate和onStop两个生命周期方法,对应组件的加载和卸载。
public class ReaderAppLike implements IApplicationLike {
Router router = Router.getInstance();
@Override
public void onCreate() {
router.addService(ReadBookService.class.getSimpleName(), new ReadBookServiceImpl());
}
@Override
public void onStop() {
router.removeService(ReadBookService.class.getSimpleName());
}
}
在app中如何使用如reader组件提供的ReaderFragment呢?注意此处app是看不到组件的任何实现类的,它只能看到componentservice中定义的ReadBookService,所以只能面向ReadBookService来编程。具体的实例代码如下:
Router router = Router.getInstance();
if (router.getService(ReadBookService.class.getSimpleName()) != null) {
ReadBookService service = (ReadBookService) router.getService(ReadBookService.class.getSimpleName());
fragment = service.getReadBookFragment();
ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.tab_content, fragment).commitAllowingStateLoss();
}
这里需要注意的是由于组件是可以动态加载和卸载的,因此在使用ReadBookService的需要进行判空处理。我们看到数据的传输是通过一个中央路由Router来实现的,这个Router的实现其实很简单,其本质就是一个HashMap,具体代码大家参见源码。
通过上面几个步骤就可以轻松实现组件之间的交互,由于是面向接口,所以组件之间是完全解耦的。至于如何让组件之间在编译阶段不不可见,是通过com.dd.comgradle实现的
3、UI跳转页面(activity)的跳转也是通过一个中央路由UIRouter来实现。
master-arouter分支采用ARouter
Android路由框架ARouter
https://github.com/alibaba/ARouter
android studio 当前项目配置默认可以集成调试
android studio Build → Generate Signed APK release版本正常生成
至于依赖的组件是如何集成到host中的,其本质还是直接使用compile project(…)或者compile modulePackage:module@aar。那么为啥不直接在build.gradle中直接引入呢,而要经过com.dd.comgradle这个插件来进行诸多复杂的操作?
那就是组件之间的完全隔离,也可以称之为代码边界。如果我们直接compile组件,那么组件的所有实现类就完全暴露出来了,
使用方就可以直接引入实现类来编程,从而绕过了面向接口编程的约束。这样就完全失去了解耦的效果了,可谓前功尽弃。
那么如何解决这个问题呢?我们的解决方式还是从分析task入手,只有在assemble任务的时候才进行compile引入.具体的代码实现在jimu-core 中build-gradle
在什么时机加载组件以及如何加载组件?目前com.dd.comgradle提供了两种方式,字节码插入和反射调用。
字节码插入模式是在dex生成之前,扫描所有的ApplicationLike类(其有一个共同的父类),然后通过javassist在主项目的Application.onCreate()中插入调用ApplicationLike.onCreate()的代码。这样就相当于每个组件在application启动的时候就加载起来了。
反射调用的方式是手动在Application.onCreate()中或者在其他合适的时机手动通过反射的方式来调用ApplicationLike.onCreate()。之所以提供这种方式原因有两个:对代码进行扫描和插入会增加编译的时间,特别在debug的时候会影响效率,并且这种模式对Instant Run支持不好;另一个原因是可以更灵活的控制加载或者卸载时机。
这两种模式的配置是通过配置com.dd.comgradle的Extension来实现的,下面是字节码插入的模式下的配置格式,添加applicationName的目的是加快定位Application的速度。
combuild {
applicationName = 'com.mrzhang.component.application.AppApplication'
isRegisterCompoAuto = true
}
demo中也给出了通过反射来加载和卸载组件的实例,在APP的首页有两个按钮,一个是加载分享组件,另一个是卸载分享组件,在运行时可以任意的点击按钮从而加载或卸载组件,具体效果大家可以运行demo查看。
参考: