1、创建项目ComponentizationDemo
2、创建ComponentService组件
1、新建ComponentService Module,使用“Android Library”类型
2、修改build.gradle
中的SDK配置(参考“思考1”),后面每个新建的Module都要修改
3、删除build.gradle
中dependencies
部分的无关内容
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.gradle
中dependencies
部分内容,同时添加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
的值,来配置不同的ApplicationId
和AndroidManifest.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.gradle
的dependencies
部分内容
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、单元测试相关的依赖方式需要更改吗?
改成testApi
、androidTestApi
?——不能统一在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、implementation
和api
依赖方式的区别
首先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来进行添加依赖。
使用场景
除了两个基础组件
ComponentService
和Base
使用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
文件中增加两个变量,用来控制测试组件。
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'
}
网友评论