安卓后端开发-移动端开发与后端开发
前言
一旦疫情结束,我相信这将是求职面试的高峰期。 如果这时候你手头有一本高质量的面试书,面对考官的各种提问一定游刃有余。 虽然我不能保证你一定能申请到你想要的职位,但我可以保证,看完这些内容,你的收获将超乎你的想象! 这本访谈书收录了各大网络平台(如有侵权请告知),谢谢他们的辛勤劳动总结出这本足够全面的访谈书!
内容丰富,建议找工作的朋友一定要抽空仔细阅读。 我会在这里随意展示,我保证不会让你失望!
一、MVVM架构模式概述
这是一个使用MVVM架构模型+Kotlin协程+JetPack(ViewModel+LiveData)+Retrofit架构实现WanAndroid登录界面的小DEMO,后续会逐步完善WanAndroid客户端
1.视图模型
为了将视图数据所有权与界面控制器Activity/Fragment逻辑分离,架构组件为界面控制器提供了一个ViewModel辅助程序类,它负责为界面准备数据。 ViewModel 对象在配置更改期间自动保留,以便它们存储的数据可立即用于下一个 Activity 或 Fragment 实例。
2. 实时数据
LiveData 是一个具有生命周期感知能力的可观察数据存储类,这意味着它遵循其他应用组件的生命周期,例如 Activity、Fragment 或 Service,这保证了 LiveData 只更新处于活跃生命周期状态的应用组件观察者。 LiveData 对象通常存储在 ViewModel 对象中,可以通过 getter 方法访问。
3. Kotlin协程
协程依附于线程,可以实现异步代码的顺序编写和线程的自动切换。 并且 ViewModelScope 为应用程序中的每个 ViewModel 定义了一个 ViewModelScope。 如果 ViewModel 被清除,则在此范围内启动的协程将自动取消。
4.改造
将服务接口中的网络请求函数声明为suspend挂起接口函数,以支持Kotlin线程,并将挂起函数的结果作为LiveData对象传输。
2.视图模型
//获取ViewModel
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)`
ViewModel对象存在的时间范围是获取ViewModel时传递给ViewModelProvider的Lifecycle。 ViewModel 将保留在内存中,直到限制其生命周期的生命周期永久消失:对于 Activity,当 Activity 完成时; 对于 Fragment,当 Fragment 分离时。
3. 实时数据
//对User数据进行观察
viewModel.user.observe(this, Observer {
//展示登录结果
if (it.errorCode == 0) {
Toast.makeText(this, it.data?.nickname, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, it.errorMsg, Toast.LENGTH_SHORT).show()
}
})
使用 LiveData 有以下优点: 确保接口符合数据的状态
LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。观察者可以在每次发生更改时更新界面,而不是在每次应用数据发生更改时更新界面。
没有内存泄漏
观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
Activity 停止时不会崩溃
如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
不再需要手动处理生命周期
界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
数据始终是最新的
如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
适当的配置更改
如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
共享资源
可以使用单一实例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。
4. Kotlin协程4.1、异步的本质
什么是异步?
异步就是同时进行一个以上彼此目的不同的任务。
但是对于有前后端依赖的任务,如何处理异步呢?
利用异步中的回调机制处理。
为什么需要异步回调机制?
因为不同的任务之间存在前后的依赖关系。
异步回调机制有什么缺点?
代码结构过分耦合,遇到多重函数回调的嵌套耦合,也就是回调地狱,代码会难以维护。
回调地狱的解决方案是什么?
链式调用结构。
常见方式就是使用RxJava,它是反应函数式编程在Java中的实现。
但是RxJava中流的创建、转化与消费都需要使用到各种类和丰富的操作符,加大了RxJava的学习成本。
减少在无封装情况下使用RxJava,因为你无法保证团队里面的每一个成员都能看懂它,并且在修改时都能做出正确选择。
在串行执行中,虽然代码确实是顺序执行的,但实际上是在不同的线程上顺序执行的。 那为什么在串行执行中,代码是按相同的顺序执行的,却仍然使用回调呢?
因为串行的执行中,执行是阻塞式的,主线程的阻塞会导致很严重的问题,所以所有的耗时操作不能在主线程中执行,所以就需要多线程并行来执行。
在并行执行中,异步回调实际上是代码的多线程顺序执行。 代码能不能按顺序写,代码在不同的线程中顺序执行,自动完成线程切换工作?
那就是Kotlin协程。
Kotlin 的协程是一种无栈协程的实现,它的控制流转依靠对协程体本身编译生成的状态机的状态流转来实现,变量保存也是通过闭包语法来实现的。
综上所述:
异步回调就是代码的多线程顺序执行,而Kotlin协程可以实现顺序编写异步代码,自动进行线程切换。
那么协程自动线程切换的原理是什么?
Yield:让出CPU,放弃调度控制权,回到上一次Resume的地方
Resume:获取调度控制权,继续执行程序,到上一次Yield的地方
例子:
1. GlobalScope.launch发起了一个协程,并在IO线程上执行,
2\. 在协程里,去调用接口获取结果。
3. 拿到结果,使用withContext(Dispatchers.Main)切换到主线程并更新界面
4.2. 协程的类型
是协程范围,指的是协程内的代码运行的时间周期范围,如果超出了指定的协程范围,协程会被取消执行。
全球视野
指的是与应用进程相同的协程范围,也就是在进程没有结束之前协程内的代码都可以运行。
JetPack 中提供的生命周期感知协程范围:
ViewModelScope,为应用中的每个 ViewModel 定义了 ViewModelScope。如果 ViewModel 已清除,则在此范围内启动的协程都会自动取消。
LifecycleScope,为每个 Lifecycle 对象定义了 LifecycleScope。在此范围内启动的协程会在 Lifecycle 被销毁时取消。
使用 LiveData 时,可能需要异步计算值。可以使用 liveData 构建器函数调用 suspend 函数,并将结果作为 LiveData 对象传送。
4.3. 协程的开始
发射方式:
/**
* 重要知识:ViewModel+协程
*/
fun ViewModel.launch(
block: suspend CoroutineScope.() -> Unit,
onError: (e: Throwable) -> Unit = {},
onComplete: () -> Unit = {}
) {
viewModelScope.launch(CoroutineExceptionHandler { _, e -> onError(e) }) {
try {
block.invoke(this)
} finally {
onComplete()
}
}
}
源代码:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
4.3.1、启动方式说明
语境
协程上下文,可以指定协程运行的线程。默认与指定的CoroutineScope中的coroutineContext保持一致,比如GlobalScope默认运行在一个后台工作线程内。也可以通过显示指定参数来更改协程运行的线程,Dispatchers提供了几个值可以指定:Dispatchers.Default、Dispatchers.Main、Dispatchers.IO、Dispatchers.Unconfined。
开始
协程的启动模式。默认的CoroutineStart.DEFAULT是指协程立即执行,除此之外还有CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED。
堵塞
协程主体。也就是要在协程内部运行的代码,可以通过lamda表达式的方式方便的编写协程内运行的代码。
协程异常处理器
指定CoroutineExceptionHandler来处理协程内部的异常。
工作
返回值,对当前创建的协程的引用。可以通过Job的start、cancel、join等方法来控制协程的启动和取消。
4.4、suspend挂起函数
suspend关键字只是用来标记这个函数是一个耗时操作,必须在协程中执行,而withContext方法则进行线程切换。
协程中的代码自动切换到其他线程,然后又自动切换回主线程! 顺序编写保证逻辑直觉安卓后端开发,协程自动线程切换保证代码的非阻塞性。 挂起函数必须在协程或其他挂起函数中调用安卓后端开发,即挂起函数必须在协程中直接或间接执行。
那为什么协程中的代码没有在主线程中执行呢? 还有为什么执行完会自动切换回主线程呢?
协程的挂起可以理解为协程中的代码离开协程所在线程的过程,协程的恢复可以理解为协程中的代码重新进入所在线程的过程协程位于。 协程通过这种挂起恢复机制来切换线程。
4.5、async await方法
使用async方法包裹suspend方法执行并发请求。 并发结果返回后,切换到主线程,然后使用await方法获取并发请求结果。
5.改造