美文网首页
ComponentizationDemo编写过程记录

ComponentizationDemo编写过程记录

作者: 清夕 | 来源:发表于2020-09-03 19:56 被阅读0次

1、创建项目ComponentizationDemo

2、创建ComponentService组件

1、新建ComponentService Module,使用“Android Library”类型

2、修改build.gradle中的SDK配置(参考“思考1”),后面每个新建的Module都要修改

3、删除build.gradledependencies部分的无关内容

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    // 以下是三个最常用的基础库,在此声明依赖,注意使用api方式
    api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    api 'androidx.core:core-ktx:1.1.0'
    api 'androidx.appcompat:appcompat:1.1.0'
}

4、创建Service单例工厂类ServiceFactory

组件通过这个工厂类来获得其他组件提供的Service。

object ServiceFactory {
    // 后面组件创建服务时会添加代码
}

3、创建Base组件(基础库)

1、新建Base Module,使用“Android Library”类型

2、修改build.gradledependencies部分内容,同时添加ARouter库

!!!将公共的基础依赖库引用改为api方式,使这些依赖库对其他引用Base的组件可见。

这也是将Base组件作为基础依赖库的原因。

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // AndroidX库。前三个在componentservice中引用过了,不需要再次引用
    // api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    // api 'androidx.core:core-ktx:1.1.0'
    // api 'androidx.appcompat:appcompat:1.1.0'
    api 'androidx.constraintlayout:constraintlayout:1.1.3'

    // ARouter
    api 'com.alibaba:arouter-api:1.5.0'
    // arouter-compiler 的注解依赖需要所有使用 ARouter 的 model 都添加依赖
    kapt 'com.alibaba:arouter-compiler:1.2.2' // Kotlin使用kapt!!!
    
    // 组件服务接口公开库,也包含最基本的三个AndroidX/Kotlin库
    api project (':componentservice')
}

3、在build.gradle中添加ARouter配置

// 使用Kapt插件
apply plugin: 'kotlin-kapt'

android {
    ……
}

kapt {
    // 所有使用到 ARouter 的组件和模块的 build.gradle都要添加以下新增配置:
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

4、添加BaseApp基类。组件需要实现该类,并在该类的方法中向ComponentService注册公开的Service

/**
 * 以下两个方法用于主App在Application#onCreate方法中加载所需组件
 * 组件实现的ComponentApp类中的onCreate方法用于在独立调试时初始化自身的App
 */
abstract class BaseApp : Application() {
    /**
     * Application初始化
     */
    abstract fun initModuleApp(application: Application)

    /**
     * 所有Application初始化后的自定义操作
     */
    abstract fun initModuleData(application: Application)
}

4、创建并编辑Login组件

1、新建Login Module,使用“Phone & Tablet Module”类型

2、新建login\gradle.properties文件,添加以下内容:

# 是否将该module作为独立App运行调试
isRunAlone=false

3、动态配置组件的ApplicationId名称和AndroidManifest文件路径

1)在login\src\main\manifest目录(没有自己新建)下新建AndroidManifest.xml文件。也可以把manifest目录命名为其他名字,如“runalone”,这样更直观。

  • login\src\main\manifest\AndroidManifest.xml:用于独立调试(手动创建的)
  • login\src\main\AndroidManifest.xml:用于集成调试

2)修改main\manifest\AndroidManifest.xml内容

即原来main\AndroidManifest.xml的内容

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.giz.login">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".LoginMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3)修改main\AndroidManifest.xml内容

只保留manifest.package、application.theme、activity.name这几项。

作为library角色参与集成调试。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.giz.login">

    <application
        android:theme="@style/AppTheme">
        <activity android:name=".LoginMainActivity" />
    </application>

</manifest>

4)在build.gradle中通过判断isRunAlone的值,来配置不同的ApplicationIdAndroidManifest.xml文件的路径

