在App功能逐渐冗杂的今天,衍生出很多优化方案,组件化作为其中一种方式也被广泛应用。抛开那些第三方,手撸一次实现组件化的通信,也更利于学习第三方的开源框架,下面来一步一步实现。
一、准备工作
1. 首先我在创建完project后,除了默认创建的app model,另外创建了两个model:LoginComponent和MineComponent。(component就是组件的意思,相信来到这里的朋友都是了解过组件化原理和一些相关术语,这里就不再赘述)
2. 在创建完两个单独的component后,创建一个Base Library,因为相互通信的需要。
3. 创建后的目录就是这样的:
二、配置依赖环境和分离运行需求
这里的配置环境就是在gradle里做一些相关操作,主要有如下操作:
- 统一每个model之间的sdk相关配置;
- 将model的两种运行方式进行分离,也就是分开处理,如果有资源文件也同样需要分开处理
- 分离的内容通常包括如下几种:
- apply运行方式的分离
- applicationId的分离
以上两种都是在component gradle里操作- app model引用library model的分离
以上一种是在app model里操作- AndroidManifest(清单文件)的分离
- 将所有model都对baseLibrary依赖
以上分离的目的是区分application和library两种运行方式所对应的需求,
而依赖的目的就是互相通信。
1. 在app的gradle.properties里,配置如下(如果有其他需求一样可以增添):
#全局配置gradle环境
compile_sdk_version = 28
min_sdk_version = 23
target_sdk_version = 28
support_sdk_version = 28.0.0
constraint_sdk_version = 1.1.3
#配置单个model是否可独立运行
loginRunAlone = false
mineRunAlone = false
2. 在引用如上配置。
以下是引用配置和分离的截图:
除library model和主app model(也就是创建项目时自动创建的那个model)不需要进行application和library的判断,因为第一个始终以library运行;第二个始终以application运行。其他的component皆需考虑是否以单独的application运行的情况。
对上面文字清晰地解释一下吧。下面三种颜色,代表的在gradle里三种需要配置的事务:
红色框:apply的运行方式的分离;
绿色框:sdk版本的统一配置;
黄色框:applicationId的分离,component以application运行需要ID;
其中红色框和黄色框部分只需要在component里配置,library和app里都不需要;
绿色框的内容则试用于所有gradle,能配置sdk的地方都可以。
3. app model引用library model的分离
在不以单独application运行的情况下,把component作为library依赖且运行。
4. AndroidManifest的分离
首先很明确,library和application的清单文件内容是不需要的,除了保存必有的包名(package)和四大组件的注册信息。
首先,切换project目录,因为在Android目录下看不到的。
然后,给所有component创建另外一个目录的AndroidManifest文件,我这里在main/下创建的文件夹和文件,跟Java文件夹同级,也可以创建其他目录,后续加载路径。
(下图中带main下的是自动创建的,作用于application运行;manifest下的是刚才我创建的,作用于library运行)
接下来,来到作用于library运行的清单文件,干掉用不着的只剩包名和四大组件注册信息(我这里只有activity示例),如下图:
来,最后一步--------------分开加载清单文件
来到所有component的gradle里如下配置:
当然注意的是,判断运行状态是根据component的,因为gradle对groovy好像不太友好,没有提示,记得改一下。顺便提一下,maniFest不要写成了mainFest,相信大家不会犯我这种低级错误。当然如果你文件名一致无所谓。
5. 各个model对baseLibrary的依赖
只至此呢,所有的配置都搞完了。下面需要搭建交互逻辑!
三、搭建model间交互逻辑
1. 来到baseLibrary下,新建接口IBaseAppComponent,写入方法initialize(Application application),不知道不要紧,后面就知道干啥了。
public interface IBaseAppComponent {
void initialize(Application application);
}
2. 让app和另外两个component的自定义application都实现IBaseAppComponent 接口。
// MainAppcation
public class MainAppcation extends Application implements IBaseAppComponent {
private static MainAppcation app;
public static MainAppcation getInstance(){ return app;}
@Override
public void onCreate() {
super.onCreate();
initialize(this);
}
@Override
public void initialize(Application application) {
app = (MainAppcation) application;// 全局统一的上下文
}
在component的onCreate方法里手动调用,则是考虑到app model单独运行时需要调用。
// LoginApplication
public class LoginApplication extends Application implements IBaseAppComponent {
private static Application app;
public static Application getInstance(){return app;}
@Override
public void onCreate() {
super.onCreate();
initialize(this);
}
@Override
public void initialize(Application application) {
app = application;
}
}
- 回到MainAppcation的initialize方法里,因为有了一层实现关系,我们可以在这个方法里通过library的接口实现调用component内的方法,并传递MainApplication的context。
@Override
public void initialize(Application application) {
app = (MainAppcation) application;
try {
for (String app : AppComponontConfig.COMPONENTS) {
// COMPONENTS是一个AppComponontConfig类里的静态字符串数组----->
// ,包含了所有component的包名
// 用包名反射,判断是否实现了IBaseAPPComponent
Object aClass = Class.forName(app).newInstance();
if (aClass instanceof IBaseAppComponent) {
// 确保component实现了此接口
((IBaseAppComponent) aClass).initialize(this);// ---->
// 这里主app的在整体运行时需要提供全局统一的上下文
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 继续在library model里,创建ServiceFactory类, 用来管理model间交互的引用对象实例。
public class ServiceFactory {
private static final ServiceFactory instance = new ServiceFactory();
public static ServiceFactory getInstance() {
return instance;
}
// 为组件化接口提供get及set方法
private I_LoginService mLoginService;
private I_MineService mMineService;
public I_LoginService getLoginService() {
// 避免在移除组件后报错。这里不返回null,返回一个空实现类
if (mLoginService == null) {
mLoginService = new EmptyLoginServiceImpl();
}
return mLoginService;
}
public void setLoginService(I_LoginService loginService) {
mLoginService = loginService;
}
public I_MineService getMineService() {
if (mMineService == null) {
mMineService = new EmptyMineServiceImpl();
}
return mMineService;
}
public void setMineService(I_MineService mineService) {
mMineService = mineService;
}
}
- 在component里创建业务类LoginServiceImpl(这里只拿其中一个component举例,另外的同理)实现I_LoginService接口。
public class LoginServiceImpl implements I_LoginService {
@Override
public void launch(Context contex) {
// 每个组件都提供一个launch方法用来启动该组件
}
}
- 在component的application里(这里同样拿其中一个举例)绑定传递对象
@Override
public void initialize(Application application) {
app = application;
ServiceFactory.getInstance().setLoginService(new LoginServiceImpl());
}
那么我们的准备也就到此结束了,下面我将准备实现两个功能,以测试是否成功和效果。
四、实现交互逻辑
终于来到这里了对吧!
先说下我要实现的功能:
- main跳转至mine界面
- mine再跳转至login界面
- login再调用mine组件中的方法展示一个fragment
那好,开整!
1. 来到login组件的实现类,前面代码里提到过我们为每个组件提供了一个launch方法,这个方法用来其他组件启动本组件。里面自然做了启动的操作。
public class LoginServiceImpl implements I_LoginService {
@Override
public void launch(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
}
2. 来到Mine组件的业务实现类,同样提供给外部组件的启动方法和login组件显示fragment的方法
public class MineServiceImpl implements I_MineService {
/**
* 提供外部组件启动方法
* @param context context
*/
@Override
public void launch(Context context) {
Intent intent = new Intent(context, MineActivity.class);
context.startActivity(intent);
}
/**
* 显示fragment的方法
*/
@Override
public Fragment showFragment(FragmentManager manager, int viewId, Bundle bundle) {
TestFragment testFragment = new TestFragment();
testFragment.setArguments(bundle);
return testFragment;
}
3. 在Mine model中的Fragment页面,这里我们把login组件中调用时传的内容展示出来 。
public class TestFragment extends Fragment {
private View mView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.fragment_test, null);
return mView;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
((TextView) mView.findViewById(R.id.tv_fr)).setText(arguments.getString("string"));
}
}
4. 来到app model的Activity,我们先写好第一个的测试代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ServiceFactory.getInstance().getMineService().launch(MainActivity.this);
}
});
}
5. 再来到Mine model的Activity,搞定第二个测试
public class MineActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mine);
findViewById(R.id.tv_go_to_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 跳转到Login界面
ServiceFactory.getInstance().getLoginService().launch(MineActivity.this);
}
});
}
}
6. Login页第三个测试,都是些没什么技术含量的代码应该没同学看不懂。
public class LoginServiceImpl implements I_LoginService {
@Override
public void launch(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
}
OK,跑一个
成功地进行了三个页面的跳转,并在一个组件中调用另一个组件的方法,展示了一个组件携带到另一个组件的数据。
总结四个自己挖的坑
- MainApplication忘记在清单文件配置name,其他两个组件因为是反射调用的,所以没有进行name配置
- 判空方法写在了setLoginService里
- 在gradle里加载AndroidManiFest,写成了AndroidMainFest
- 修改了两个组件的application类名,忘记在AppComponontConfig.COMPONENTS修改,导致instanceOf始终走了异常
另外提醒一下,在组件和app model里资源名字一样时,组件里的activity加载的是app model里的资源,需要加上标识。
五、小结一下
组件化在最初的实现是比较繁琐的一件事,特别是在搭建项目通信环境,即使在使用第三方的情况下。所以,在开发过程中依据业务特性进行项目架构选择,分析出各种优缺点才是最好的。