记一次组件化开发历程

android studio 下载 | 2018-11-19 23:24

最近也不知道什么原因就突然换了公司,新的公司新的需求。由于新的项目比较大,各个模块需要解耦,所以就用到了组件化开发。而且APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。

上图是组件化工程模型,为了方便理解这张架构图,下面会列举一些组件化工程中用到的名词的含义:

1.集成模式 :所有的业务组件被“app壳工程”依赖,组成一个完整的APP;2.组件模式:可以独立开发业务组件,每一个业务组件就是一个APP;3.app壳工程:负责管理各个业务组件,和打包apk,没有具体的业务功能;4.业务组件:根据公司具体业务而独立形成一个的工程;5.功能组件 提供开发APP的某些基础功能,例如打印日志、树状图等;6.Main组件 属于业务组件,指定APP启动页面、主界面;7.Common组件 属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;

Android APP组件化架构的目标是告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为arr包集成到“app壳工程”中,组成一个完整功能的APP;从组件化工程模型中可以看到,业务组件之间是独立的,没有关联的,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整业务功能的APP应用,但是在组件开发模式下,业务组件又变成了一个个application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。

Android Studio中的Module主要有两种属性,分别为:1、application属性,可以独立运行的Android程序,也就是我们的APP;apply plugin: ‘com.android.application’2.library属性,不可以独立运行,一般是Android程序依赖的库文件;apply plugin: ‘com.android.library’

Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;

Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否):

每次更改“isModule”的值后,需要点击 “Sync Project” 按钮isModule=false

然后我们在业务组件的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到 isModule 是个String类型的值,而我们需要的是Boolean值,代码如下:

if (isModule.toBoolean()) {apply plugin: ‘com.android.application’} else {apply plugin: ‘com.android.library’}

1.当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。2.在 组件化工程模型图中,功能组件集合中有一个 Common 组件, Common 有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖Common组件,Common 组件就像是万丈高楼的地基,而业务组件就是在 Common 组件这个地基上搭建起来我们的APP的,Common 组件会专门在一个章节中讲解,这里只讲 Common组件中的一个功能,在Common组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类。BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;

在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。通过路由实现模块之间的通信。

组件开发的demo已经上传到github上 :开发流程:

1.配置工程

ACMobi跨平台移动开发框架内部采用组件化结构设计。组件之间相互独立,不存在依赖关系,通过框架层的路由组件进行数据通信降低耦合度。组件化使得不同团队并行开发各业务,极大提高迭代效率。本文介绍了组件的开发、调试、测试、发布等一系列流程。

Android插件入口类必须继承ACComponentBase类。Android组件入口类类名需要配置到component.xml中。

已经有Android开发环境的可以略过本段

Android的开发环境搭建主要包括JDK、Android Studio的安装。

安装完成之后,可以在检查JDK是否安装成功。打开终端,输入java –version 查看JDK的版本信息。出现类似下面的画面表示安装成功了:

Android Studio 的安装教程网上有很多,请自行搜索一下,可能需要使用代理

官网地址

下载组件开发基础工程, Android Studio点击File->Open…,选择刚刚下载下来的基础工程ACMobiNativeMainDebug打开。

在工程下点击New->Module,新建ACComponentTabBar模块,如下图:

启动的activity固定的全类名为:com.appcan.activity.MainActivity

因此需要在java下新建com.appcan.activity包,如下:

在该包中新建MainAcitivity,继承Acitivity,如下:

该Acitivity中定义一个简单的button。

在模块ACComponentTabBar中的build.gradle文件中添加如下引用:

compile 'com.appcan.engine:ACRouter:1.0.0'//注意1.0.0为初始版本,开发时注意替换为最新版本

在ACComponentTabBar模块java中新建Class,需继承ACComponentBase类,如下图:

该类必须重写onCreate方法,在该方法中做一些初始化的操作。因为在设置启动页时,不需要对外提供方法,所以这里Initializer类的onCreate方法里面可以为空,若需要对外提供方法,则注册路由必须在这个方法中实现(实现方式后文介绍)。

在ACComponentTabBar模块的res目录下,新建xml文件夹,并新建component.xml文件,如下:

内容如下:

<?xml version="1.0" encoding="utf-8"?><acplugins>    <!--其中className属性值即为继承自ACComponentBase的入口类,moduleId属性值为组件唯一标示-->    <plugin className="com.appcan.component.accomponenttabbar.Initializer" moduleId="main"/></acplugins>配置中className属性值为继承自ACComponentBase的入口类,moduleId属性值为组件唯一标示