if (isRunAlone.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29

    defaultConfig {
        if (isRunAlone.toBoolean()) {
            // 独立调试时添加applicationId,集成调试时不添加
            applicationId "com.giz.login"
        }

        minSdkVersion 27
        targetSdkVersion 29
        ……
    }

    buildTypes {
        ……
    }

    sourceSets {
        main {
            manifest {
                // 独立调试与集成调试时使用不同的AndroidManifest.xml
                if (isRunAlone.toBoolean()) {
                    srcFile 'src/main/manifest/AndroidManifest.xml'
                } else {
                    srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    }
}

dependencies {
    ……
}

4、修改build.gradledependencies部分内容

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // implementation base库,不能用api
    implementation project (':base')
    // ARouter所需的Apt
    kapt 'com.alibaba:arouter-compiler:1.2.2'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

同时添加ARouter配置(下同,不再重复)。

// 使用Kapt插件
apply plugin: 'kotlin-kapt'

android {
    ……
}

kapt {
    // 所有使用到 ARouter 的组件和模块的 build.gradle都要添加以下新增配置:
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

5、创建数据类UserInfo,以及保存该数据的单例AccountUtil

6、在ComponentService中公开自己提供的Service,创建IAccountService接口。同时在componentservice.emptyservice包下创建空实现类

interface IAccountService {
    /**
     * 是否已经登录
     */
    fun isLogin(): Boolean

    /**
     * 获取登录用户的 AccountId
     */
    fun getAccountId(): String
}

class EmptyAccountService : IAccountService {
    override fun isLogin(): Boolean = false

    override fun getAccountId(): String = ""
}

7、在ServiceFactory单例中添加相关注册与获取IAccountService实现的方法

object ServiceFactory {

    private lateinit var mAccountService: IAccountService

    fun setAccountService(accountService: IAccountService) {
        this.mAccountService = accountService
    }

    fun getAccountService(): IAccountService {
        if(!this::mAccountService.isInitialized) {
            mAccountService = EmptyAccountService()
        }
        return mAccountService
    }

}

8、创建IAccountService的实现类AccountServiceImpl

class AccountServiceImpl : IAccountService {
    override fun isLogin(): Boolean = AccountUtil.userInfo != null

    override fun getAccountId(): String = AccountUtil.userInfo?.accountId ?: ""
}

9、创建BaseApp抽象类的实现类LoginApp,并在onCreate方法中向ComponentService注册AccountServiceImpl

class LoginApp : BaseApp() {

    override fun onCreate() {
        super.onCreate()
        initModuleApp(this)
        initModuleData(this)
    }

    override fun initModuleApp(application: Application) {
        ServiceFactory.setAccountService(AccountServiceImpl())
    }

    override fun initModuleData(application: Application) {}
}

5、创建并编辑Share组件

1-4步同4、创建并编辑Login组件

5、编辑ShareMainActivity,使用Login组件提供的服务,进行数据传递

class ShareMainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_share_main)
        
        share_btn.setOnClickListener {
            share()
        }
    }
    
    @SuppressLint("SetTextI18n")
    private fun share() {
        // 获取Login组件的服务
        val _as = ServiceFactory.getAccountService()
        if (_as.isLogin()) {
            share_text.text = "用户已登录,可以分享。用户ID为:${_as.getAccountId()}"
        } else {
            share_text.text = "用户未登录,分享失败"
            // 跳转登录页面
        }
    }
}

此时,将Share组件的isRunAlone改为true,便可以编译成App运行了。但是因为没有加载Login组件,所以点击分享按钮会提示“用户未登录,分享失败”。

6、在主App中加载组件

1、创建储存所有组件的Application类名的类,叫AppConfig

这些都是需要加载的组件。

object AppConfig {
    // Login组件
    private const val LOGIN_APP = "com.giz.login.LoginApp"

    // 所有实现BaseApp接口的组件App类全名
    val moduleApps = arrayOf(
        LOGIN_APP
    )
}

2、在build.gradle中添加相关依赖

// 使用Kapt插件
apply plugin: 'kotlin-kapt'

android {
    ……
}

kapt {
    // 所有使用到 ARouter 的组件和模块的 build.gradle都要添加以下新增配置:
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // 依赖组件
    implementation project(':base')
    runtimeOnly project(':share')
    runtimeOnly project(':login')
    // ARouter
    kapt "kapt 'com.alibaba:arouter-compiler:$arouter_compiler_version"
    
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

3、创建主App的Application类,在onCreate方法中调用initModuleApp方法初始化所需组件。

class MainApplication : BaseApp() {
    override fun onCreate() {
        super.onCreate()
        // 初始化组件 Application
        initModuleApp(this);
        // 其他操作
        
        // 所有 Application 初始化后的操作
        initModuleData(this);
    }

    override fun initModuleApp(application: Application) {
        AppConfig.moduleApps.forEach {
            try {
                (Class.forName(it).newInstance() as BaseApp).initModuleApp(this)
            } catch (e: Exception) {
                Log.d("MainApplication", "initModuleApp: 初始化ModuleApp失败:$e")
            }
        }
    }

    override fun initModuleData(application: Application) {
        AppConfig.moduleApps.forEach {
            try {
                (Class.forName(it).newInstance() as BaseApp).initModuleData(this)
            } catch (e: Exception) {
                Log.d("MainApplication", "initModuleData: 初始化ModuleData失败:$e")
            }
        }
    }
}

4、在AndroidManifest.xml中设置<application>标签的name属性

设为.MainApplication

<application
        android:name=".MainApplication"
       ……>
        <activity android:name=".MainActivity">
            ……
        </activity>
    </application>

7、组件之间界面(UI)跳转逻辑

使用ARouter。

跳转逻辑:

  • 主项目跳转登录组件界面
  • 登录成功后跳转分享组件界面

1、在主App的Application中初始化ARouter

override fun onCreate() {
        super.onCreate()

        // 初始化 ARouter
        if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
            ARouter.openLog()      // 打印日志
            ARouter.openDebug()    // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(this)

        // 初始化组件 Application
        initModuleApp(this);
        // 所有 Application 初始化后的操作
        initModuleData(this);
    }

    private fun isDebug() = BuildConfig.DEBUG

2、编写Login组件的主界面LoginMainActivity

// 添加路由:
@Route(path = "/account/login")
class LoginMainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login_main)

        updateTextView()
    }

    @SuppressLint("SetTextI18n")
    private fun updateTextView() {
        login_tv.text = "登录状态:${AccountUtil.userInfo?.name ?: "未登录"}"
    }

    // 登录按钮
    fun login(view: View) {
        AccountUtil.userInfo = UserInfo("u1234", "Michael")
        updateTextView()
    }

    // 退出登录按钮
    fun exit(view: View) {
        AccountUtil.userInfo = null
        updateTextView()
    }

    // 跳到分享组件
    fun gotoShare(view: View) {
        // 使用ARouter跳转:
        ARouter.getInstance().build("share/share").withString("share_content", "分享数据到XXX")
    }
}

