Compose 是什么?
Compose是Jetpack系列中用于构建原生Android界面的工具库,Jetpack是Google推出的一系列帮助开发者规范代码的库。简单来说就是用代码写UI,也就是声明式UI。
声明式UI和命令式UI的区别在于,声明式UI更关心做什么,而命令式UI关心怎么做。
简单来讲,我们直接使用View本身去显示内容,就是声明式,而我们要指定XML要去呈现什么内容,就是命令式。
写一个最简单的界面
下面用一个简单的界面对比一下Compose和常规的xml布局,这是一个非常简单的界面,如下。
我们常规的写布局的方式
- 定义布局文件
- 在Activity中引入
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello World!" />
</FrameLayout>
引入
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
如果用Compose来写
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Text(
text = "Hello World!",
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
当然这样可能看起来比较臃肿,我们可以把布局部分独立出来。如下
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
mainContentView()
}
}
}
@Composable
fun mainContentView() {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
Text(
text = "Hello World!",
modifier = Modifier.align(Alignment.Center)
)
}
}
从以上可以看出:
- Compose跟Flutter很像,如果有Flutter基础可基本上写Compose无压力
- 布局逻辑清晰,入手难度不高
为什么要推进Compose?XML有什么问题?
为什么Android选择XML作为的布局语言
- 可读性高:如果用代码写布局,很明显JAVA代码可读性非常差,布局逻辑非常不清晰
- 与业务逻辑解耦:xml语言实现与java代码物理隔离
XML加载比较耗时
通过分析setContentView源码,可以得出XML加载是个非常耗时的操作。他的耗时来源于两个操作
- 将布局文件加载到内存中:IO
- 解析文件,生成对应的View:反射
当然我们可以避开这两个操作,有两种方式
- AsyncLayoutInflater:异步加载,就是将IO和反射放到子线程去执行。
- X2C:将布局文件在编译时转换成用JAVA代码写布局
AsyncLayoutInflater存在的问题是,他并没有解决掉布局加载耗时问题,即使放到了子线程,我们还是要等待布局加载完成,才能进行view的相关操作。
X2C存在的问题是,用JAVA写布局代码很不友好,而且很多属性不支持。
Compose 对比 XML
- UI更加直观:从布局代码上来看,Compose的逻辑非常清晰,可以在Android Studio上实时预览,甚至可以在手机上实时刷新
- 布局更加灵活:基于代码的布局方式,可以更方便的动态添加或修改布局,实现更复杂的UI效果
- 更易于维护和修改:基于代码的布局文件,可以更方便的处理业务逻辑以及实现复用。省去了各种插件都想干掉的findviewbyid
- 不需要手动更新:Compose的元素会对依赖的数据自动进行订阅,数据改变了,界面自动改变,不需要手动更新
使用代码写函数还有一个XML不能实现的效果
Column {
repeat(4) {
log("repeat $it")
Text(text = "Hello $it")
}
}
引入Compose
Compose基础配置
- AS版本:Android Studio Arctic Fox 版本
- Gradle:7.0 +
- TargetSdk / CompileSdk:31 +
- MinSdk:21 +
- Kotlin:1.6.0+
- Compose:1.1.1
实际配置
以下是项目引入Compose后实际的依赖情况
config.gradle
ext {
android = [
compileSdkVersion: 33,
buildToolsVersion: "30.0.2",
...
]
}
主module下build.gradle
apply plugin: 'kotlin-kapt'
...
// 启用Jetpack Compose组件特性
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.1.0'
kotlinCompilerVersion "1.1.1"
}
kotlinOptions {
jvmTarget = "1.8"
}
dependencies {
...
// Compose 库的主体包
implementation 'androidx.compose.material:material:1.3.1'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
// 将 Activity 支持 Compose
implementation 'androidx.activity:activity-compose:1.5.0'
// Compose ui 预览
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
implementation 'androidx.compose.ui:ui-util:1.3.3'
// Compose ui 相关的基础支持
implementation 'androidx.compose.ui:ui:1.1.1'
// Compose 基于 ui 层封装的更实用的组件库 (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation 'androidx.compose.foundation:foundation:1.1.1'
// Compose ViewModels 存储数据
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0'
// Compose LiveData
implementation 'androidx.compose.runtime:runtime-livedata:1.1.1'
}
...
项目下build.gradle
...
buildscript {
dependencies {
...
classpath "com.android.tools.build:gradle:7.0.4"
...
}
}
gradle.properties
KotlinVersion=1.6.10
kotlin 升级后的问题
废弃了 ‘kotlin-android-extensions’ 插件。这个插件非常好用,可以直接通过id来引用View实例,但是他也存在一些问题
- 销毁之后的空指针问题
KAE通过生成缓存解决findviewbyid问题,但是Activity的onDestory和Fragment的onDestoryView之后会清除缓存,这里导致的问题就是如果在这个生命周期时做一些view的操作,会出现空指针问题
如下代码会出现崩溃
import kotlinx.android.synthetic.main.activity_main.*
class MainFragment : Fragment() {
...
override fun onDestroyView() {
super.onDestroyView()
textView.text = "Crash!"
}
}
而下面不会
lateinit var textView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
textView = view.findViewById(R.id.textView)
}
override fun onDestroyView() {
super.onDestroyView()
textView.text = "Nothing happened."
}
当然KAE也存在一些小问题,比如不存在的id等等,这些影响不大,还有个问题是它不支持Compose。我们可以使用viewbinding作为替代方案,它比KAE更安全
Gradle升级问题
- maven依赖http的需要新增allowInsecureProtocol属性
maven {
url "..."
allowInsecureProtocol = true
}
- 上传aar的方式变为publishing
...
apply plugin: 'maven-publish'
task androidSourcesJar(type: Jar) {
archiveClassifier.set('sources')
from android.sourceSets.main.java.srcDirs
}
publishing {
repositories {
maven {
//文件发布到下面目录
url = MAVEN_URL
credentials {
username MAVEN_USERNAME
password MAVEN_PASSWORD
}
}
}
publications {
aar(MavenPublication){
groupId = groupId1
artifactId artifactId1
version versionName
artifact androidSourcesJar
}
}
}
引入Compose的影响
- 包体积:因为引入了新的库和依赖,所以会有所增加,不过Google的拆包都比较细,可以只引入需要的库。根据Google官方的统计,约会增加2~3M
- 编译速度:Compose基于Kotlin编写,并且引入了一些新的编译工具和插件,对编译速度有一定的影响。不过如果使用构建缓存,编译速度影响不大,另外,Compose支持即时刷新界面
总结
Compose是未来Android的趋势,这是肯定的。但是他跟kotlin一样,不是一种“必须使用”的方案,他会逐步的替代XML,成为主流的开发方式。
仍需要跟进的问题
- Compose的绘制原理是什么,Compose的Text和TextView区别在哪
- Compose比XML快吗?
参考
使用 Jetpack Compose 更快地打造更出色的应用
Android Jetpack Compose —— 集成到现有项目中
要再见了吗,Kotlin Android Extension
扔物线compose
ChatGPT