布景
在项目的开发过程中,跟着参加人员的增多以及功用的增加,假如没有运用合理的开发架构,代码会越来越臃肿,耦合越来越严峻。为了解决这个问题,组件化应运而生。
组件化的优势
组件化能够解决以下问题:
- 能够独自调试和运转独自的事务模块,这样开发人员能够专心于自己担任的事务模块的开发,进步开发的功率。
- 能够下降代码的耦合度,不会导致牵一发而动全身,新参加的开发人员也更简略上手项目。各事务模块之间没有依靠联系,也会削减提交代码抵触的问题。
- 能够灵活装备依靠的模块,让基础模块更好地得到重用。
- 能够让开发人员的分工更加清晰,其他模块的开发人员不能轻易修正你担任的模块的代码。
示例
下面我就用一个简略示例来解说怎么完结App的组件化。
现在有一个运用市场App,该App包含5个模块:
-
app模块
:App的进口; -
home模块
:App的主页,首要用于展现主页引荐下载的运用; -
game模块
:首要用于展现引荐下载的游戏运用; -
download模块
:首要用于展现下载中的运用列表和下载列表的操控; -
base模块
:供给BaseActivity、BaseFragment、图片加载、网络恳求等基础才能,各个事务模块都需求依靠它;
app模块需求依靠home模块和game模块,同时,home模块和game模块需求显现运用的下载进度,需求依靠download模块,这样,没有完结组件化之前,各模块之间的依靠联系图如下所示:
组件独立调试
完结组件化,首先咱们要让home模块、game模块、download模块这几个事务模块能够独自调试和运转,怎么完结呢?
咱们能够在工程的gradle.properties中装备一个常量值moduleRunAlone
,值为true即表示这几个事务模块能够独立调试和运转:
# Enables the module to run alone
moduleRunAlone = true
然后在各事务模块的build.gradle中读取moduleRunAlone的值:
if(moduleRunAlone.toBoolean()){
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}
android {
sourceSets {
main {
// 独自调试与集成调试时运用不同的AndroidManifest.xml文件
if (moduleRunAlone.toBoolean()) {
manifest.srcFile 'src/main/moduleManifest/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
Android Studio开发Android项目运用的是Gradle来构建,Gradle供给了3种插件,这儿经过读取moduleRunAlone的值来装备module的类型:
-
application插件
:apply plugin: ‘com.android.application’
-
library插件
:apply plugin: ‘com.android.library’
在对应的事务模块中,新建moduleManifest目录,增加模块独自调试对应的AndroidManifest.xml文件,如下图所示:
然后在build.gradle中依据moduleRunAlone的值来装备不同的AndroidManifest.xml文件的路径。
在home模块中,集成调试的进口页面是HomeFragment,独自运转需求有进口Activity,这儿咱们新建HomeActivity作为进口Activity:
public class HomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
FragmentManager manager = this.getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.add(R.id.container_fragment, new HomeFragment());
ft.commit();
}
}
activity_home.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/container_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
这样独自调试的时分就能够运用HomeActivity来启动HomeFragment页面了。
独自调试的AndroidManifest.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.home">
<application
android:name=".HomeApp"
android:allowBackup="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".HomeActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
这样装备完结后,把moduleRunAlone的值改为true,然后Sync Project,home模块就能够独自运转了,这样就把home模块改造成了一个能够独立调试运转的组件。
页面跳转
组件化的核心是解耦,组件间不能有依靠,那么怎么完结组件间的页面跳转呢?
比如这儿我想从home组件下的HomeFragment跳转到download组件下的DownloadActivity,由于home组件不依靠download组件,直接跳转是行不通的,这儿咱们运用ARouter来进行跳转。
ARouter是阿里开发的,是一个帮助安卓App进行组件化改造的路由结构。
前面说到,各个事务模块都依靠base模块,咱们在base模块的build.gradle中运用api来增加ARouter依靠:
api "com.alibaba:arouter-api:1.3.1"
这样在各个事务模块中能够传递依靠ARouter库。
需求留意的是,arouter-compiler的annotationProcessor依靠需求在一切运用到ARouter的模块中独自增加,不然无法生成索引文件,会导致无法跳转成功,并且还要在对应模块的build.gradle中增加特定装备。
例如,在home模块的build.gradle增加的装备如下:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
//运用ARouter的模块需增加这个依靠
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}
在DownloadActivity上增加注解@Route(path = "/download/DownloadActivity")
,路径留意至少需求有两级,前面的download对应模块名,与build.gradle中装备的moduleName对应:
@Route(path = "/download/DownloadActivity")
public class DownloadActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
...
}
}
从HomeFragment跳转到DownloadActivity需调用ARouter.getInstance().build("/download/DownloadActivity").navigation()
:
public class HomeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
...
Button btnDownload = view.findViewById(R.id.btn_download);
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ARouter.getInstance()
.build("/download/DownloadActivity")
.navigation();
}
});
...
}
}
此刻还无法跳转成功,要在app模块的Application中初始化ARouter:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
if(BuildConfig.DEBUG){
ARouter.openLog();
ARouter.openDebug();
}
ARouter.init(this);
...
}
}
由于组件间不能有依靠,home组件不能依靠download组件,可是home组件和download组件都需求参加编译,不然不能生成路由,能够让app模块依靠home模块和download模块来完结编译。这儿咱们运用Gradle 3.0新供给的依靠办法runtimeOnly,经过runtimeOnly办法依靠时,依靠项仅在运转时可见,编译期间依靠项的代码是彻底阻隔的,则app模块的build.gradle装备如下:
dependencies {
runtimeOnly project(':home')
runtimeOnly project(":download")
runtimeOnly project(":game")
implementation project(":base")
}
这样在编译期,app模块与home模块、download模块、game模块都是互相阻隔的,那么在app模块中的HomeActivity中和怎么拿到home模块中的HomeFragment和game模块中的GameFragment,完结这两个Fragment的切换呢?答案仍是运用ARouter,代码如下:
public class HomeActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {
Fragment mHomeTabFragment;
Fragment mGameTabFragment;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
RadioGroup rg_tab = findViewById(R.id.tabs);
rg_tab.setOnCheckedChangeListener(this);
initDefaultFragment();
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
hideAllFragments();
switch (checkedId) {
case R.id.main_tab_home:
replaceFragment(0);
break;
case R.id.main_tab_game:
replaceFragment(1);
break;
}
}
private void initDefaultFragment(){
if (mHomeTabFragment == null) {
mHomeTabFragment = (Fragment) ARouter.getInstance().build("/home/homeFragment").navigation();
}
FragmentTransaction transaction = this.getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.container_fragment, mHomeTabFragment, "home");
transaction.commit();
}
private void replaceFragment(int position) {
FragmentTransaction transaction = this.getSupportFragmentManager().beginTransaction();
switch (position) {
case 0:
if (mHomeTabFragment == null) {
mHomeTabFragment = (Fragment) ARouter.getInstance().build("/home/homeFragment").navigation();
transaction.add(R.id.container_fragment, mHomeTabFragment, "home");
}
transaction.show(mHomeTabFragment);
break;
case 1:
if (mGameTabFragment == null) {
mGameTabFragment = (Fragment) ARouter.getInstance().build("/game/gameFragment").navigation();
transaction.add(R.id.container_fragment, mGameTabFragment, "game");
}
transaction.show(mGameTabFragment);
break;
}
transaction.commitAllowingStateLoss();
}
private void hideAllFragments() {
FragmentTransaction transaction = this.getSupportFragmentManager().beginTransaction();
if (mHomeTabFragment != null) {
transaction.hide(mHomeTabFragment);
}
if (mGameTabFragment != null) {
transaction.hide(mGameTabFragment);
}
transaction.commitAllowingStateLoss();
}
}
组件间通讯
有时分组件之间没有办法做到彻底阻隔,比如,home组件的HomeFragment中需求显现某一个运用的下载进度,home组件与download组件之间没有依靠联系,那么它们之间怎么通讯?答案是从download模块中拆分出一个露出的模块:export_download,里边供给接口给home组件运用。
平常开发中咱们常用接口进行解耦,不需求关心接口的详细完结,避免接口调用与事务逻辑完结之间紧密相关,这儿组件间的通讯也是采用相同的思路。
咱们新建一个library模块:export_download,home模块和download模块都依靠export_download模块:
//在home模块和download模块的build.grale中增加
dependencies {
implementation project(":export_download")
}
在export_download模块中增加IDownloadService承继IProvider:
public interface IDownloadService extends IProvider {
int getDownloadProgress(String name);
}
download模块中是对这个接口的详细完结:
@Route(path = "/download/service")
public class DownloadServiceImpl implements IDownloadService {
@Override
public int getDownloadProgress(String name) {
Map<String, Integer> progressMap = new HashMap<>();
progressMap.put("tiktok", 47);
progressMap.put("weibo", 89);
return progressMap.get(name);
}
@Override
public void init(Context context) {
}
}
这样,在集成调试模式下,home模块中的HomeFragment能够经过ARouter来获取其间某个运用的下载进度,这样就完结了组件间的通讯:
@Route(path = "/home/homeFragment")
public class HomeFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView tv1 = view.findViewById(R.id.tv1);
IDownloadService downloadService = (IDownloadService) ARouter.getInstance().build("/download/service").navigation();
tv1.setText("Weibo:" + downloadService.getDownloadProgress("weibo") + "%");
}
}
Applicaiton的生命周期分发
咱们通常会在app模块的Application的onCreate()办法中做一些初始化操作,而事务组件有时也需求履行一些初始化操作,你或许会说,直接一股脑都在app模块的Application中初始化不就行了吗,可是这样做会带来问题,由于咱们期望app模块与事务组件之间的代码是阻隔,并且咱们期望组件内部的使命要在组件内部初始化。
这儿运用AppLifeCycle插件,它能够让事务组件无侵入地获取Application生命周期,它专门用在组件化开发中,运用后app模块的Application的生命周期会主动分发给事务组件,详细运用办法如下:
- 在项目的build.gradle中增加applifecycle插件仓:
buildscript {
repositories {
google()
jcenter()
//applifecycle插件仓也是jitpack
maven { url 'https://jitpack.io' }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
//加载插件applifecycle
classpath 'com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-plugin:1.0.3' }
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
- 在app的build.grale中增加:
//增加运用插件applifecycle
apply plugin: 'com.hm.plugin.lifecycle'
- 在base模块中增加依靠:
//base模块的build.grale
dependencies {
api 'com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-api:1.0.4'
}
- 在事务组件中增加依靠:
dependencies {
annotationProcessor 'com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-compiler:1.0.4'
}
- Sync Project后,在事务组件中新建类完结IApplicationLifecycleCallbacks接口并增加
@AppLifecycle
注解,用于接收Application生命周期的分发,如download模块新建类如下:
@AppLifecycle
public class HomeAppLifecycleCallbacks implements IApplicationLifecycleCallbacks {
@Override
public int getPriority() {
return 0;
}
@Override
public void onCreate(Context context) {
Log.i("HomeApp", "onCreate");
//可在此处做模块内使命的初始化
}
@Override
public void onTerminate() {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
}
完结的办法有onCreate()、onTerminate()、onLowMemory()、onTrimMemory()、getPriority(),其间最重要的是onCreate()办法,可在此处做模块内使命的初始化。还能够经过getPriority()办法设置多个模块中onCreate()办法调用的优先次序。
- 在app的Application中触发生命周期的分发:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.i("App", "onCreate");
...
ApplicationLifecycleManager.init();
ApplicationLifecycleManager.onCreate(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ApplicationLifecycleManager.onTerminate();
}
@Override
public void onLowMemory() {
super.onLowMemory();
ApplicationLifecycleManager.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
ApplicationLifecycleManager.onTrimMemory(level);
}
}
App运转后经过打印能够看到HomeAppLifecycleCallbacks的onCreate()办法也会随之履行。
HomeAppLifecycleCallbacks用于集成调试中,模块独立调试能够自己新建一个HomeApp来履行独立调试运转需求的初始化操作。
完结组件化后,各模块之间的依靠联系图如下图所示:
app模块依靠一切的事务模块,一切涉及到下载的模块都需求依靠export_download模块,还有,一切模块都需求依靠base模块。这儿一切模块间的依靠联系都不运用api传递依靠,这样做是为了后续依靠的灵活装备。后续能够把各个模块编译生成的aar包都上传到maven仓库,这样能够像依靠一般插件相同装备依靠的模块,这样做还有一个优点,其他模块的开发人员没有办法轻易修正你担任的模块的代码了。
Demo地址:github.com/EnzoXRay/My…