3、编写Share组件的主界面ShareLoginActivity

@Route(path = "/share/share")
class ShareMainActivity : AppCompatActivity() {

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_share_main)

        // 判断是否有数据传入
        if (intent != null) {
            val content = intent.getStringExtra("share_content")
            share_text.text = "收到分享内容:$content"
        }
        
        share_btn.setOnClickListener {
            share()
        }
    }

    @SuppressLint("SetTextI18n")
    private fun share() {
        // 获取Login组件的服务
        val service = ServiceFactory.getAccountService()
        if (service.isLogin()) {
            share_text.text = "用户已登录,可以分享。用户ID为:${service.getAccountId()}"
        } else {
            share_text.text = "用户未登录,分享失败"
            // 跳转登录页面
        }
    }
    
    // 跳转到登录组件
    fun gotoLogin(view: View) {
        ARouter.getInstance().build("/account/login").navigation()
    }
}

4、在主App的界面中编写两个跳转逻辑

然后在 MainActivity 中通过 ARouter 跳转,其中build 处填的是 path 地址,withXXX 处填的是 Activity 跳转时携带的参数的 key 和 value,navigation 就是发射了路由跳转。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun gotoLogin(view: View) {
        ARouter.getInstance().build("/account/login").navigation()
    }

    fun gotoShare(view: View) {
        ARouter.getInstance().build("/share/share").withString("share_content", "分享到XXX的APP").navigation()
    }
}

8、主App中使用Login组件提供的展示“用户界面”的Fragment

1、在Login组件中新建用户界面UserFragment

class UserFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_user, container, false).apply { 
            tv_user_name.text = if (AccountUtil.userInfo == null) "未登录" else "登录用户名为:${AccountUtil.userInfo?.name}"
        }
    }

}

2、在IAccountService中添加新接口,并在AccountServiceImpl中添加对应实现

interface IAccountService {
    ……

    /**
     * 创建UserFragment
     */
    fun newUserFragment(activity: Activity, containerId: Int, manager: FragmentManager, bundle: Bundle?, tag: String?): Fragment?
}