并将该配置加入app模块的res/xml/component.xml文件中。如下:

在app模块下的build.gradle文件中,新增ACComponentTabBar模块的工程引用,如下:

compile project(':ACComponentTabBar')3.4.5.AndroidManifest.xml配置说明关于ACMobiNativeMainDebug工程AndroidManifest.xml的配置有如下几点需要注意

1、工程依赖了NativeMain引擎,在NativeMain引擎的AndroidManifest.xml中已经指定了Application的具体实现类,其中包含了引擎的初始化等重要操作,所以在ACMobiNativeMainDebug工程任何模块的AndroidManifest.xml中标签下都不允许添加android:name=”xxxx”属性再来指定其它的Application实现类,如下图。

2、另外NativeMain引擎中已经配置好入口Activity,所以要把入口组件的启动界面命名为com.appcan.activity.MainActivity,如上图。

3、ACMobiNativeMainDebug中的app模块是示例工程的启动模块,其作用仅为创建程序入口,关联ACComponentTabBar和ACComponentActivity。通过打包服务器打包时是不需要上传app模块的,app中的修改并不会在打包生成的apk中生效,为了避免造成困扰请不要在app模块中做任何修改。

4、所有组件不允许配置android:allowBackup属性,引擎中已指定该属性值为fasle,当组件中配置为true时会出现冲突导致打包失败。

5、为了做到主题可配置,引擎中并未指定android:theme属性,但该属性又是打包必要属性,缺少该属性会导致打包失败,所以必须在入口组件中进行配置。

上面提到NativeMain引擎封装了Application的实现,那么组件中有些需要在应用初始化时执行的操作该如何处理?引擎的做法是将Application中onCreate、onConfigurationChanged、onTerminate、onLowMemory、onTrimMemory等系统方法向组件分发,并将Application实例以方法参数的形式传递给各个组件。所有组件中必须有一个继承于ACComponentBase.java的类来完成组件的初始化工作和其它Application生命周期的响应。其中onCreate被定义为抽象方法组件必须实现,组件初始化需要执行的操作必须放在这里。其它onConfigurationChanged、onTerminate、onLowMemory、onTrimMemory等方法可根据情况重写。

以ACComponentTabBar组件为例,Initializer.java继承了ACComponentBase.java实现了OnCreate方法,在该组件初始化时将自己注册到路由中供其它组件调用。

public class Initializer extends ACComponentBase {    @Override    public void onCreate(Application application) {        ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {            @Override            public String initModule() {                return getId(Initializer.class);            }        });    }}3.4.7.运行app点击运行app模块,ACComponentTabBar模块中定义的MainActivity就会被启动,该activity中定义了一个按钮,启动其他模块的Activity,下文讲述启动其他模块Activity的方法。

在新模块中定义activity供ACComponentTabBar模块启动。新建模块ACComponentActivity,根据步骤3.4.1,引入ACRoute。

根据步骤3.4.2中描述的同样方法创建入口类,在入口类的onCreate方法中添加路由的注册。注册代码如下:

