2024年了解Android架构组件后,构建APP超简单!(2),2024年最新阿里面试官迟到

最后

一线互联网Android面试题含详解(初级到高级专题)

这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率

如果设置门槛,很多开发者朋友会因此错过这套高级架构资料,错过提升成为架构师的可能。这就失去了我们的初衷;让更多人都能通过高效高质量的学习,提升自己的技术和格局,升职加薪。

最后送给大家一句话,望共勉,永远不要放弃自己的梦想和追求;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

private val _waveCode = MutableLiveData()
val waveCode: LiveData = _waveCode

内部用 MutableLiveData ,可以修改值,对外暴露成 LiveData 类型,只能获取值,不能修改值。

LiveData 有一个实现了中介者模式的子类 —— MediatorLiveData,它可以把多个 LiveData 整合成一个,只要任何一个 LiveData 有数据变化,它的观察者就会收到消息:

val liveData1 = …
val liveData2 = …

val liveDataMerger = MediatorLiveData<>();
liveDataMerger.addSource(liveData1) { value -> liveDataMerger.setValue(value))
liveDataMerger.addSource(liveData2) { value -> liveDataMerger.setValue(value))

综上,我们汇总一下 LiveData 的使用场景:

  • LiveData - immutable 版本
  • MutableLiveData - mutable 版本
  • MediatorLiveData - 可汇总多个数据源
  • SingleLiveEvent - 事件总线

LiveData 只存储最新的数据,虽然用法类似 RxJava2 的 Flowable,但是它不支持背压(backpressure),所以不是一个流(stream),利用 LiveDataReactiveStreams 我们可以实现 Flowable 和 LiveData 的互换。

如果把异步获取到的数据封装成 Flowable,通过 toLiveData 方法转换成 LiveData,既利用了 RxJava 的线程模型,还消除了 Flowable 与 UI Controller 生命周期的耦合关系,借助 Data Binding 再把 LiveData 绑定到 xml UI 元素上,数据驱动 UI,妥妥的响应式。于是一幅如下模样的数据流向图就被勾勒了出来:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图中右上角的 Local Data 是 AAC 提供的另一个强大武器 —— ORM 框架 Room。

Room

数据库作为数据持久层,其重要性不言而喻,当设备处于离线状态时,数据库可用于缓存数据;当多个 App 需要共享数据时,数据库可以作为数据源,但是基于原生 API 徒手写 CRUD 实在是痛苦,虽然 Github 上出现了不少 ORM 框架,但是它们的易用性也不敢让人恭维,直到 Room 出来之后,Android 程序员终于可以像 mybatis 那样轻松地操纵数据库了。

Room 是 SQLite 之上的应用抽象层,而 SQLite 是一个位于 Android Framework 层的内存型数据库。虽然 Realm 也是一个优秀的数据库,但是它并没有内置于 Android 系统,所会增大 apk 的体积,使用 Room 则没有这方面烦恼。

Room 的结构抽象得非常简单,数据对象(表名 + 字段)用 @Entity 注解来定义,数据访问用 @Dao 来注解,db 本身则用 @Database 来定义,如果要支持复杂类型,可以定义 @TypeConverters,然后在编译阶段,apt 会根据这些注解生成代码。Room 与 App 其他部分的交互如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Entity 是一个数据实体,表示一条记录,它的用法如下:

@Entity(tableName = “actors”)
data class Actor(
@PrimaryKey @ColumnInfo(name = “id”)
val actorId: String,
val name: String,
val birthday: Date?,
val pictureUrl: String
)

Actor 是一个用 @Entity 注解的 data class,它会生成一个名字是 actors 的表,注意到有一个字段是 @Date? ,但是 SQLite 本身不支持这种复杂类型(complex type),所以我们还需要写一个可以转换成基础类型的转换器:

class Converters {
@TypeConverter
fun timestampToDate(value: Long?) = value?.let { Date(it) }

@TypeConverter
fun dateToTimestamp(date: Date?) = date?.time
}

转换器通过 @TypeConverters 可作用于 class、field、method、parameter,分别代表不同的作用域。比如作用在 @Database 类的上,那么它的作用域就是 db 中出现的所有 @Dao 和 @Entity。