// 空实现
class EmptyAccountService : IAccountService {
    ……

    override fun newUserFragment(
        activity: Activity,
        containerId: Int,
        manager: FragmentManager,
        bundle: Bundle?,
        tag: String?
    ): Fragment? = null
}

// 实际实现
class AccountServiceImpl : IAccountService {
    ……

    override fun newUserFragment(
        activity: Activity,
        containerId: Int,
        manager: FragmentManager,
        bundle: Bundle?,
        tag: String?
    ): Fragment? = UserFragment().also {
        manager.beginTransaction().add(containerId, it, tag).commit()
    }
}

3、在主App中新建FragmentActivity,并调用newUserFragment方法添加用户界面

class FragmentActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fragment)

        // 添加用户界面
        ServiceFactory.getAccountService().newUserFragment(this, R.id.layout_fragment, supportFragmentManager, null, null)
    }
}

4、在主App的MainActivity中添加按钮跳转到FragmentActivity

组件内跳转,不需要路由。

fun gotoFragment(view: View) {
        startActivity(Intent(this, FragmentActivity::class.java))
    }

思考

1、抽取出所有组件的build.gradle中共同的配置项

  • project/build.gradle中定义SDK和依赖库的版本:
buildscript {
    ext.kotlin_version = "1.4.0"
    repositories {
       ……
    }
    dependencies {
        ……
    }
    ext {
        // SDK和依赖库版本
        minSdkVersion = 27
        targetSdkVersion = 29
        compileSdkVersion = 29
        arouter_compiler_version = 1.2.2
    }
}
  • module/build.gradle中引用
android {
    compileSdkVersion rootProject.compileSdkVersion // 使用rootProject.xxx来引用

    defaultConfig {
        applicationId "com.giz.login"

        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        ……
    }
    
    dependencies {
        // 注意引用arouter_compiler_version时,需要使用双引号!!!
        kapt "com.alibaba:arouter-compiler:$arouter_compiler_version"
    }
}

也可以在项目级的gradle.properties中定义:

compile_sdk_version = 27
support_version = 25.2.0
min_sdk_version = 15
target_sdt_version = 27

然后引用:

android {
    compileSdkVersion compile_sdk_version.toInteger() // 需要进行类型转换

    defaultConfig {
        minSdkVersion min_sdk_version.toInteger()
        targetSdkVersion target_sdt_version.toInteger()

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        ……
    }
}

2、单元测试相关的依赖方式需要更改吗?

改成testApiandroidTestApi?——不能统一在Base组件中声明。

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

————经过测试,在Base组件中使用testApi,在其他组件中implementation project('base'),这样会报错。所以测试依赖库必须在每个需要的组件中添加。

3、资源冲突问题

  • 组件之间肯定是不能互相引用资源的
  • 如果几个组件与主App位于同一个Project中,则主App在布局文件中也无法直接引用其他组件中定义的资源
  • 如果将某个组件(如Login)导出为aar,然后主App通过fileTree引入该aar文件。
    • implementation引入:代码、资源均不隔离
    • runtimeOnly引入:代码、资源均隔离

4、如何在需要时才加载组件?

参考MainApplication中的加载代码,可以移到任何需要加载的地方。

// 使用反射,在运行中动态加载组件
try {
    (Class.forName("com.giz.login.LoginApp").newInstance() as BaseApp).initModuleApp(application)
} catch (e: Exception) {
    Log.d("MainApplication", "initModuleApp: 初始化ModuleApp失败:$e")
}
// 然后再获取Service并使用
ServiceFactory.getAccountService()?.newUserFragment(this, R.id.layout_fragment, supportFragmentManager, null, null)

5、组件中的Activity需要在AndroidManifest.xml(集成调试)中声明吗?

<font color="red">!!!当前需要,否则无法通过ARouter跳转。</font>

6、Kotlin下使用Kapt插件的配置与Java使用Apt配置不同

Kotlin下Kapt的配置:

apply plugin: 'kotlin-kapt'

kapt {
    // 所有使用到 ARouter 的组件和模块的 build.gradle都要添加以下新增配置:
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
     kapt 'com.alibaba:arouter-compiler:1.2.2'
}

Java下Apt配置:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
     annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}

