模块化和组件化的区别:
模块化,以业务维度,组件化以功能维度,业务的维度很多情况下会有多个模块相互调用,耦合在一起,可以理解为模块化其实是包含了多个组件化(功能点)来实现的,组件化是以更小的维度去处理的,以单个功能点去完成,比如登陆,或者咚咚聊天,分享,支付等。
组件化开发过程中需要解决的六大问题:
1,独立运行和开发,提升开发效率,比如京麦咚咚的人员就全部在这个模块上开发
2,数据在组件之间传递,可以理解为组件之间的方法相互调用,调用的同时也是支持,数据传递。
3,组件之间的跳转,Arouter 阿里开源路由项目
4,主项目不直接访问组件中具体类的情况下,如何获取组件中 Fragment 的实例并将组件中的 Fragment 实例添加到主项目的界面中。
5,组件开发完成后相互之间的集成调试如何实现?还有就是在集成调试阶段,依赖多个组件进行开发时,如果实现只依赖部分组件时可以编译通过?这样也会降低编译时间,提升效率
6,解耦的目标以及如何实现代码隔离?其实简单的来说怎么样做到完全隔离,不留一点耦合性,组件在不集成进入的时候也不会出现奔溃,完全的隔绝模块对组件中类的使用会使解耦更加彻底。
7,资源文件
8,主工程怎么样访问组件的类
第一步: 怎么样做到开发时 用于运行apk,集成时用于当成 aar文件输出
Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来配置不同的工程。
- App 插件,id: com.android.application 输出一个 APK 安装包
- Library 插件,id: com.android.libraay 构建后输出 aar 包
- Test 插件,id: com.android.test 配置测试
a,gradle.properties 配置文件 每个module 目录下都可以进行配置 isRunAlone = true 说明他是aar 还是 apk
isRunAlone=false (gradle.properties )
if (isRunAlone.toBoolean()) { (build.gradle)
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
以上这两个文件的配置。
b, 动态配置组件的 ApplicationId 和 AndroidManifest 文件 (build.gradle)同一个module下的文件
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml' //这个里面没
}
}
}
defaultConfig {
if (isRunAlone.toBoolean()) {
// 单独调试时添加 applicationId ,集成调试时移除
applicationId "com.loong.login"
}
...
}
文件不一样的地方就是没有这个配置还有 applicationid 这个也需要在build.gradle 文件中说明
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
第二步:组件之间数据怎么样做到传递以及方法的相互调用。
框架思路
https://mmbiz.qpic.cn/mmbiz_jpg/zKFJDM5V3WwINADplKGiaqesicBqSy0icdricrQ1H6Ij3P6RLXtXoJnPxDzSJGVN0MpNsflcHicAJ6tt8ib7xLAOFtdg/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1
简单思路:定义一个ComponentBase 模块(library),被其他组件所依赖,这个lib定义了,每个组件可以对外提供访问的抽象方法,比如login组件加以实现,ServiceFactory 工厂中将自身的实现设置到里面去(setservice方法),之后其他组件再调用getservice的方法。(前提是在编译的时候要求,按照顺序加载,这个时候就用到了反射机制里,对每个类进行调用加载,比如base组件-》login 组件- 》 share组件,这个就可以保证share拿到了login组件的方法
当然,ServiceFactory 中也会提供所有的 Service 的空实现,在组件单独调试或部分集成调试时避免出现由于实现类对象为空引起的空指针异常。
a, ComponentBase 模块创建过程
File -> New -> New Module - > Android Library(Phone & Tablet Module 是用来创建组件的)
<~~&~~>/Users/mahaisheng/Library/Application Support/jd/mahaisheng/image/74002D3D-73DE-4D3C-B3E0-D59CBEAA9465.jpg</~~&~~>
// IAccountService
public interface IAccountService {
/**
* 是否已经登录
* @return
*/
boolean isLogin();
/**
* 获取登录用户的 AccountId
* @return
*/
String getAccountId();
}
// EmptyAccountService
public class EmptyAccountService implements IAccountService {
@Override
public boolean isLogin() {
return false;
}
@Override
public String getAccountId() {
return null;
}
}
// ServiceFacoty
public class ServiceFactory {
private IAccountService accountService;
/**
* 禁止外部创建 ServiceFactory 对象
*/
private ServiceFactory() {
}
/**
* 通过静态内部类方式实现 ServiceFactory 的单例
*/
public static ServiceFactory getInstance() {
return Inner.serviceFactory;
}
private static class Inner {
private static ServiceFactory serviceFactory = new ServiceFactory();
}
/**
* 接收 Login 组件实现的 Service 实例
*/
public void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 返回 Login 组件的 Service 实例
*/
public IAccountService getAccountService() {
if (accountService == null) {
accountService = new EmptyAccountService();
}
return accountService;
}
}
b,login组件和 share组件开始建立依赖compbase 组件
1 ,componentbase 定义好后, login开始实现sevice接口,并且把自身设置进入(注册),之后share组件调用对应的servicefactory 工厂的进行调用
// Base 模块的 build.gradle
dependencies {
api project (':componentbase')
...
}
// login 组件的 build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project (':base')
}
// login 组件中的 IAccountService 实现类
public class AccountService implements IAccountService {
@Override
public boolean isLogin() {
return AccountUtils.userInfo != null;
}
@Override
public String getAccountId() {
return AccountUtils.userInfo == null ? null : AccountUtils.userInfo.getAccountId();
}
}
// login 组件中的 Aplication 类
public class LoginApp extends BaseApp {
@Override
public void onCreate() {
super.onCreate();
// 将 AccountService 类的实例注册到 ServiceFactory
ServiceFactory.getInstance().setAccountService(new AccountService());
}
}
上面说明了一点就是 BaseApp需要被调用,否则根本就注册不了,(这就用到最开始说的反射的机制去调用)
第三步: Share 组件与 Login 组件实现数据传递
// Share 组件的 buidl.gradle
dependencies {
implementation project (':base')
...
}
// Share 组件的 ShareActivity
public class ShareActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share);
share();
}
private void share() {
if(ServiceFactory.getInstance().getAccountService().isLogin()) {
Toast.makeText(this, "分享成功", Toast.LENGTH_SHORT);
} else {
Toast.makeText(this, "分享失败:用户未登录", Toast.LENGTH_SHORT);
}
}
}
其实就是直接调用就可以了,其实就是一个基于接口的编程方式,用接口来做通信,像mvp的那种方式
第五步: 加载各个组件application里面要初始化的内容,因为application 只会执行主application 组件application是不会执行的
所以在主application执行的时候要去调用反射的机制去动态执行组件的application的方法,做一些初始化,
我们组件的 Service 在 ServiceFactory 的注册又必须放到组件初始化的地方。这个时候就做到实现
处理方案:
1,写一个baseapp 继承 application ,同时里面定义 一些需要初始化的方法,
2,组件的application都实现base里面的抽象方法
3, appconfig 配置需要反射的组件application
4,在mainapplication方法里面调用 init 进行初始化操作
// Base 模块中定义
public abstract class BaseApp extends Application {
/**
* Application 初始化
*/
public abstract void initModuleApp(Application application);
/**
* 所有 Application 初始化后的自定义操作
*/
public abstract void initModuleData(Application application);
}
第五步: 路由实现:
要使用 ARouter 进行界面跳转,需要我们的组件对 Arouter 添加依赖,因为所有的组件都依赖了 Base 模块,所以我们在 Base 模块中添加 ARouter 的依赖即可。其它组件共同依赖的库也最好都放到 Base 中统一依赖。
这里需要注意的是,arouter-compiler 的依赖需要所有使用到 ARouter 的模块和组件中都单独添加,不然无法在 apt 中生成索引文件,也就无法跳转成功。并且在每一个使用到 ARouter 的模块和组件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。
// Base 模块的 build.gradle
dependencies {
api 'com.alibaba:arouter-api:1.3.1'
// arouter-compiler 的注解依赖需要所有使用 ARouter 的 module 都添加依赖
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}
// 所有使用到 ARouter 的组件和模块的 build.gradle
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
...
implementation project (':base')
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}
// 主项目的 build.gradle 需要添加对 login 组件和 share 组件的依赖
dependencies {
// ... 其他
implementation project(':login')
implementation project(':share')
}
添加了对 ARouter 的依赖后,还需要在项目的 Application 中将 ARouter 初始化,我们这里将 ARouter 的初始化工作放到主项目 Application 的 onCreate 方法中,在应用启动的同时将 ARouter 初始化。
// 主项目的 Application
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化 ARouter
if (isDebug()) {
// 这两行必须写在init之前,否则这些配置在init过程中将无效
// 打印日志
ARouter.openLog();
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug();
}
// 初始化 ARouter
ARouter.init(this);
// 其他操作 ...
}
private boolean isDebug() {
return BuildConfig.DEBUG;
}
// 其他代码 ...
}
这里我们以主项目跳登录界面,然后登录界面登录成功后跳分享组件的分享界面为例。其中分享功能还使用了我们上面提到的调用登录组件的 Service 对登录状态进行判断。
首先,需要在登录和分享组件中分别添加 LoginActivity 和 ShareActivity ,然后分别为两个 Activity 添加注解 Route,其中 path 是跳转的路径,这里的路径需要注意的是至少需要有两级,/xx/xx
Login 组件的 LoginActivity:
@Route(path = "/account/login")
public class LoginActivity extends AppCompatActivity {
private TextView tvState;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
updateLoginState();
}
private void initView() {
tvState = (TextView) findViewById(R.id.tv_login_state);
}
public void login(View view) {
AccountUtils.userInfo = new UserInfo("10086", "Admin");
updateLoginState();
}
private void updateLoginState() {
tvState.setText("这里是登录界面:" + (AccountUtils.userInfo == null ? "未登录" : AccountUtils.userInfo.getUserName()));
}
public void exit(View view) {
AccountUtils.userInfo = null;
updateLoginState();
}
public void loginShare(View view) {
ARouter.getInstance().build("/share/share").withString("share_content", "分享数据到微博").navigation();
}
}
Share 组件的 ShareActivity:
@Route(path = "/share/share")
public class ShareActivity extends AppCompatActivity {
private TextView tvState;
private Button btnLogin, btnExit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
updateLoginState();
}
private void initView() {
tvState = (TextView) findViewById(R.id.tv_login_state);
}
public void login(View view) {
AccountUtils.userInfo = new UserInfo("10086", "Admin");
updateLoginState();
}
public void exit(View view) {
AccountUtils.userInfo = null;
updateLoginState();
}
public void loginShare(View view) {
ARouter.getInstance().build("/share/share").withString("share_content", "分享数据到微博").navigation();
}
private void updateLoginState() {
tvState.setText("这里是登录界面:" + (AccountUtils.userInfo == null ? "未登录" : AccountUtils.userInfo.getUserName()));
}
}
然后在 MainActivity 中通过 ARouter 跳转,其中build 处填的是 path 地址,withXXX 处填的是 Activity 跳转时携带的参数的 key 和 value,navigation 就是发射了路由跳转。
// 主项目的 MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 跳登录界面
* @param view
*/
public void login(View view){
ARouter.getInstance().build("/account/login").navigation();
}
/**
* 跳分享界面
* @param view
*/
public void share(View view){
ARouter.getInstance().build("/share/share").withString("share_content", "分享数据到微博").navigation();
}
}
如果研究过 ARouter 源码的同学可能知道,ARouter拥有自身的编译时注解框架,其跳转功能是通过编译时生成的辅助类完成的,最终的实现实际上还是调用了 startActivity。
第七步:
// componentbase 模块的 IAccountService
public interface IAccountService {
// 其他代码 ...
/**
* 创建 UserFragment
* @param activity
* @param containerId
* @param manager
* @param bundle
* @param tag
* @return
*/
Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag);
}
// Login 组件中的 AccountService
public class AccountService implements IAccountService {
// 其他代码 ...
@Override
public Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag) {
FragmentTransaction transaction = manager.beginTransaction();
// 创建 UserFragment 实例,并添加到 Activity 中
Fragment userFragment = new UserFragment();
transaction.add(containerId, userFragment, tag);
transaction.commit();
return userFragment;
}
}
// 主模块的 FragmentActivity
public class FragmentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
// 通过组件提供的 Service 实现 Fragment 的实例化
ServiceFactory.getInstance().getAccountService().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, "");
}
}
8,怎么样解决资源文件还有独立开发
a, 我们将主项目中对 Login 组件和 Share 组件的依赖方式修改为 runtimeOnly 的方式就可以解决开发阶段可以直接引用到组件中类的问题。
// 主项目的 build.gradle
dependencies {
// 其他依赖 ...
runtimeOnly project(':login')
runtimeOnly project(':share')
}
b,resourcePrefix ,其 xml 中定义的资源没有以 resourcePrefix 的值作为前缀,在对应的 xml 中定义的资源会报红,图片资源需要自己加前缀,命名规则按照 "组件名_"
// Login 组件的 build.gradle
android {
resourcePrefix "login_"
// 其他配置 ...
}