@Database(entities = [Actor::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun actorDao(): ActorDao
}

代码出现的 ActorDao 定义了 CRUD 操作。用 @Dao 来注解,它既可以是一个接口,也可以是抽象类,用法如下:

@Dao
interface ActorDao {
@Query(“SELECT * FROM actors WHERE id = :actorId”)
fun getActor(actorId: String): LiveData

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(actors: List)
}

@Query 中的 SQL 语句可以直接引用方法参数,而且它的返回值可以是 LiveData 类型,也支持 Flowable 类型,也就是说,Room 原生支持响应式,这是对数据驱动最有利的支持,也是 Room 区别于其他 ORM 框架的显著特征。

至此,我们可以确定,无论数据来自 Remote 还是来自本地 DB,架构蓝图中的 Repository 对 ViewModel 提供的数据可以永远是 LiveData 类型,接下来我们看一下 ViewModel 的妙用。

ViewModel

ViewModel 是一个多面手,因为它的生命周期比较长,可以跨越因为配置变动(configuration changed,比如屏幕翻转)引起的 Activity 重建,因此 ViewModel 不能持有对 Activity / Fragment 的引用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果 ViewModel 中要用到 context 怎么办呢?没关系,框架提供了一个 ViewModel 的子类 AndroidViewModel ,它在构造时需要传入 Application 实例。

既然 ViewModel 与 UI Controller 无关,当然可以用作 MVP 的 Presenter 层提供 LiveData 给 View 层,因为 LiveData 绑定了 Lifecycle,所以不存在内存泄露的问题。除此之外,ViewModel 也可以用做 MVVM 模式的 VM 层,利用 Data Binding 直接把 ViewModel 的 LiveData 属性绑定到 xml 元素上,xml 中声明式的写法避免了很多样板代码,数据驱动 UI 的最后一步,我们只需要关注数据的变化即可,UI 的状态会自动发生变化。

ViewModel 配合 Data Binding 的用法与 React 非常相似,ViewModel 实例相当于 state,xml 文件就好比 render 函数,只要 state 数据发生变化,render 就会重新渲染 UI,但是 data binding 还有更强大的一点,它支持双向绑定。举个例子,UI 需要展示一个评论框,允许展示评论,也允许用户修改,那么我们可以直接把 EditText 双向绑定到一个 LiveData 之上,只要用户有输入,我们就可以收到通知,完全不需要通过 Kotlin/Java 来操控 UI:

android:text=“@={viewModel.commentText}” />

注意,如果要在 xml 中使用 LiveData,需要把 lifecycle owner 赋给 binding:

val binding: MainBinding = DataBindingUtil.setContentView(this, R.layout.main)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)

因为 ViewModel 拿到的数据是 Repository 给的,可能不适用于 UI 元素,所以 ViewModel 还承担了数据适配的工作,有时候我们需要汇总 repository 的多个返回值一次性给到 UI,那么就可以使用 LiveData 的“操作符” Transformations.switchMap,用法可以认为等同于 Rx 的 flatMap;如果只想对 LiveData 的 value 做一些映射,可以使用 Transformations.map,目前 Transformations 只有这两个操作符,因为不管 Kotlin 还是 Java8,都提供了很多声明式的操作符,对流的支持都比较友好,而 LiveData 本身不是一个流,所以这两个操作符足矣。

除了数据适配之外,ViewModel 还有一个强大的用法 —— Fragment 之间共享数据,这样 ViewModel 又扮演了 FLUX 模式中的 store 这一角色,是多个页面(fragment)之间唯一的数据出口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ViewModel 的用法也非常简单,通过 ViewModelProviders.of 可以获取 ViewModel 实例:

.get(ActorViewModel::class.java)

一通操作猛如虎之后,UI controller 层变得薄如蝉翼,它只做了一件事情,把数据从左手(ViewModel)倒给了右手(使用了 Data Binding 的 xml)。

