Android 组件化

但是,项目在发版的时候,个人中心这个模块一定是作为一个子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_的前缀。只是加了这个配置,如果你的命名不合法,编译器会给一个提示。

image.png

对于资源命名冲突这一块我们不仅需要关注布局文件,还有类似于color,anim, dimen等命名

组件间的通信


此处组件间的通信主要包括两方面的内容:一种是业务之间的通知消息,比如订单模块中,一个订单提交了,需要通知购物车刷新一个购物车中的商品列表,对于这种类型的消息通知,我们可以采用EventBus,RxBus这种消息总线来做。另一个通信则是组件间基础数据的打通。比如在订单模块中,用户下单时需要判断是否登录,从单一职责的原则上讲用户的登录信息是在个人中心模块中才有。那么个人中心模块如何向其它模块提供用户相关的数据呢?

还记得我们前面提到的commonmodule吗?它可以被其它的所有业务子模块依赖,于是我们在commonmodule中定义一个接口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的架构依赖

image.png

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();

}

}

再来从时间顺序上梳理一遍整个流程:

  1. 用户启动app, 首先执行AppattachBaseContext方法

  2. attachBaseContext方法中会调用AppStateManagerinit方法,该方法会把所有业务module的AppStateListener都加载进来

  3. ApponCreate方法中会调用AppStateManager类中的onCreate方法,最终会执行所有业务module里面AppStateListeneronCreate方法,也包括个人中心模块, 个人中心模块的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,这样便能做到全局依赖版本的统一。

以上简单总结了一下组件化的相关架构思路,我一直觉得组件化并不是一门新的技术,它更多的是一种项目的架构方法,采用这种方法可以更加方便的管理代码,使我们代码以一种按模块的方式整合起来,满足特定场景的需求。

  • 采用面向接口编程去掉组件间的相互依赖
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值