布景

在项目的开发过程中,跟着参加人员的增多以及功用的增加,假如没有运用合理的开发架构,代码会越来越臃肿,耦合越来越严峻。为了解决这个问题,组件化应运而生。

组件化的优势

组件化能够解决以下问题:

  1. 能够独自调试和运转独自的事务模块,这样开发人员能够专心于自己担任的事务模块的开发,进步开发的功率。
  2. 能够下降代码的耦合度,不会导致牵一发而动全身,新参加的开发人员也更简略上手项目。各事务模块之间没有依靠联系,也会削减提交代码抵触的问题。
  3. 能够灵活装备依靠的模块,让基础模块更好地得到重用。
  4. 能够让开发人员的分工更加清晰,其他模块的开发人员不能轻易修正你担任的模块的代码。

示例

下面我就用一个简略示例来解说怎么完结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的生命周期会主动分发给事务组件,详细运用办法如下:

  1. 在项目的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' }
    }
}
  1. 在app的build.grale中增加:
//增加运用插件applifecycle
apply plugin: 'com.hm.plugin.lifecycle'
  1. 在base模块中增加依靠:
//base模块的build.grale
dependencies {
    api 'com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-api:1.0.4'
}
  1. 在事务组件中增加依靠:
dependencies {
    annotationProcessor 'com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-compiler:1.0.4'
}
  1. 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()办法调用的优先次序。

  1. 在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…