7、若要使用外部定义的版本号,在build.gradle中引入依赖库时需要使用双引号

project\build.gradle

buildscript {
    ext.kotlin_version = "1.4.0"
    
    ext {
        // SDK和依赖库版本
        arouter_compiler_version = "1.2.2"
    }
}

module\build.gradle

dependencies {
    // 使用双引号括起来
    api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    kapt "com.alibaba:arouter-compiler:$arouter_compiler_version"
}

8、implementationapi依赖方式的区别

首先compile被废弃了,而是分成了两个:implementation和api,其中api与之前的compile功能基本一致,不再赘述;implementation就比较高级了,其作用就是,使用implementation添加的依赖不会再编译期间被其他组件引用到,但在运行期间是完全可见的。这也是一种代码隔离。举个例子,

组件A依赖lib1,既A implementation lib1
组件B依赖组件A,既B api A

在gradle3.0.0之前,B是完全可以引用到lib1里面的类的,但是现在B在编译期间就做不到了,只能在运行期可以。这种思想有点类似于“下属的下属不是你的下属”的思想。但是这种隔离在组件之间是不起作用的,在上面的例子中A的所有类对B还是完全可见的,也就是没有做任何隔离的。不过implementation的确是一种有效减少编译时间的方式,还是上面的例子,lib1发生了变化,现在只需要编译A就可以了,而在之前B有可能也使用到了lib1,所以需要同时编译B和A。按照官方建议,大部分情况下都应该使用implementation来进行添加依赖。

使用场景

除了两个基础组件ComponentServiceBase使用api之外,其他全部使用implementation。主App也会使用runtimeOnly。

runtimeOnly:编译期间隔离,即编写代码的时候,无法看到和使用runtimeOnly依赖库的任何东西。

  • ComponentService组件:全部api
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // AndroidX库
    api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    api 'androidx.core:core-ktx:1.1.0'
    api 'androidx.appcompat:appcompat:1.1.0'
}
  • Base组件:全部api
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // AndroidX库
    api 'androidx.constraintlayout:constraintlayout:1.1.3'

    // ARouter
    api 'com.alibaba:arouter-api:1.5.0'
    // arouter-compiler 的注解依赖需要所有使用 ARouter 的 model 都添加依赖
    kapt "com.alibaba:arouter-compiler:$arouter_compiler_version"

    // 组件服务接口公开库,也包含最基本的三个AndroidX/Kotlin库
    api project (':componentservice')
}
  • 普通组件:全部implementation
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    implementation project(':base')
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}
  • 主App:implementation与runtimeOnly结合
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    kapt 'com.alibaba:arouter-compiler:1.2.2'
    implementation project(':base')
    runtimeOnly project(':login')
    runtimeOnly project(':share')
}

9、如何指定某一次集成测试中使用到的组件?

1)在主App的build.gradle中配置依赖

dependencies {
    implementation fileTree(dir: "libs", include: ["*.aar", "*.jar"])
    // 配置依赖组件
    implementation project(':base')
    runtimeOnly project(':share')
    runtimeOnly project(':login')
    // ARouter
    kapt "com.alibaba:arouter-compiler:$arouter_compiler_version"
}

2)编写自定义Gradle插件,然后在主app的gradle.properties文件中增加两个变量,用来控制测试组件。

JIMU的自定义Gradle插件

debugComponent=readercomponent,sharecomponent,sharecomponentkotlin
compileComponent=readercomponent,sharecomponent,sharecomponentkotlin

其中debugComponent是运行debug的时候引入的组件,compileComponent是release模式下引入的组件。我们可以看到debugComponent引入的两个组件写法是不同的,这是因为组件引入支持两种语法,module或者modulePackage:module,前者直接引用module工程,后者使用componentrelease中已经发布的aar。

当前如果使用这个自定义Gradle插件,那么组件build.gradle文件也需要相应地修改。

参考文章:Android彻底组件化demo发布

10、Kotlin组件与Java组件共存问题

完全兼容,可以互相调用。不同的地方是注解处理工具:

  • Kotlin:使用Kapt
  • Java:使用APT

具体build.gradle的代码见附录1

依赖库方面,除了官方的AndroidX库,Kotlin组件会依赖一些Ktx扩展,都在Base组件的build.gradle中声明即可。

11、如何自动生成ComponentService中的接口类?