package com.appcan.component.accomponenttabbar;import android.app.Application;import com.appcan.router.ACComponentBase;import com.appcan.router.ACRouter;import com.appcan.router.ServiceStub;public class Initializer extends ACComponentBase {    @Override    public void onCreate(Application application) {        ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {            @Override            public String initModule() {            return getId(Initializer.class);//这里通过路由提供的获取该模块唯一标示的方法getId(Class clazz)返回该模块注册路由时的唯一标识,在其他模块调用时需要用到。            }        });    }}以上代码中指定路由方法的接口类为IntentBridge,则需要在该模块下新建IntentBridge接口类。在ACComponentActivity模块java中新建Class,修改类型为interface,继承RouterService类,如下图:

在该类中定义对外提供的接口,定义代码如下:

@RouterUri(ACUri.Patten.ACTIVITY)//RouterUri注解指定接口类型,必须是Patten常量    @RouterImp(impClass = SecondActivity.class)//RouterImp注解指定接口实现类    void startSecondActivity(RouterCallback callback            , @RouterParam("context") Context context            , @RouterParam("param") String param1            , @RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode);//startSecondActivity为方法名称,callback为固定必选项,RouterParam注解指定的参数为可选项,注解括号中的内容指定在其他模块调用时参数的关键字,紧跟着注解的是参数类型。然后新建SecondActivity,将SecondActivity注册到AndroidManifest.xml中。

下一步,根据步骤3.4.3和3.4.4配置xml文件,并将配置添加到app中的component.xml文件中,同时在app的build.gradle文件中添加ACComponentActivity模块的引用。

接口配置完成之后,可以在模块ACComponentTabBar中调用ACComponentActivity模块中的方法。调用方法如下:

String uri = "second://activity/startSecondActivity?param=test&" + ACTIVITY_REQUEST_CODE + "=" + REQUEST_SECOND_CODE;        ACRouter.getACRouter().getmClient().invoke(MainActivity.this                , new ACUri(uri)                , new RouterCallback() {                    @Override                    public void callback(RouterCallback.Result result) {                        Log.i("callback", result + "");                    }                });其中uri的组合规则为”模块在路由中的唯一标识符://接口类型/接口名称?接口参数1=value1&接口参数2=value2”

对应关系如下图:(相同颜色箭头表示一个对应关系)

注意startSecondActivity方法定义了通过setResult返回给前一个Activity数据,则需要被启动的SecondActivity类中调用setResult方法,调用如下:

setResult(Activity.RESULT_OK); finish();同时,需要在调用startSecondActivity方法的MainActivity中重写onActivityResult方法。代码如下:

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {    if(requestCode == REQUEST_SECOND_CODE){        Toast.makeText(this, R.string.activity_back, Toast.LENGTH_SHORT).show();    }}至此,启动其他模块Activity的调用方法全部配置完毕, 可以运行程序了。

上文讲述的是ACTIVITY类型方法定义,即在定义方法时RouterUri注解指定的ACUri.Patten常量类型,本节讲述METHOD类型方法的定义。

ACComponentActivity模块中的SecondActivity中定义一个按钮,点击该按钮改变ACComponentTabBar模块中MainActivity界面的背景颜色。

根据上文讲述的方法,ACComponentTabBar模块定义继承自ACComponentBase的入口类Initializer,在Initializer的onCreate方法代码如下:

@Overridepublic void onCreate(Application application) {    ACRouter.getACRouter().regist(new ServiceStub<IntentBridge>() {        @Override        public String initModule() {            return getId(Initializer.class);//在路由中定义的唯一标识符        }    });}以及继承自RouterService的接口IntentBridge类,在IntentBridge类中定义接口,代码如下:

@RouterUri(ACUri.Patten.METHOD)//接口类型为METHOD@RouterImp(impClass = ImplementMethod.class)//接口的实现在ImplementMethod类中void changeTabBarBackground(RouterCallback callback, @RouterParam("color") String bgColor);//接口的名称changeTabBarBackground,固定参数callback和RouterParam注解指定color参数。ImplementMethod类中代码如下:

public class ImplementMethod {    public void changeTabBarBackground(RouterCallback callback, String color){        RouterCallback.Result result;        try {            MainActivity.getInstance().setBackgroundColor(color);//MainActivity方法需要定义setBackgroundColor方法            result = new RouterCallback.Result(                    RouterCallback.Result.SUCCESS,                    null,                    null);        } catch (Exception e) {            e.printStackTrace();            result = new RouterCallback.Result(                    RouterCallback.Result.FAIL,                    e.getMessage(),                    null);        }        callback.callback(result);    }}对应关系如下图:

紧接着在MainActivity方法中定义setBackgroundColor方法实现背景颜色的更换设置,代码如下:

public void setBackgroundColor(String color) {    findViewById(R.id.bgLinearLayout).setBackgroundColor(Color.parseColor(color));}至此,ACComponentTabBar模块METHOD类型接口定义完毕,下一步在ACComponentActivity接口中调用。

在ACComponentActivity的按钮点击事件中加入如下代码:

String uri = "main://method/changeTabBarBackground?color=#ff0000";ACRouter.getACRouter().getmClient().invoke(new ACUri(uri), new RouterCallback() {    @Override    public void callback(Result result) {        if(result != null && result.getCode() == Result.SUCCESS){            Toast.makeText(SecondActivity.this, "设置成功", Toast.LENGTH_LONG).show();        }else{            Toast.makeText(SecondActivity.this, "设置失败", Toast.LENGTH_LONG).show();        }    }});到这里,ACComponentActivity模块调用ACComponentTabBar模块的method类型接口定义及调用完成。

以上代码,Android组件开发Demo中,开发者可自行下载查看源码及运行查看效果。

该注解指定接口的类型,在继承自RouterService的接口类中定义方法时需要使用,目前只支持两种,activity和method

activity类型的接口表示启动一个Activity,定义方法时指定RouterUri注解的值为:ACUri.Patten.ACTIVITY,如下:

@RouterUri(ACUri.Patten.ACTIVITY)    @RouterImp(impClass = SecondActivity.class)    void startSecondActivity(RouterCallback callback            , @RouterParam("context") Context context            , @RouterParam("param") String param            , @RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode);调用时uri的组成为:

@RouterUri(ACUri.Patten.METHOD)    @RouterImp(impClass = ImplementMethod.class)    void changeTabBarBackground(RouterCallback callback, @RouterParam("color") String bgColor);调用时uri的组成为:

public class ImplementMethod {    public void changeTabBarBackground(RouterCallback callback, String color){    }}若一个模块中包含多个method类型的方法,这些方法都可以定义在同一个实现类中。

若是自定义的参数,则可以直接写成:

@RouterParam("参数名称") 参数类型 形参名称参数名称是指定调用时uri中参数的关键字,两者必须保持一致。形参名称可随意定义,无影响。

若是路由开放的特定的参数,即参数名称和参数类型固定,则写法如下:

@RouterParam(value = 参数名称, type = 参数类型) int 形参名称)目前特定的参数有两种,

一种是requestCode:

@RouterParam(value = ActivityProcessor.ACTIVITY_REQUEST_CODE, type = int.class) int requestCode)该参数表示启动的Activity可以通过setResult回传给调用者数据,上文有详细讲解。

另一种是flags:

@RouterParam(value = ActivityProcessor.ACTIVITY_FLAGS, type = int.class) int flags该参数表示启动Activity时的intent的flags参数。

callback参数固定表示通过路由调用其他模块接口时的回调方法。该方法有一个Result类型的参数,Result是一个内部JavaBean的类,声明了三个成员变量,code,msg和data,开发者可根据需要定义这三个参数的值。

private int code;private String msg;private String data;若RouterUri指定为activity类型,则callback回调方法是路由处理的,不会传递给被启动的Activity触发。组件开发时,只需要在调用方法时对回调的内容做处理,如下:

String uri = "second://activity/startSecondActivity?param=test&" + ACTIVITY_REQUEST_CODE + "=" + REQUEST_SECOND_CODE;        ACRouter.getACRouter().getmClient().invoke(MainActivity.this                , new ACUri(uri)                , new RouterCallback() {                    @Override                    public void callback(RouterCallback.Result result) {                        //只有在启动activity失败时,该方法被调用,result包含code和data参数。code表示错误码,data表示错误信息。                    }                });若RouterUri指定为method类型,则callback回调方法会传递给方法实现中去触发,如下:

public void changeTabBarBackground(RouterCallback callback, String color){        RouterCallback.Result result;        try {            MainActivity.getInstance().setBackgroundColor(color);            result = new RouterCallback.Result(                    RouterCallback.Result.SUCCESS,                    null,                    null);        } catch (Exception e) {            e.printStackTrace();            result = new RouterCallback.Result(                    RouterCallback.Result.FAIL,                    e.getMessage(),                    null);        }        callback.callback(result);    }5.上传组件打包测试5.1.脚本引用在ACComponentTabBar模块的build.gradle文件末尾加上如下代码:

apply plugin: 'ACComponent'apply plugin: 'maven'def VERSION_NAME = '1.0.0'//组件版本号def DESCRIPTION = '初始化'//更新日志def ARTIFACT_ID = 'ACComponentTabBar'//指定组件名称ACComponent{    name = "$ARTIFACT_ID"    versionName = "$VERSION_NAME"    description = "$DESCRIPTION"}

加入代码之后,还需要在工程下的gradle.properties文件中添加MARVEN_PATH,该值为ACMobi的官方maven库地址,其中包括ACComponent(组件出包脚本)等官方脚本,在工程的build.gradle中需要加入对该脚本的引用.

buildscript {    repositories {         jcenter()         maven {           url MARVEN_PATH        }    }    ...}

在ACComponentTabBar模块的根目录下,新建跟build.gradle中定义的ARTIFACT_ID变量值相同名称的文件夹,即ACComponentTabBar,在ACComponentTabBar文件夹下新建文件名为ACComponentTabBar(即ARTIFACT_ID的值)的xml文件以及info.xml文件。如下图:

ACComponentTabBar.xml文件内容如下:(该文件内容固定,无需更改)

<?xml version="1.0" encoding="utf-8"?><plugin>    <manifest_config>    </manifest_config>    <dependencies>    </dependencies></plugin>info.xml文件内容如下:

<?xml version="1.0" encoding="utf-8"?><acplugins>    <plugin acName="ACComponentTabBar" version="1.0.0" build="0" desc="xxxx" type="compoment">        <info>0:测试组件Demo</info>    </plugin></acplugins>特别注意:

info文件只需要按照上述内容做初始化设置即可,后面版本升级不需要手动修改该文件,该文件中除了acName的属性值”ACComponentTabBar”和info标签内的文字”测试组件Demo”会根据组件不同而改变,其余内容必须保证不变。

gradle出包脚本会根据build.gradle的配置,自动更新inof.xml文件内容。请参考5.5章节。

desc属性的值为组件功能简介,必须填写。

type=”compoment”不可修改,表示库为组件类型。

如果组件中有用到本地aar文件,需要在工程根目录下新建aar文件夹,并在工程的build.gradle文件中加入对aar文件夹的配置。所有被引用的*.aar文件必须放置于该目录下 如图:

repositories {    ...    flatDir {        dirs '../aar'    }}dependencies {    ...    compile(name:'testaar-1.0.0', ext:'aar')}5.4.组件混淆配置在打包服务器打正式包时会进行代码混淆,组件需要配置混淆文件告诉打包服务器该如何混淆。

在组件module的根目录下新建proguard.pro文件,并将混淆配置写入该文件,注意编写时请严格遵循ProGuard语法,如下图:

注意proguard.pro文件只需要按照上述内容编写、放置即可,不需要在组件build.gradle中的buildTypes里配置模块混淆,打包服务器会自动合并

根据需求先修改build.gradle中以下两个变量的值。

def VERSION_NAME = '1.0.0'//组件版本号def DESCRIPTION = '初始化'//更新日志组件升级只需要更新VERSION_NAME 和 DESCRIPTION即可,gradle出包脚本会自动更新变量值到info.xml中。

和步骤5.5.1一样的方式,双击ACComponentTabBar模块下的other->buildComponent执行该task,如下图:

运行完成之后,生成的组件包在ACComponentTabBar根目录下,文件名为:ACComponentTabBar-android-1.0.0.zip,将该组件包上传到打包服务器即可以打包在项目中使用。

ACComponentMyView是一个提供视图组件的demo,该组件中定义了一个TextView,并提供了一个路由方法getTextView(…)供其他组件调用,最后通过回调的方式让其他组件获得这个TextView。

在ACComponentMyView中创建了一个layout文件myview_textview.xml,文件中仅包含一个TextView

ImplementMethod类中getTextView方法是TextView传递的具体实现,具体内容就是将TextView解析出来通过RouterCallback返回给其他组件,RouterCallback中的范型T要和View类型保持一致这里是TextView

@RouterUri(ACUri.Patten.METHOD)    @RouterImp(impClass = ImplementMethod.class)    void getTextView(@RouterParam("context") Context context, RouterCallback<TextView> callback);public class ImplementMethod {        public void getTextView(Context context, RouterCallback<TextView> callback){            RouterCallback.Result<TextView> result;            try {                TextView textView = (TextView) MyViewActivity.getInstance().getWindow().getLayoutInflater().inflate(R.layout.myview_textview, null);                textView.setText("我是ACComponentMyView组件中的TextView");                result = new RouterCallback.Result<>(                        RouterCallback.Result.SUCCESS,                        null,                        textView);            } catch (Exception e) {                e.printStackTrace();                result = new RouterCallback.Result<>(                        RouterCallback.Result.FAIL,                        e.getMessage(),                        null);            }            callback.callback(result);        }    }ACComponentActivity组件中拿到TextView后将其添加到一个LinearLayout中

如果ACComponentMyView中的View对象已经被添加到了具体的布局中,那么这个对象在传递到其他的组件后是不允许添加到其他父布局的,要遵循一个View只能被添加到一个父布局的原则。

例子里是通过LayoutInflater初始化了一个新的TextView对象,没有被添加到任何布局,所以在传递到ACComponentActivity组件后能够被添加到ACComponentActivity的布局中。

对于自定义View由于组件之间类型不能识别,自定义View在传递到其他组件之后只能作为Android系统View使用。如果要用到自定义属性和方法,需要将自定义View定义到公共包中,并在两个组件中添加引用。

在这里获得的不仅仅是技术!