Android kotlin Jetpack mvvm 项目,Android插件化主流框架和实现原理
emit(“value2”)
delay(1000)
emit(“value3”)
}
val flow = flowOf(“value”)
val flow = listOf(1, 2, 3).asFlow()
- 收集Flow----collect()
由于 collect 是挂起函数,因此需要在协程中执行
scope.launch {
flow.collect {
LogUtil.e(it)
}
}
- 转换Flow----map()
flowOf(1, 2, 3).map {
“第$it 个”
}.collect {
LogUtil.e(it)
}
- 过滤Flow----filter()
flowOf(1, 2, 3).filter {
it > 1
}.collect {
LogUtil.e(it)
}
- 合并Flow
zip操作符会把 flow1 中的一个 item 和 flow2 中对应的一个 item 进行合并,如果 flow1 中 item 个数大于 flow2 中 item 个数,合并后新的 flow 的 item 个数 = 较小的 flow 的 item 个数
val flow1 = flowOf(1, 2, 3, 4, 5)
val flow2 = flowOf(“一”, “二”, “三”, “四”, “五”, “六”)
flow1.zip(flow2) { a, b ->
“ a − − − a--- a−−−b”
}.collect {
LogUtil.e(it)
}
combine合并时,每次从 flow1 发出新的 item ,会将其与 flow2 的最新的 item 合并
val flow1 = flowOf(1, 2, 3, 4, 5).onEach { delay(1000) }
val flow2 = flowOf(“一”, “二”, “三”, “四”, “五”, “六”).onEach { delay(500) }
flow1.combine(flow2) { a, b ->
“ a − − − a--- a−−−b”
}.collect {
LogUtil.e(it)
}
- 捕获异常----catch()
flow {
emit(1)
emit(1 / 0)
emit(2)
}.catch {
it.printStackTrace()
}.collect {
LogUtil.e(it)
}
- 线程切换----flowOn()
withContext(Dispatchers.IO){
flowOf(1, 2, 3, 4).onEach {
//受到下面最近的flowOn控制-Main
LogUtil.e(“init—KaTeX parse error: Expected 'EOF', got '}' at position 33: …read().name}") }̲.filter { //受到下…{Thread.currentThread().name}”)
it > 1
}.flowOn(Dispatchers.Main).map {
//受到下面最近的flowOn控制-IO
LogUtil.e(“map— T h r e a d . c u r r e n t T h r e a d ( ) . n a m e " ) " 第 {Thread.currentThread().name}") "第 Thread.currentThread().name")"第it”
}.flowOn(Dispatchers.IO).map {
//受到下面最近的flowOn控制-Main
LogUtil.e(“第二次map— T h r e a d . c u r r e n t T h r e a d ( ) . n a m e " ) " {Thread.currentThread().name}") " Thread.currentThread().name")"it 个结果”
}.flowOn(Dispatchers.Main).collect {
//collect要看整个flow处于哪个线程,此处为IO
LogUtil.e(“collect—${Thread.currentThread().name}”)
LogUtil.e(it)
}
}
- 转为liveData----asLiveData()
添加依赖
“androidx.lifecycle:lifecycle-livedata-ktx:${LibraryVersion.LIVEDATA_KTX}”
flowOf(1, 2, 3, 4).asLiveData().observe(viewLifecycleOwner, Observer {
LogUtil.e(it)
})
更多操作符
本地保存 DataStore
DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。 参考鸿洋的公众号内容
SharedPreferences存在的问题
- 通过 getXXX() 方法获取数据,可能会导致主线程阻塞
- SharedPreference 不能保证类型安全
- SharedPreference 加载的数据会一直留在内存中,浪费内存
- apply() 方法虽然是异步的,可能会发生 ANR,在 8.0 之前和 8.0 之后实现各不相同
- apply() 方法无法获取到操作成功或者失败的结果
DataStore 解决了什么问题
- DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
- 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
- 没有 apply() 和 commit() 等等数据持久的方法
- 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
- 可以监听到操作成功或者失败结果
- 另外 Jetpack DataStore 提供了 Proto DataStore 方式,用于存储类的对象(typed objects ),通过 protocol buffers 将对象序列化存储在本地,protocol buffers 现在已经应用的非常广泛,无论是微信还是阿里等等大厂都在使用
DataStore使用
添加依赖
const val DATA_STORE = “1.0.0-alpha05”
const val PROTOBUF = “3.11.0”
“androidx.datastore:datastore-preferences: L i b r a r y V e r s i o n . D A T A S T O R E " / / p r o t o b u f 需下面的依赖 " a n d r o i d x . d a t a s t o r e : : d a t a s t o r e − c o r e : {LibraryVersion.DATA_STORE}" //protobuf需下面的依赖 "androidx.datastore::datastore-core: LibraryVersion.DATASTORE"//protobuf需下面的依赖"androidx.datastore::datastore−core:{LibraryVersion.DATA_STORE}”
“com.google.protobuf:protobuf-java:${LibraryVersion.PROTOBUF}”
保存键值对
object DataStore {
private const val APP_DATA_STORE_NAME = “APP_DATA_STORE_NAME”
private lateinit var dataStore: DataStore
fun init(context: Context) {
dataStore = context.createDataStore(APP_DATA_STORE_NAME)
}
suspend fun save(key: Preferences.Key, value: T) {
dataStore.edit {
it[key] = value
}
}
suspend fun get(key: Preferences.Key): T? {
val value = dataStore.data.map {
it[key]
}
return value.first()
}
}
保存
CoroutineScope(scope).launch {
DataStore.save(preferencesKey(“key1”), “aa”)
}
读取
CoroutineScope(scope).launch {
val get = DataStore.get(preferencesKey(“key1”))
}
保存protobuf
protobuf相关知识不在这里展开叙述
- 定义.proto文件
syntax = “proto3”;
option java_package = “com.haikun.jetpackapp.home.ui.demo.datastore.bean”;
option java_multiple_files = true;
message MessageEvent {
int32 type = 1;
string message = 2;
}
- 编译文件
编译后得到下图三个文件
- 定义Serializer
object MessageSerializer : Serializer {
override val defaultValue: MessageEvent
get() = MessageEvent.getDefaultInstance()
override fun readFrom(input: InputStream): MessageEvent {
return MessageEvent.parseFrom(input)
}
override fun writeTo(t: MessageEvent, output: OutputStream) {
t.writeTo(output)
}
}
- 保存
val createDataStore = context?.createDataStore(“data”, MessageSerializer)
createDataStore?.updateData {
it.toBuilder().setType(12).setMessage(“消息”).build()
}
- 读取
CoroutineScope(scope).launch {
context?.createDataStore(“data”, MessageSerializer)?.data?.first()?.let {
LogUtil.e(“ i t . t y p e − − − {it.type}--- it.type−−−{it.message}”)
}
}
声明式UI DataBinding
数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源,可以使用 LiveData 对象作为数据绑定来源,自动将数据变化通知给界面 。
声明式UI VS 命令式UI
-
声明式UI 只需要把界面给「声明」出来,而不需要手动更新,只要声明的数据发生了变化,UI就跟着变化
-
命令式UI 需要主动让UI更新,比如setText()
DataBinding使用
开启DataBinding
android {
…
dataBinding {
enabled = true
}
}
基本用法
- 在ViewModel中定义数据和方法
class DataBindingViewModel : ViewModel() {
val userName = MutableLiveData()
val clickTimes = MutableLiveData()
val sexCheckId = MutableLiveData()
val love = MutableLiveData()
fun save(){
LogUtil.e(“ u s e r N a m e . v a l u e − − − {userName.value}--- userName.value−−−{sex.value}—${love.value}”)
}
}
- xml中引用和调用
- Fragment
class DataBindingFragment : Fragment() {
private val mViewModel: DataBindingViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val dataBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_data_binding,
container,
false
)
//使用liveData必须要设置lifecycleOwner,否则无法更新数据
dataBinding.lifecycleOwner = viewLifecycleOwner
dataBinding.viewModel = mViewModel
return dataBinding.root
}
}
进阶用法
- BindingMethods 绑定方法名
@BindingMethods(value = [BindingMethod(type = MyButton::class, attribute = “maxTimes”, method = “setMaxTimes”)])
xml中使用
app:maxTimes=“@{15}”
- BindingAdapter 提供自定义逻辑
一些属性需要自定义绑定逻辑。例如,android:paddingLeft 特性没有关联的 setter,而是提供了 setPadding(left, top, right, bottom) 方法。使用 BindingAdapter 注释的静态绑定适配器方法支持自定义特性 setter 的调用方式。
object ViewAdapter {
@BindingAdapter(“minTimes”)
@JvmStatic
fun setMinTimes(view: MyButton, minTimes: Int) {
view.setMin(minTimes)
}
}
xml中使用
app:minTimes=“@{8}”
- 自定义双向绑定
@InverseBindingAdapter(attribute = “clickTimes”)
@JvmStatic
fun getClickTimes(view: MyButton): Int {
return view.clickTimes
}
@BindingAdapter(“clickTimesAttrChanged”)
@JvmStatic
fun setListener(view: MyButton, listener: InverseBindingListener?) {
view.onTimesChangeListener = {
listener?.onChange()
}
}
xml使用
app:clickTimes=“@={viewModel.clickTimes}”
Compose----android声明式UI未来的趋势
- 2019 年中,Google 在 I/O 大会上公布了 Android 最新的 UI 框架:Jetpack Compose。Compose 可以说是 Android 官方有史以来动作最大的一个库了。它在 2019 年中就公布了,但要到今年也就是 2021 年才会正式发布。这两年的时间 Android 团队在干嘛?在开发这个库,在开发 Compose。一个 UI 框架而已,为什么要花两年来打造呢?因为 Compose 并不是像 RecyclerView、ConstraintLayout 这种做了一个或者几个高级的 UI 控件,而是直接抛弃了我们写了 N 年的 View 和 ViewGroup 那一套东西,从上到下撸了一整套全新的 UI 框架。直白点说就是,它的渲染机制、布局机制、触摸算法以及 UI 的具体写法,全都是新的。
- 第一眼看到Compose,第一感觉就是觉得和Flutter的写法惊人的相似,Compose需要系统的学习,而且需要较高的学习成本,不在这里展开叙述
- Compose只支持kotlin,并且目前需要用Canary版本的android studio进行开发
数据库 Room
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库 Room可以和kotlin协程/flow结合使用 Room可以和LiveData结合使用
Room 包含 3 个主要组件:
- 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
- Entity:表示数据库中的表。
- DAO:包含用于访问数据库的方法。
添加依赖
implementation “androidx.room:room-runtime: r o o m v e r s i o n " k a p t " a n d r o i d x . r o o m : r o o m − c o m p i l e r : room_version" kapt "androidx.room:room-compiler: roomversion"kapt"androidx.room:room−compiler:room_version”
implementation “androidx.room:room-ktx:$room_version”
基本用法
- 定义Entity
@Entity
data class Car(
@PrimaryKey(autoGenerate = true) val id: Long,
var name: String,
val color: String,
)
@PrimaryKey 主键 类名就是表名,也可以在@Entity(table=)设置表名 可以使用@Ignore忽略某个字段
- 定义Dao
@Dao
interface CarDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCar(car: Car):Long
@Delete
fun deleteCar(car: Car)
@Update
fun updateCar(car: Car)
@Query(“SELECT * From Car”)
fun queryCarList(): MutableList
@Query(“Select * from Car where id=:id”)
fun queryCarById(id: Long): Car?
}
- 定义Database
@Database(entities = [Car::class], version = 1,exportSchema = false)
abstract class DemoDatabase : RoomDatabase() {
abstract fun carDao(): CarDao
}
- 创建Database和获取Dao
private val db: DemoDatabase by lazy {
Room.databaseBuilder(
JetpackApp.getContext(),
DemoDatabase::class.java, “demo-database”
).build()
}
private val carDao: CarDao by lazy {
db.carDao()
}
- 增删改查
carDao.insertCar(car)
carDao.delete(car)
carDao.updateCar(car)
val car = carDao.queryCarById(mUpdateId)
Room并不支持在主线程访问数据库, 除非在Builder调用allowMainThreadQueries()方法, 因为它很可能将UI锁上较长一段时间. 但是, 异步查询–返回LiveData/Flowable实例的查询–则从此规则中免除, 因为它们在需要的时候会在后台线程异步地运行查询.
- 使用Flow流进行响应式查询
只要表中的任何数据发生变化,返回的 Flow 对象就会再次触发查询并重新发出整个结果集。
使用 Flow 的响应式查询有一个重要限制:只要对表中的任何行进行更新(无论该行是否在结果集中),Flow 对象就会重新运行查询。通过将 distinctUntilChanged() 运算符应用于返回的 Flow 对象,可以确保仅在实际查询结果发生更改时通知界面:
@Query(“Select * From Car where id = :id”)
fun queryCarAsFlowById(id: Long): Flow
fun queryCarAsFlowByIdDistinctUntilChanged(id: Long): Flow =
queryCarAsFlowById(id).distinctUntilChanged()
- 使用 Kotlin 协程进行异步查询
将 suspend Kotlin 关键字添加到 DAO 方法中,以使用 Kotlin 协程功能使这些方法成为异步方法。这样可确保不会在主线程上执行这些方法。
- 使用 LiveData 进行可观察查询
@Query(“Select * From Car where id = :id”)
fun queryCarAsLiveDataById(id: Long): LiveData
对象之间的关系
嵌套关系
一对一
一对多
多对多
以一对多为例
- 定义Entity和关系
@Entity
data class One(@PrimaryKey(autoGenerate = true) val id: Long, val name: String)
@Entity
data class More(@PrimaryKey(autoGenerate = true) val id: Long, val oneId: Long, val name: String)
data class OneAndMore(
@Embedded val one: One,
@Relation(
parentColumn = “id”,
entityColumn = “oneId”
) val moreList: MutableList
)
- 定义Dao
添加 @Transaction 注释,以确保整个操作以原子方式执行。
@Transaction
open fun insertOneAndMore(){
val one = One(0, “OneName”)
val insertOneId = insertOne(one)
val more = More(0, insertOneId, “moreName1”)
val more1 = More(0, insertOneId, “moreName2”)
insertMore(more)
insertMore(more1)
}
@Transaction
@Query(“Select * from One”)
abstract fun queryOneAndMore():MutableList
使用类型转换器处理复杂数据
有时需要使用自定义数据类型,其中包含想要存储到单个数据库列中的值。TypeConverter可以在自定义类与 Room 可以保留的已知类型之间来回转换。
例如需要把一个包含List的对象保存到数据库
- 定义Entity
@Entity
data class ComplexEntity(
@PrimaryKey val id: Long,
val list: MutableList
)
- 定义Converter
class Converters {
@TypeConverter
fun fromJson(value: String): MutableList? {
val types =
Types.newParameterizedType(MutableList::class.java, OneAndMore::class.java)
return MoshiInstance.moshi.adapter(types).fromJson(value)
}
@TypeConverter
fun toJson(list: MutableList): String {
val types =
Types.newParameterizedType(MutableList::class.java, OneAndMore::class.java)
return MoshiInstance.moshi.adapter(types).toJson(list)
}
}
- 添加Converter到Database
@TypeConverters(Converters::class)
abstract class DemoDatabase : RoomDatabase()
- Dao
@Insert
abstract fun insertComplexEntity(complexEntity: ComplexEntity)
@Query(“Select * from ComplexEntity”)
abstract fun queryComplexEntity():MutableList
依赖注入 Koin
参考 依赖项注入 (DI) 是一种广泛用于编程的技术,Hilt、Dagger、Koin 等等都是依赖注入库,依赖注入是面向对象设计中最好的架构模式之一,使用依赖注入库有以下优点:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
要如何成为Android架构师?
搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;
对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;
最后我必须强调几点:
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境
本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
ndroid高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;
最后我必须强调几点:
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境
本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
- Dao
- 添加Converter到Database
- 定义Converter
- 定义Entity
- 定义Dao
- 定义Entity和关系
- 使用 LiveData 进行可观察查询
- 使用 Kotlin 协程进行异步查询
- 使用Flow流进行响应式查询
- 增删改查
- 创建Database和获取Dao
- 定义Database
- 定义Dao
- 定义Entity
- 自定义双向绑定
- BindingAdapter 提供自定义逻辑
- BindingMethods 绑定方法名
- Fragment
- xml中引用和调用
- 在ViewModel中定义数据和方法
-
- 读取
- 保存
- 定义Serializer
- 编译文件
- 定义.proto文件
- 转为liveData----asLiveData()
- 线程切换----flowOn()
- 捕获异常----catch()
- 合并Flow
- 过滤Flow----filter()
- 转换Flow----map()










