一直以来,插件化,组件化,模块化开发一直都是大家容易弄混淆的东西,而其中让人最不易分清的是插件化与组件化,那么首先从简单说起,模块化,他很好理解,他就像是我们平时开发的工具类一样,比如说,一个网络请求库就可以说是一个模块,这么说,相信大家就都懂了,再来说说两个最易混淆的。从规模上来说,他们一个是apk,一个是module,从通信角度看,一个是进程内通信(组件化)一个是进程间通信(插件化),至于他们之间的优缺点以及其他特点这里就不过多的赘述了,因为本文的重点在组件化。
首先新建一个工程,然后创建两个module加上一个主module(app),
在这里我们可以看到这个项目的大体构成,一个主app,一个baselibrary,一个login的module,他们之间的关系是,app与longin都依赖baselibrary,app动态依赖login(当login为组件时被app依赖,如果不是组件时,login单独运行,此时app不依赖他)。为了实现这样一个整体效果,我们首先要进行项目之间的配置,而且这些配置是必不可少的。
这个配置我选择是在gradle.properties中进行的,当然了,这个配置大多数童鞋还是喜欢单独写一个config.gradle来配置的,这个无伤大雅,根据自己的习惯来就好,这些配置中只说明其中一个配置条件,那就是这个singleModule,这个变量贯穿整个项目,当他设置为true的时候,login就是一个单独的app,他可以自己单独调试,可以单独运行,而且此时主app还不会依赖他,当他为false的时候,那么login这个module此时就是一个组件了,主app会依赖他,而且login此时也变成了library了,而不再是一个application了。
接下来进行每个module来进行配置了,我们先来看看login 的gradle配置,
在这里,我们重点关注三个红色框框里面的内容,第一个框框里面配置的是该module的性质,当singleModule为true 的时候,他是一个application,当他为false 的时候,他是一个library,也就是一个组件。第二个框框跟第一个框框一个意思,就是当login为组件的时候,他是不会有applicaionID的,第三个框框,他是为login这个module动态设置manifest文件的,当他为application的时候,manifest的文件在默认的位置,当他是一个组件的时候,他是在manifest这个文件加下的(manofest文件夹是自己创建的,大家也可以是其他的文件名称)。那么大家肯定好奇当他是组件的时候这个manifest文件里面的东西是什么了。那么我就带大家来看一看,其实没多大区别,
大家可以对比下正常的manifest文件缺了哪些东西了。
接下来再看看主app里面的配置了,
我们还是重点看这个框框里面的内容了,当login是组件的时候,我们就依赖他,而且此时我们依赖的方式是用runtimeOnly,具体解释上面也写了,大家也可以自行百度再看看他的解释。
到这里,我们在gradle中的所有的配置操作都已经完成了,现在我们要正式进入组件撸码环节了,在此之前,我先说说我们要达到的目的:通过之前设置的singleModule配置信息来动态控制login与app之间的依赖关系,并且他们之间是完全解耦的,也就是说,如果我们想让他们产生依赖关系就让singleModule的值为false,否则为true,而且在app依赖login的时候,主app中没有任何一点login中的代码痕迹(app中没有任何improt login中的代码),这样才能达到我们组件化的目的。
要想达到这个目的,那么肯定需要一个中间者了,很显然,在这个demo工程中,这个中间者就是这个library了,我们先来看看这个library的结构:
在这个library中就这么几个类,但是可以实现我们想要的效果,我们一个个来看,首先是ILoginService这个接口,
public interface ILoginService {
void launch(Context context,String targetName);
}
这个接口里面暂时只写了一个启动Activity的方法,在正式项目中,大家根据需求自行添加方法。很显然,我们是要实现这个接口的,根据上面的需求,我们要在主app中启动login,但是又不能跟login中的代码产生关联,所以,我们在login中去实现这个接口,
public class LoginService implements ILoginService {
@Override
public void launch(Context context,String targetName) {
Intent intent = new Intent(context,LoginActivity.class);
intent.putExtra(Constant.COMPONENTNAME,targetName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
在这个实现类中,我们看到在login这个模块中启动了LoginActivity了,这个肯定没问题了,至于里面的启动参数啥的,大家根据需求自行添加。至此,启动类有了,启动方法也有了,那么怎么样跟主App产生联系呢?在demo中,我的做法是首先创建一个单例ServiceFactory,我们看看该类的代码:
public class ServiceFactory {
private static final ServiceFactory ourInstance = new ServiceFactory();
public static ServiceFactory getInstance() {
return ourInstance;
}
private ServiceFactory() {
}
private ILoginService loginService;
public ILoginService getLoginService() {
if(loginService == null){
loginService = new EmptyService();
}
return loginService;
}
public void setLoginService(ILoginService loginService) {
this.loginService = loginService;
}
}
在这个类中,我们看到有一个ILoginService 的引用,然后设置了他的getter,setter方法,目的就是方便app拿到他们,而且这个实例类的作用远不止于此,因为这只是我们一个登陆模块,如果还有其他的service,依然可以设置在这里面,那么这样的话,主app想要调用的类都在这里面了,我们看到在getLoginService方法中对loginService做了一个判空的判断,目的是如果当login模块是单独运行的时候,那么此时主app是不会依赖login的,那么获取到的loginService肯定是空的,在这里返回一个EmptyService就是防止空指针,因为这个时候主app点击登录的时候是不会有反应的,不会崩溃(这种情况可以根据实际项目做对应的处理),现在获取,设置loginservice的类,方法都有了,那么在哪里设置呢,而且这个设置是要在一个组件启动的时候就要设置进去了,很明显,我们是要在组件启动最早的时候去启动它了,那么就是组件的Application中去操作了,但是这里又要跟前面的需求挂钩了,我们在主app中不能有login的任何代码,那么此时我们就在library中新增加一个接口了,然后让主App的Application与组件的Application都去实现它,这样的话就可以做到最早初始化了,所以对于login组件的Application,他的代码是这样的:
public class App extends Application implements IModuleComponent{
@Override
public void onCreate() {
super.onCreate();
initial(this);
}
@Override
public void initial(Application application) {
ServiceFactory.getInstance().setLoginService(new LoginService());
}
}
初始化之后,那么主App如果去获取这个App呢,他们之间既然没有任何关系,但是又想拿到他,那么怎么做呢?相信大家都想到了,那就是反射了,我们看看主App的Application:
public class App extends Application implements IModuleComponent{
@Override
public void onCreate() {
super.onCreate();
initial(this);
}
@Override
public void initial(Application application) {
for(String component: AppConfig.COMPONENTS){
try {
Class<?> clazz = Class.forName(component);
Object obj = clazz.newInstance();
if(obj instanceof IModuleComponent){
((IModuleComponent)obj).initial(this);
}
} catch (Exception e) {
Log.e("molin", "loginApp can't be found");
}
}
}
}
通过这个反射,我们就可以获取到login组件的Application了,那么这样的话,loginService此时不就设置到了ServiceFactory中去了吗,至于这个AppConfig类,里面只是记录我们需要反射的组件全名称。他的代码如下:
public class AppConfig {
public static String[] COMPONENTS = {"lml.molin.com.login.App"};
}
至此,我们整个流程就已经全部完成,现在就是见证成果的时候了:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import lml.molin.com.baselibrary.ServiceFactory;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void login(View view) {
ServiceFactory.getInstance().getLoginService().launch(this,MainActivity.class.getName());
}
}
这是我们的主App的MainActivity类,login方法是一个按钮点击方法,我们发现,在这整个类中,我们没有任何有关Login组件的代码。
但是我们在这个组件化开发的过程中我们还要注意一些问题,也是特别要注意的,就是资源名称一致的问题,特别是layout的资源问题,名称一定不能相同,否则调用的时候就会出异常了。至此,组件化整个开发流程已经完成,希望可以帮助到大家,谢谢!!!