但是,项目在发版的时候,个人中心
这个模块一定是作为一个子module引进来的,PersonalMainActivity
绝对不可能是整个app的入口。为此我们需要定义两套不同的manifest文件,一套是当该组件做为app运行的时采用,另一套是当该组件做为module时采用。
我们在项目的根目录定义一个文件config.gradle
, 里面定义一个变量moduleAsApp
, 当该值为false
时,代表该项目中所有的业务子module都作为组件被主module依赖,当它为true
时,代表业务子module可作为独立app运行。然后在每一个业务子module中都引入该配置。
config.gradle
文件:
ext {
moduleAsApp = false;
appId = [
“app” : “com.fred.routerapp”,
“order” : “com.fred.order”,
“product” : “com.fred.product”,
“personal” : “com.fred.personal”
]
packageNameForAPT = appId.app + “.apt”;
androidConfig = [
compileSdkVersion: 29,
buildToolsVersion: “29.0.3”,
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1,
versionName : “1.0”
]
}
再回到我们的个人中心module里面来,对这两种情况分别采用不同的manifest文件,如下:
sourceSets {
main {
if (moduleAsApp) {
manifest.srcFile ‘src/main/debug/AndroidManifest.xml’
} else {
manifest.srcFile ‘src/main/AndroidManifest.xml’
java {
// release 时 debug 目录下文件不需要合并到主工程
exclude ‘/debug/’
}
}
}
}
对于module配置和applicationId
if (moduleAsApp) {
apply plugin: ‘com.android.application’
} else {
apply plugin: ‘com.android.library’
}
applicationId:
if (moduleAsApp) {
applicationId appId.personal
}
在组件化开发过程中,可能会存在资源名冲突的问题,假如在product模块中有一个price_detail.xml
用来显示价格相关的视图,在order
模块中也有一个price_detail.xml
,那便会出现资源名冲突。对于这种问题,可以统一命名方式,如加前缀,将product
模块中的这些资源命名全部加上product_
前缀,order
模块中全部加上order_
前缀,这样可以一定程序上避免。当然如果只是针对于布局xml文件可以在gradle文件中进行配置来约束
android {
…
resourcePrefix “personal”
…
}
这种方式并不是说在编译的时候会手动将你的文件名进行修改,加personal_
的前缀。只是加了这个配置,如果你的命名不合法,编译器会给一个提示。
对于资源命名冲突这一块我们不仅需要关注布局文件,还有类似于color,anim, dimen等命名
此处组件间的通信主要包括两方面的内容:一种是业务之间的通知消息,比如订单模块中,一个订单提交了,需要通知购物车刷新一个购物车中的商品列表,对于这种类型的消息通知,我们可以采用EventBus,RxBus这种消息总线来做。另一个通信则是组件间基础数据的打通。比如在订单模块中,用户下单时需要判断是否登录,从单一职责的原则上讲用户的登录信息是在个人中心模块中才有。那么个人中心模块如何向其它模块提供用户相关的数据呢?
还记得我们前面提到的common
module吗?它可以被其它的所有业务子模块依赖,于是我们在common
module中定义一个接口IAccountService
public interface IAccountService {
public boolean isLogin();
…
}
在个人中心模块有它的实现类
public class AccountServiceImpl implements IAccountService {
@Override
public boolean isLogin() {
return false;
}
}
在common
模块中有一个ServiceManager
类,这个类是一个服务的管理者,它会持有一个AccountService
, 如下:
public class ServiceManager {
private ServiceManager() {}
private IAccountService accountService;
private static class AppConfigurationHolder {
private static final ServiceManager instance = new ServiceManager();
}
public static ServiceManager getInstance() {
return AppConfigurationHolder.instance;
}
public void setAccountService(IAccountService as) {
this.accountService = as;
}
public IAccountService getAccountService() {
return this.accountService;
}
}
我们期望其它模块通过调用ServiceManager.getInstance().getAccountService()
便可以拿到用户信息相关的服务。如果要达到此目的,那这个AcccountService
在什么时候注入呢?我们在再看app的架构依赖
AccountService
的实现在个人中心module,我们不确定用户在什么场景会调用这个服务。对于这种类型的服务,需要在app启动的时候便注入到app中。而AccountService
只能在个人中心模块实例化,为此个人中心模块必须要能监听到应用的初始化时机,也就是Application的onCreate
方法
监听Application的状态
在common
组件中定义一个接口, 其它的业务module都会实现这个接口。
public interface AppStateListener {
void onCreate();
void onLowMemory();
}
在个人中心模块会有一个类, 在onCreate
中会new
一个AccountService
,并且注入到ServiceManager
中
public class PersonalAppStatusListener implements AppStateListener {
@Override
public void onCreate() {
ServiceManager.getInstance().setAccountService(new AccountServiceImpl(AppStateManager.getInstance().getApplication()));
}
@Override
public void onLowMemory() {
}
}
在common
模块中,会有一个类来维护所有的子业务module对Application状态的监听
public class AppStateManager {
private List lifeCycleListenerList;
private AppStateManager(){}
private Application application;
public void init(Context context) {
this.application = (Application) context;
initAppLifeCycleListener();
}
public void initAppLifeCycleListener() {
//定义所有模块的listener的类名
String [] names = Constants.moduleLifeCycleListener;
if (names != null && names.length > 0) {
lifeCycleListenerList = new ArrayList<>();
}
for (int i = 0; i < names.length; i ++) {
try {
Class clazz = Class.forName(names[i]);
if (AppStateListener.class.isAssignableFrom(clazz)) {
lifeCycleListenerList.add((AppStateListener) clazz.newInstance());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
public Application getApplication() {
return this.application;
}
public void onCreate() {
if (lifeCycleListenerList != null) {
for (AppStateListener listener : lifeCycleListenerList) {
listener.onCreate();
}
}
}
public void onLowMemory() {
if (lifeCycleListenerList != null) {
for (AppStateListener listener : lifeCycleListenerList) {
listener.onLowMemory();
}
}
}
private static class Instance{
public static AppStateManager INSTANCE = new AppStateManager();
}
public static AppStateManager getInstance() {
return AppStateManager.Instance.INSTANCE;
}
}
在Application中会在适当的时机调用AppStateManager
方法,将Application的状态分发给各子业务模块
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
AppStateManager.getInstance().onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AppStateManager.getInstance().init(this);
}
@Override
public void onLowMemory() {
super.onLowMemory();
AppStateManager.getInstance().onLowMemory();
}
}
再来从时间顺序上梳理一遍整个流程:
-
用户启动app, 首先执行
App
的attachBaseContext
方法 -
attachBaseContext
方法中会调用AppStateManager
的init
方法,该方法会把所有业务module的AppStateListener
都加载进来 -
App
的onCreate
方法中会调用AppStateManager
类中的onCreate
方法,最终会执行所有业务module里面AppStateListener
的onCreate
方法,也包括个人中心模块, 个人中心模块的AppStateListener
中的onCreate
方法会创建AccountService
, 并且将AccountService
注入到Servicemanager
中.
以上步骤完成后,各模块都可以使用ServiceManager.getInstance().getAccountService()
来拿到用户信息相关的数据。
由于业务模块之间是不相互依赖的。所以路由的配置只能加到common
模块中,在common
模块里面维护着一个大的Map,来管理特定的url与具体的Activity之间的映射。在app启动的时候,去初始化这个Map。具体思路可能是下面这个样子:
定义一个RouterManager
public class RouterManager {
private Map<String, String> map = new HashMap<>();
private RouterManager instance;
private RouterManager() {}
public static RouterManager getInstance() {
return InstanceHolder.instance;
}
public void put(String url, String className) {
map.put(url, className);
}
public void startActivity(Activity activity, String url, Intent intentData) {
try {
Intent intent = new Intent(activity, Class.forName(map.get(url)));
intent.putExtras(intentData);
activity.startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static class InstanceHolder {
public static RouterManager instance = new RouterManager();
}
}
添加映射关系
在上面的例子中我们有看到每个子业务模块会有一个监听器用来监听Application的onCreate
方法,我们可以在那个里面加个各自业务模块的页面与url之间的映射关系
public class PersonalAppStatusListener implements AppStateListener {
@Override
public void onCreate() {
…
RouterManager.getInstance().put(“personal/login”, “com.fred.personal.view.LoginActivity”);
RouterManager.getInstance().put(“personal/main”, “com.fred.personal.view.PersonalMainActivity”);
}
@Override
public void onLowMemory() {
}
}
使用
使用时我们可以这样调起个人中心的主页面
RouterManager.getInstance().startActivity(activity, “personal/main”, null);
以上只是简单说明了实现一个router的思路,当前这种方案的弊端显而易见,每次新增一个页面,还需要在自己手动添加到总的Map里面,这个总的Map需要自己来维护,很容易出,同时这种使用方式也不是很友好。这就是为什么需要一个类似于ARoute
这样的一个路由组件,后面我们讲一下如何自己实现一个路由组件。
混淆的配置在主module中,每个业务组件中都保留一份混淆配置文件不便于修改和管理。
虽然在做组件化,但是我们期望各个子module在开发环境的配置上能保持统一,比如compileSdk
, targetSdk
等参数以及第三方包的版本统一。于是我们在项目的根目录下定义一个config.gradle
文件,里面对这些信息进行配置
ext {
moduleAsApp = false;
appId = [
“app” : “com.fred.routerapp”,
“order” : “com.fred.order”,
“product” : “com.fred.product”,
“personal” : “com.fred.personal”
]
packageNameForAPT = appId.app + “.apt”;
android = [
compileSdkVersion: 28,
buildToolsVersion:‘29.0.2’,
minSdkVersion : 14,
targetSdkVersion : 26,
versionName : “2.0”,
versionCode : 20210510,
]
dependVersion = [
rxJava : “2.1.0”,
rxAndroid : ‘2.0.2’,
retrofitSdkVersion : ‘2.3.0’,
glideTrans : ‘4.1.0’,
glide : ‘4.9.0’,
room : ‘2.0.0’,
]
retrofitDeps = [
“retrofit” : “com.squareup.retrofit2:retrofit:${dependVersion[‘retrofitSdkVersion’]}”,
“retrofitConverterGson” : “com.squareup.retrofit2:converter-gson:${dependVersion.retrofitSdkVersion}”,
“retrofitAdapterRxjava2” : “com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofitSdkVersion}”
]
rxJavaDeps = [
“rxJava” : “io.reactivex.rxjava2:rxjava:${dependVersion.rxJava}”,
“rxAndroid”: “io.reactivex.rxjava2:rxandroid:${dependVersion.rxAndroid}”,
]
glideDeps = [
“glide” : “com.github.bumptech.glide:glide:${dependVersion.glide}”,
‘glideOKhttp’ : “com.github.bumptech.glide:okhttp3-integration:${dependVersion.glide}”,
“glideTrans” : “jp.wasabeef:glide-transformations:${dependVersion.glideTrans}”,
]
roomDeps = [
‘room-runtime’: “androidx.room:room-runtime:${dependVersion.room}”,
‘room-rxjava’: “androidx.room:room-rxjava2:${dependVersion.room}”,
“room-compiler”: “androidx.room:room-compiler:${dependVersion.room}”
]
kotlinDeps = [
“kotlin” : “org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version”,
“kotlin-coroutines” : “org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2”
]
compactDeps = [
“recyclerview” : ‘androidx.recyclerview:recyclerview:1.0.0’,
‘constraintlayout’ : ‘androidx.constraintlayout:constraintlayout:1.1.3’,
‘cardview’ :‘androidx.cardview:cardview:1.0.0’,
‘compact’ : ‘androidx.appcompat:appcompat:1.0.0’
]
commonDeps = [
‘multidex’ : ‘androidx.multidex:multidex:2.0.0’,
“okHttp” : ‘com.squareup.okhttp3:okhttp:3.12.0’,
‘eventBus’ : ‘org.greenrobot:eventbus:3.0.0’,
‘statusBar’ : ‘com.jaeger.statusbarutil:library:1.5.0’,
‘gson’ : ‘com.google.code.gson:gson:2.3.1’,
‘wechatShare’ : ‘com.tencent.mm.opensdk:wechat-sdk-android-without-mta:6.6.4’,
‘bugly’ : ‘com.tencent.bugly:crashreport:3.2.3’
]
retrofitLibs = retrofitDeps.values()
rxJavaLibs = rxJavaDeps.values()
glideLibs = glideDeps.values()
roomLibs = roomDeps.values()
kotlinLibs = kotlinDeps.values()
compactLibs = compactDeps.values()
commonLibs = commonDeps.values()
}
在项目的build.gradle
中引入apply from "config.gradle"
, 然后在需要引入的各子业务module的build.gradle
中加入对应的需要个入的依赖如 api rxJavaLibs
,这样便能做到全局依赖版本的统一。
以上简单总结了一下组件化的相关架构思路,我一直觉得组件化并不是一门新的技术,它更多的是一种项目的架构方法,采用这种方法可以更加方便的管理代码,使我们代码以一种按模块的方式整合起来,满足特定场景的需求。
- 采用面向接口编程去掉组件间的相互依赖