如果把 ViewModel 作为 SSOC(唯一真相源),多个 Fragment 之间共享数据,再利用 SingleLiveEvent 做总线,一个 Activity 配多个 Fragment 的写法就避免了 Activity 之间通过 Intent 传递数据的繁琐。但是 Fragment 的堆栈管理一直是一个让人头疼的问题,AAC 的 Navigation 不但完美解决了这个问题,而且还提供可视化的路由,只需拖拽一下就能生成类型安全的跳转逻辑。

Navigation

Navigation 用一个图(graph)来表示页面间的路由关系,图的节点(node)表示页面,边(edge)表示跳转关系。例如下图 8 个页面的跳转关系,一目了然:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

页面与页面之间的连线叫 action,它可以配置进离场动画(Animations),也可以配置出栈行为(Pop Behavior),还支持 Single Top 的启动选项(Launch Options)。进离场动画和启动选项很好理解,出栈行为是一个比较强大的功能,action 箭头所指的方向表示目标页面入栈,箭头的反方向则表示目标页面出栈,而出栈的行为在 Navigation 编辑器中完全可控,我们可以指定要出栈到哪个页面,甚至可以指定目标页面是否也需要出栈:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

针对页面节点,还可以定义它要接收的参数(arguments),支持默认值,从此 Fragment 之间的参数传递变得非常直观,非常安全。

看一下具体用法,首先在跳转发起页面,通过 apt 生成的跳转函数传入参数:

val direction = ActorListFragmentDirections.showDetail(actorId)
findNavController().navigate(direction)

然后利用目标页面生成的 *Args 获取参数:

private val args: ActorDetailFragmentArgs by navArgs()

这里的 navArgs 是一个扩展函数,利用了 Kotlin 的 ReadWriteProperty。

几行代码就搞定了页面之间的跳转,而且还是可视化!从没有想过 Android 的页面跳转竟会变得如何简单,但是 Navigation 的方案并不是原创,iOS 的 Storyboard 很早就支持拖拽生成路由。当年 Android 推出 ConstraintLayout 之时,我们都认为是参考了 Storyboard 的页面拖拽,现在再配上 Navigation,从页面到跳转,一个完整的拖拽链路就形成了。平台虽然有差异化,但是使用场景一致的前提下,解决方案也就殊途同归了。

了解完了与生命周期有关的组件,接下来我们来看细节。

Paging

UI 没有办法一次性展示所有的数据,端上的系统资源(电量、内存)也有限制,不可能把所有数据都加载到内存中;而且大批量请求数据不但浪费带宽,在某些网络情况(弱网、慢网)下还会导致请求失败,所以分页是很多情景下的刚需。Github 上有各式各样的解决方案,这一次,Google 直接推出了官方的分页组件——Paging。

Paging 将分页逻辑拆解为三部分:

  • 数据源 DataSource
  • 数据块 PagedList
  • 数据展示 PagedListAdapter

DataSource 的数据来源于后端服务或者本地数据库,并且用三个子类来表示三种分页模式:

  • PageKeyedDataSource - 单页数据以 page key 为标识,例如当前页的 Response 中包含了下一页的 url,这个 url 就是 page key。
  • ItemKeyedDataSource - 单页数据以 item key 为标识,比如下一页的请求要带当前页最后一个 item 的 id,这个 itemId 就是 item key。
  • PositionalDataSource - 单页数据以位置为标识,这种模式比较常见,Room 只支持这一种,因为数据库查询以 OFFSET 和 LIMIT 做分页。

PageKeyedDataSource 和 ItemKeyedDataSource 适用于内存型数据,比如直接从后端获取后需要展示的数据。PositionalDataSource 适用于本地 Room 数据或者使用 Room 做缓存的 Cache 数据。

数据流向的关系图如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

LivePagedListBuilder 利用 DataSource.Factory 和 PageList.Config 创建 LiveData,UI Controller 拿到数据之后交给 PagedListAdapter 展示到 RecyclerView。

上图表达了数据的流向,如果从 UI 层往回看,页面展示的数据存储在 PagedList 中,PagedList 只是 DataSource 的数据块(chunk),当 PagedList 需要更多数据时,DataSource 就会给更多,当 DataSource 一无所有时便会触发 BoundaryCallback 获取更多数据,直到数据全部展示完毕。

结语

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是目录截图:

由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

再附一部分Android架构面试视频讲解:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值