没办法吧,手动添加。

12、数据如何保存?数据(Model/Repository)层应在哪里?

13、UI跳转如果用组件间数据传递的方式(通过Service公开)实现,与ARouter相比,优劣势如何?

公开Service实现:

interface IAccountService {
    /**
     * 使用Service跳转到注册界面
     */
    fun gotoRegisterActivity(context: Context): Intent
}

class AccountServiceImpl : IAccountService {
    // 构建跳转到RegisterActivity的Intent
    override fun gotoRegisterActivity(context: Context): Intent = Intent(context, RegisterActivity::class.java)
}

// MainActivity中通过Service接口调用
fun gotoRegister(view: View) {
        startActivity(ServiceFactory.getAccountService().gotoRegisterActivity(this))
    }

优势:

  • 传统方法,直观,易于控制。如果是带数据的Intent,组件中的Activity可以通过Service公开所需数据的相关信息,同时可以隐藏EXTRA KEY。:question:如果使用ARouter,需要统一规定EXTRA KEY等信息,可能需要额外的人为验证。

劣势:

  • 需要先判断Service返回的Intent是否合法(能否唤起对应的Activity),否则会造成应用闪退。如果使用ARouter,则不用担心路由不存在造成的后果(ARouter会弹出一个Toast,应用不会出错)。

ARouter有拦截器。

14、几个小组件组成大组件(一个模块),如何实现?即多层次组件。

15、组件分类。有界面的组件,无界面的组件。

16、数据在哪里存储?组件内还是公共层?

*JSON传递数据。

使用Repository抽象数据层。Repository负责数据的加载和保存,对外提供读取接口。

  • 非常常用的公共基础数据:公共层
  • 一般数据:组件内

17、组件间数据请求的异步回调

18、组件划分原则

其他

1、*ARouter的工作原理

创建一个路由表单例,保存路由信息

object RouterTable {
    const val LOGIN_PATH = "/account/login"
}

// 注解中使用
@Route(path = RouterTable.LOGIN_PATH)
2、组件化目标:编译期隔离、运行期可见。

非组件化:编译期可见、运行期可见。

组件化后,在运行时,各个组件注册了自己的服务,这些服务都是彼此可见、可以相互调用的。所以我觉得组件化的目标是在编译期实现各个组件之间的代码隔离。运行后,各个组件就形成一个整体。组件化把应用整体化的过程向后推迟到运行时期。而非组件化则是在编译时期就实现了应用整体化。

组件化通过技术手段在编译期施加一道屏障,防止组件之间的意外依赖。然后在运行期起到粘合剂的效果,将各个组件组装、粘合成一个整体。

非组件化的应用也可以人为地实现组件化效果,但因为没有组件化技术的参与,人为干预既要充当屏障,也要保证粘合正确,耗时耗力,且极易出错。此外,也不利于扩展与拆分。

附录

1、组件的build.gradle配置

Kotlin版:
if (isRunAlone.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion rootProject.compileSdkVersion

    defaultConfig {
        if (isRunAlone.toBoolean()) {
            // 独立调试时添加applicationId,集成调试时不添加
            applicationId "com.giz.login"
        }

        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            manifest {
                // 独立调试与集成调试时使用不同的AndroidManifest.xml
                if (isRunAlone.toBoolean()) {
                    srcFile 'src/main/manifest/AndroidManifest.xml'
                } else {
                    srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    }
}

kapt {
    // 所有使用到 ARouter 的组件和模块的 build.gradle都要添加以下新增配置:
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    // implementation base库
    implementation project (':base')
    // ARouter所需的Apt
    kapt "com.alibaba:arouter-compiler:$arouter_compiler_version"

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Java版:
if (isRunAlone.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    compileSdkVersion rootProject.compileSdkVersion

    defaultConfig {
        if (isRunAlone.toBoolean()) {
            applicationId "com.giz.pay"
        }

        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            manifest {
                if (isRunAlone.toBoolean()) {
                    srcFile 'src/main/manifest/AndroidManifest.xml'
                } else {
                    srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    implementation project(':base')
    annotationProcessor "'com.alibaba:arouter-compiler:$arouter_compiler_version"

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

相关文章

网友评论

      本文标题:ComponentizationDemo编写过程记录

      本文链接:https://www.haomeiwen.com/subject/ghbosktx.html