应用模板代码地址:https://github.com/thfhongfeng/AndroidAppTemplate
项目架构和路由介绍
Android项目架构主要目的就是实现业务的模块化,使之成为可插拔可配置的组件:
业务模块化:这点Gradle的模块化编译实际上已经实现。
模块间的通信:模块化后,模块间的通信成为重点。这个有很多现成的库帮我们实现了,比如Arouter。
Arouter的路由功能的实现主要分三个阶段:
编译阶段:Arouter在编译阶段通过注解处理器将标记的通信类收集起来生成处理这些信息的类文件(类似ARouter$$Group$$XXX类文件),并放在指定的包中(com.alibaba.android.arouter.routes )。
初始化阶段:Arouter初始化时通过扫描指定的包(com.alibaba.android.arouter.routes ),将里面的类进行初始化。然后调用指定的方法(loadInto)将通信类放到几个静态的Map中。
使用阶段:在使用过程中通过标记在Map中找到对应的通信类了,然后就是实例化这些通信类,根据通信类的类别做出相应的处理和返回相应的结果了。
而笔者应用模板的架构正是基于Arouter。
Arouter的路由功能虽然很强大,但仍然对业务代码有入侵。为了尽量减少对业务代码的入侵,应用模板搭架了一个路由模块router。router通信模块是一个通信规范化的模块,Arouter只是通信的一种实现方式。
router模块的目录结构:
应用模板模块间通信的主要分两块(新增模块时模块间通信的搭建):
router模块的command集:新增一个模块时,如果此模块需要给其它模块提供模块间服务,就需要在router模块中添加该模块提供给其他模块使用的服务命令集RouterXxxCommand。
业务模块的提供的remote通信服务:新增一个模块时,如果此模块需要给其它模块提供模块间服务,就需要在本模块中添加提供具体服务的类XxxRemoteService和服务协议类XxxArouterService。XxxRouterClient为本模块调用其它模块的统一服务出口类。
以上两块都是通过注解的方式来实现的,下面进行解析说明。
本应用架构模块间通信的原理
从使用出发一步一步解析
上面已经展示了Login模块的模块间通信设施的搭架。那么我们来看看Welcome模块是如何使用Login模块提供的服务的。
Welcome模块在合适的位置会通过调用WelcomeRouterClient里的autoLogin尝试自动登录。
public class WelcomeRouterClient {
public static void callCommand(Context context, String bundleKey,
String command, Bundle args, IRouterCallback callback) {
RouterManager.getInstance(bundleKey).callUiCommand(context,
command, args, callback);
}
public static void autoLogin(Context context, Bundle args, IRouterCallback callback) {
RouterManager.getInstance(ConfigKey.BUNDLE_LOGIN_KEY).callOpCommand(context,
RouterLoginCommand.autoLogin, args, callback);
}
public static void goMainHomeActivity(Context context, Bundle args, IRouterCallback callback) {
RouterManager.getInstance(ConfigKey.BUNDLE_MAIN_KEY).callUiCommand(context,
RouterMainCommand.goMainHomeActivity, args, callback);
}
}
autoLogin方法先根据Key获取RouterManager实例。看看如何获取实例的
public class RouterManager {
public static IRouterManager getInstance(String bundleKey) {
switch (BuildConfig.APP_THIRD_ROUTER_PROVIDER) {
case "arouter":
return ARouterManager.getInstance(bundleKey);
default:
return ARouterManager.getInstance(bundleKey);
}
}
}
RouterManager的规划是为了让模块间通信的第三方工具(如Arouter)可插拔,即也可以用其它的三方模块间通信库来实现。目前只实现了使用Arouter的方式,所以都会去调用ArouterMananger的getInstance方法。
public class ARouterManager implements IRouterManager {
private final String TAG = LogUtils.makeLogTag(this.getClass());
private static volatile List mClassNameList = new ArrayList<>();
private static volatile HashMap mInstanceMap = new HashMap<>();
private String mBundleKey = "";
private String mRemoteAction = "";
static {
try {
mClassNameList = AndroidClassUtils.getFileNameByPackageName(AppUtils.getApplicationContext(),
"com.pine.router.command");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ARouterManager(@NonNull String bundleKey) {
mBundleKey = bundleKey;
for (int i = 0; i < mClassNameList.size(); i++) {
try {
Class> clazz = Class.forName(mClassNameList.get(i));
ARouterRemoteAction remoteAction = clazz.getAnnotation(ARouterRemoteAction.class);
if (remoteAction != null) {
if (mBundleKey.equals(remoteAction.Key())) {
mRemoteAction = remoteAction.RemoteAction();
break;
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public static ARouterManager getInstance(@NonNull String bundleKey) {
if (mInstanceMap.get(bundleKey) == null) {
synchronized (ARouterManager.class) {
if (mInstanceMap.get(bundleKey) == null) {
mInstanceMap.put(bundleKey, new ARouterManager(bundleKey));
}
}
}
return mInstanceMap.get(bundleKey);
}
……
……
public void callCommand(final String commandType, final Context context, String commandName,
Bundle args, final IRouterCallback callback) {
if (!checkBundleValidity(commandType, context, callback)) {
return;
}
ARouterBundleRemote routerService = ((ARouterBundleRemote) ARouter.getInstance().build(mRemoteAction)
.navigation(context, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onFound");
}
@Override
public void onLost(Postcard postcard) {
LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onLost");
if (callback != null && !callback.onFail(IRouterManager.FAIL_CODE_LOST, "onLost")) {
onCommandFail(commandType, context, IRouterManager.FAIL_CODE_LOST, "onLost");
}
}
@Override
public void onArrival(Postcard postcard) {
LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onArrival");
}
@Override
public void onInterrupt(Postcard postcard) {
LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onInterrupt");
if (callback != null && !callback.onFail(IRouterManager.FAIL_CODE_INTERRUPT, "onInterrupt")) {
onCommandFail(commandType, context, IRouterManager.FAIL_CODE_INTERRUPT, "onInterrupt");
}
}
}));
if (routerService != null) {
routerService.call(context, commandName, args, callback);
}
}
}
ARouterManager类承载了主要的实现方法。可以看到在类初始化的时候会去查找“com.pine.router.command”包下的所有类,而在构造方法中会遍历这些类,找到有注解@ARouterRemoteAction的类,并根据Key(模块标记)值,找到对应的RemoteAction并保存起来。
看一下RouterLoginCommand这个类
@ARouterRemoteAction(Key = ConfigKey.BUNDLE_LOGIN_KEY, RemoteAction = "/login/service")
public interface RouterLoginCommand {
String goLoginActivity = "goLoginActivity";
String autoLogin = "autoLogin";
String logout = "logout";
}
因此,这个getInstance方法实际上就是获取Key值对应的ARouterManager对象,这个对象在初始化的时候保存RemoteAction到mRemoteAction成员变量中。实际上这个mRemoteAction就是Arouter的@Route注解的path值。
在Welcome模块中获取到RouterManager对象后调用的callXxxCommand,实际上最后都会走到RouterManager的callCommand方法。
callCommand方法就是我们熟悉的Arouter的使用了,而这个mRemoteAction对应的@Route注解的类就是Login模块的LoginARouterRemote。
@Route(path = "/login/service")
public class LoginARouterRemote extends ARouterBundleRemote {
}
这个类是一个空类,通过@Route注解让Arouter找到它,在其父类ARouterBundleRemote中通过泛型找到实际的服务实现类LoginRemoteService。
最后调用ARouterBundleRemote的call方法,call方法中通过@RouterCommand注解找到对应的服务方法,调用该方法实现模块间的调用服务。
本应用模板之所以这样路由的原因,主要有两个:
1. 实现第三方路由库的可插拔
2. 进一步解耦业务代码和路由代码
搭架好业务模块的路由功能后,往后的服务接口的添加只需要两步:
1. 在router模块中的RouterXxxCommand中添加一个表示该服务接口名的常量yyy
2. 在业务模块中的XxxRemoteService中编写实现方法,并添加注解@RouterCommand,CommandName值为第一步中添加的常量
其它模块中调用该服务接口:
RouterManager.getInstance(ConfigKey.BUNDLE_XXX_KEY).callOpCommand(context,
RouterXxxCommand.yyy, args, callback);
2019-08-26变更:
router模块的command集移到了base模块中,通过调用RouterManager的init方法,传入command集所在的包名来初始化。从而将command集与router模块分离,使得router模块成为方法库,在开发过程中不再需要频繁更改。