全面了解Kotlin,2021大厂Android高级面试题及答案
Posted m0_66264856
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全面了解Kotlin,2021大厂Android高级面试题及答案相关的知识,希望对你有一定的参考价值。
val str : String = “test”
根据上图,我们需要注意两点:
2.1.val等于final
我们可以看到var定义的num可以被重新赋值,str却不可以。上图的val实际上就等于Java中的final String,也就是val定义的变量默认添加了final关键字。
2.2.可空?以及空匹配
第二点就是num变量在定义为Int的时候是不能赋值为Null的,如果需要我们需要这么定义
var num : Int?
2.3.类型推断
val str = “”
kotlin具有类型推断功能,上面的语句等于Java
final String str = “”
自定义getter&setter
var msg: String
get() = field
set(value)
field = value
3.函数
3.1.定义
3.1.1普通函数
定义一个名为test函数,返回值为String?,可能返回为空
fun test(): String?
return null
调用:
调用和Java类似,由于test返回的是可空的字符串,添加?:表示当前面为空取冒号后面的值。
val result = test() ?: “x”
也可以
fun isEmpty(str: String) = str.isEmpty()
其中isEmpty函数的返回值为后面isEmpty()的返回值。
3.1.2默认参数
kotlin支持带默认参数的函数,默认参数不传则为默认值。
data class EnvConfig(val baseUrl: String, val isDebug: Boolean = false)
//构造1 等于 EnvConfig(“https://xx.com”,false)
val env1 = EnvConfig(“https://xx.com”)
3.1.3命名参数
kotlin方法调用可以指明参数名称,以避免混淆。更加直观。
EnvConfig(
baseUrl = “https://xx2.com”,
isDebug = true
)
3.2顶层函数和属性
kotlin可以定义全局可以调用的工具函数,它会编译成该文件的静态方法以供调用。
TopFunc.kt
fun toString(obj:Any) = obj.toString()
翻译成Java类
public final class TopFuncKt
@NotNull
public static final String toString(@NotNull Object obj)
Intrinsics.checkParameterIsNotNull(obj, “obj”);
return obj.toString();
同理顶层属性
var count = 0
Java
public static final int getCount()
return count;
public static final void setCount(int var0)
count = var0;
3.3 给别人的类添加方法: 拓展函数和属性
3.3.1拓展函数
拓展函数非常简单,它就是一个类的成员函数。
TopFunc.kt
//定义一个成员函数 方法内的this会指向被拓展的对象。
//即这里的this是这个字符串
fun String.print() = println(this)
//使用
“string extension”.print()
//输出
string extension
拓展函数也是顶层函数,所以它在Java中也是静态函数,调用如下:
TopFuncKt.print(“extension in Java”);
拓展函数仅仅是为了缩短语法而存在。并非真正意义上的"拓展",也做不到真正的拓展,所以拓展函数无法进行重写,或者在Java中当作成员函数来调用。
3.3.2拓展属性
类似拓展函数,拓展属性提供了一种方法,用来拓展类的API,可以用来访问属性,用的是属性语法而不是函数的语法。**尽管它们被称为属性,但他们可以没有任何状态,也没有合适的地方来存储它们,**不能给现有的Java对象的实例添加额外的字段。
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char)
setCharAt(length - 1, value)
**上面的代码只是利用拓展属性提供了一种快捷访问该类成员方法的途径,但是并没有给StringBuilder这个类添加lastChar这个属性。**尽管如此,拓展属性依旧十分实用,比如android中常用的Float转换为dp:
val Float.dp
get() = get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)
3.4 可变参数和中缀调用
vararg:可变参数。只需要注意一点
fun test(vararg string: String)
test2(string)//这里传递可变参数需要添加
fun test2(vararg string: String)
中缀调用:
//允许使用中缀符号调用函数,函数会返回一个Pair对象
infix fun Any.with(other:Any) = Pair(this,other)
val c = “3” with 4 // c为pair对象
val (f, s) = “3” with 4 //val (f,s)称为解构声明,将Pair的first给f,second给s.
println(“f:
f
,
s
:
f,s:
f,s:s”)
//输出结果
f:3,s:4
4.程序逻辑控制
4.1带返回值的if
kotlin没有三目运算符,取而代之的是带返回值的if语句。而且是把每一个条件中的最后一行代码作为返回值
//Kotlin 中把每一个条件中的最后一行代码作为返回值
fun largeNumber(number1: Int,number2: Int) : Int
return if(number1 > number2)
number1
else
number2
//根据上面学习的语法糖和 Kotlin 类型推导机制,我们还可以简写 largeNumber 函数,最终变成了这样
fun largeNumber(number1: Int,number2: Int) = if(number1 > number2) number1 else number 2
4.2带返回值的when
类比 Java 中的 Switch 语句学习,Java 中的 Switch 并不怎么好用:
①Switch 语句只能支持一些特定的类型,如整型,短于整型,字符串,枚举类型。如果我们使用的并非这几种类型,Switch 并不可用
②Switch 语句的 case 条件都要在最后加上一个 break
这些问题在 Kotlin 中都得到了解决,而且 Kotlin 还加入了许多强大的新特性:
③when 条件语句也是有返回值的,和 if 条件语句类似,条件中的最后一行代码作为返回值
④when 条件语句允许传入任意类型的参数
⑤when 条件体中条件格式:匹配值 -> 执行逻辑
⑥when 条件语句和 if 条件语句一样,当条件体里面只有一行代码的时候,条件体的 可省略
//when 中有参数的情况
fun getScore(name: String) = when (name)
“tom” -> 99
“jim” -> 80
“lucy” -> 70
else -> 0
//when 中无参数的情况,Kotin 中判断字符串或者对象是否相等,直接使用 == 操作符即可
fun getScore(name: String) = when
name == “tom” -> 99
name == “jim” -> 80
name ==“lucy” -> 70
else -> 0
4.3循环语句
kotlin中主要有两种循环语句:while和for-in。其中while和Java中使用一致。
而kotlin中的for-in则比Java中更为方便易用。
for-in:
//使用 … 表示创建两端都是闭区间的升序区间[0,10]
for (i in 0…10)
print("$i ")
//打印结果
0 1 2 3 4 5 6 7 8 9 10
for-until:
//使用 until 表示创建左端是闭区间右端是开区间的升序区间[0,10)
for (i in 0 until 10)
print("$i ")
//打印结果
0 1 2 3 4 5 6 7 8 9
for-downTo:
//使用 downTo 表示创建两端都是闭区间的降序区间[10,0]
for (i in 10 downTo 0)
print("$i ")
//打印结果
10 9 8 7 6 5 4 3 2 1 0
步进:
//使用 downTo 表示创建两端都是闭区间的降序区间,每次在跳过3个元素
for (i in 10 downTo 0 step 3)
print("$i ")
迭代list
//使用withIndex迭代list
val list = arrayListOf(“10”,“11”,“100”)
for ((index,element) in list.withIndex())//解构申明
println(“
i
n
d
e
x
:
index:
index:element”)
//打印结果
0:10
1:11
2:100
迭代map
val map = mapOf(1 to “Java”, 2 to “Kotlin”, 3 to “Flutter”)//中缀调用
for ((key, value) in map) //解构
println(“
k
e
y
:
key:
key:value”)
//打印结果
1:Java
2:Kotlin
3:Flutter
5.类
和Java一样,类的定义如下
class Base
var num = 1
但是意义却不太一样。
5.1可见性
**Kotlin中,默认类的可见性为public以及final的。**内部类默认为static的,用inner标记非静态内部类。
①Kotlin 中可见性
private
:私有,本类内部可见protected
:子类可见internal
:模块内可见public
:默认,公有
②对比 Java
private
:私有,本类内部可见protected
:子类可见default
:默认,包内可见public
:公有
单个构造函数&私有构造
class Response private constructor(val code: Int, val msg: String)
override fun toString(): String
return “code:
c
o
d
e
,
m
s
g
:
code,msg:
code,msg:msg”
多个构造函数
//非open不可被继承
class Response
val code: Int
val msg: String
constructor(code: Int)
this.code = code
msg = “”
constructor(code: Int, msg: String)
this.code = code
this.msg = msg
override fun toString(): String
return “code:
c
o
d
e
,
m
s
g
:
code,msg:
code,msg:msg”
其中code和msg的getter会自动生成。
kotlin中也是单继承多实现,共同存在时,继承类写到第一位,后面追加逗号跟上接口接口。
public class String : Comparable, CharSequence
…
密封类(private构造,默认open)
sealed class Expr
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
5.2Object关键字
①类单例
object SingleTon
@JvmStatic
fun isEmpty(str: String) = str.isEmpty()
反编译成Java
public final class SingleTon
public static final SingleTon INSTANCE;
@JvmStatic
public static final boolean isEmpty(@NotNull String str)
Intrinsics.checkParameterIsNotNull(str, “str”);
CharSequence var1 = (CharSequence)str;
boolean var2 = false;
return var1.length() == 0;
private SingleTon()
static
SingleTon var0 = new SingleTon();
INSTANCE = var0;
根据面可以看出,object单独修饰一个类,表示为静态代码块实现的单例。
@JvmStatic修饰的方法,kotlin会在其方法上添加static关键字,而static关键字在kotlin中是不存在的。
②匿名类
test(object : Listener
)
interface Listener
kotlin中没有new关键字,所以匿名类用object实现。
③伴生对象
kotlin没有static,那么如何实现静态变量以及常量等的定义和使用呢?
答案是伴生对象
class UserManager
companion object
val USER_TYPE = 0x01
上面的companion object会生成一个内部类Companion,并添加返回USER_TYPE的静态getter,如下
public final class UserManager
private static final int USER_TYPE = 1;
public static final UserManager.Companion Companion = new UserManager.Companion((DefaultConstructorMarker)null);
public static final class Companion
public final int getUSER_TYPE()
return UserManager.USER_TYPE;
…
PS:const关键字
const关键字只能用在静态类中, 只能与val连用,即const val,而且只能修饰基本类型。意义为编译期常量,在用到的地方替换为该常量的值。如下:
object SingleTon
const val str = “const”
fun test(): String?
return SingleTon.str
其中
test反编译Java如下:
public final String test()
return “const”;
可以看到kotlin对const常量做了内联。
5.3类委托
equals
在Java中,可以使用来比较基本数据类型和引用类型,基本数据类型比较的是值,引用类型上比较的是引用。在kotlin中就等于调用Java中的equals。如果需要比较引用则需要用===。
by关键字
装饰器模式的代码通常就较为模板,kotlin中可以利用by关键字来实现类的委托。比如:
class MyList : List by ArrayList()
//这里面默认利用ArrayList实现了List的所有接口
转换成Java:
public final class MyList implements List, KMappedMarker
// $FF: synthetic field
private final ArrayList KaTeX parse error: Expected '', got 'EOF' at end of input: … return this.delegate_0.get(index);
…
当然,我们也可以通过重写来实现自己的逻辑。
by也可以用来实现延迟加载:
private val arr by lazy MyList()
它的实现是double-check的懒加载方式,如下:
private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get()
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE)
@Suppress(“UNCHECKED_CAST”)
return _v1 as T
return synchronized(lock)
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE)
@Suppress(“UNCHECKED_CAST”) (_v2 as T)
else
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else “Lazy value not initialized yet.”
private fun writeReplace(): Any = InitializedLazyImpl(value)
6.lambda
6.1语法
lambda表达式,本质上就是可以传递给其他函数的一小段代码。
kotlin中简单lambda:
button.setOnclickListener
kotlin中lambda始终被花括号包围。可以把lambda表达式存储在变量中:
val sum = x:Int,y:Int -> x + y
println(sum(1,2))
3
kotlin中,lambda作为最后一个参数可以把它放到()后面如下1;如果只有lambda作为参数,可以省略(),如下2。
list.maxBy(it.length)
list.maxBy()it.length//1
list.maxByit.length//2
6.2集合函数API
比如list过滤:
val list = arrayListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list.filter it % 2 == 0
.forEach print(it)
//打印结果
246810
类似的还有:
all:全部匹配返回true
any:存在一个匹配返回true
count:返回匹配的数量
firstOrNull:第一个匹配,没有返回null
first:第一个匹配,没有抛出异常
find:找到第一个匹配 等于 firstOrNull
还有map,flatmap,groupby…
基本涵盖了RxJava的常用操作符。
apply,let,also…
apply改变this指向调用者。方便各种操作,返回调用者
val str = “123456”
val rec = str.apply
println(“lastChar:KaTeX parse error: Expected 'EOF', got '' at position 20: …(lastIndex)") ̲ println("rec:rec”)
//打印结果
lastChar:6
rec:123456
with改变this指向参数,返回lambda最后一行结果
let创建局部变量,返回最后一行结果。
val str :String ? = “123456”
val res = str?.let
println(“it:KaTeX parse error: Expected 'EOF', got '' at position 15: it") "return" ̲ println("res:res”)
//打印结果
it:123456
res:return
also:创建it,返回调用者
run:改变this指向调用者,返回最后一行
类似的语法糖takeIf,repeat等等,都在Standard.kt中有定义。
二、深入理解
1.kotlin类型系统
1.1可空性
kotlin中类型定义如果没有添加为可空,当它接受到一个null时,kotlin会在运行时抛出ERROR:Type mismatch的错误。
当一个类规定为可空,可以使用安全调用?.,后面可以跟上Elvis运算符?:。标识在前面?.调用者为null时执行。
val str :String ? = “123456”
str?.get(str.lastIndex) ?: toast(“str is null”) //toast会在str为null时执行
安全转换:as?
is:检查类型,可以自动转型
val obj: Any = “str”
if (obj is String)
println(“obj:$obj”)//obg自动转型为string
//打印结果
obj:str
as:类型转换,as?安全类型转换
val obj: Any = “str”
(obj as? String)?.print()//在obj为String时才会执行后面的语句,print为本地定义的拓展函数
//打印结果
str
!!非空断言
让kotlin在对象为空的时候抛出异常。
val obj: Any? = “str”
obj!!.toString()
obj.hashCode()//不需要再加!!,kotlin编译器自动检查
2.运算符重载
data class Point(val x: Int, val y: Int)
operator fun plus(other: Point): Point
return Point(x + other.x, y + other.y)
val p1 = Point(1,2)
val p2 = Point(5,6)
val p = p1 + p2
println(“p:$p”)//自动调用toString
//打印结果
p:Point(x=6, y=8)
可用于重载的运算符:
表达式 | 函数名 |
---|---|
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
PS:位运算也有自己的符号
运算符 | 操作 |
---|---|
shl | 带符号左移 |
shr | 带符号右移 |
ushr | 无符号右移 |
and | 按位与 |
or | 按位或 |
xor | 按位异或 |
inv | 按位取反 |
3.lambda作为形参
无其他参数:
fun exec(func:()->Unit)
func.invoke()
exec
println(“hello world”)
带其他参数:
fun exec(msg: String,func:(msg:String)->Unit)
func.invoke(msg)
exec(“hello world”) msg->
println(msg)
以上的lambda是作为不能为空的形参。如果为空,需要将其定义用()?包裹。如下:
fun exec(msg: String,func:((msg:String)->Unit)?)
func?.invoke(msg)
lambda作为参数传递虽然好,但是其实现传递的还是对象(匿名类),在每一次调用都会创建一个对象,如何避免这部分开销提升性能?
答案是内联函数。
al p = p1 + p2
println(“p:$p”)//自动调用toString
//打印结果
p:Point(x=6, y=8)
可用于重载的运算符:
表达式 | 函数名 |
---|---|
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
PS:位运算也有自己的符号
运算符 | 操作 |
---|---|
shl | 带符号左移 |
shr | 带符号右移 |
ushr | 无符号右移 |
and | 按位与 |
or | 按位或 |
xor | 按位异或 |
inv | 按位取反 |
3.lambda作为形参
无其他参数:
fun exec(func:()->Unit)
func.invoke()
exec
println(“hello world”)
带其他参数:
fun exec(msg: String,func:(msg:String)->Unit)
func.invoke(msg)
exec(“hello world”) msg->
println(msg)
以上的lambda是作为不能为空的形参。如果为空,需要将其定义用()?包裹。如下:
fun exec(msg: String,func:((msg:String)->Unit)?)
func?.invoke(msg)
lambda作为参数传递虽然好,但是其实现传递的还是对象(匿名类),在每一次调用都会创建一个对象,如何避免这部分开销提升性能?
答案是内联函数。
超长文,带你全面了解Kotlin的协程
https://me.csdn.net/NJP_NJP
1. 什么是异步
我记得小学二年级碰到过一个让我受益终身的数学题: 烧开水需要15分钟,洗碗需要5分钟,扫地需要5分钟,请问做完这三件事,总共需要几分钟?从此我做什么事,都事先想想先后顺序,看看可不可以一并去做。
长大后才知道这就是异步的用法,它其实已经渗透到你的生活中。
2. 为什么需要回调
3. 回调的缺点
//烧2000mL热水来洗碗
boilWater(2000) { water ->
washDishes(water)
}
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
request1(parameter)
.map { value1 ->
request2(value1)
}.map { value2 ->
request3(value2)
}.subscribe { value3 ->
updateUI(value3)
}
//1.简单秩序的串行世界:
print("Hello ")
print("World!")
//结果为:Hello World!
//2.复杂混沌的并行世界:
Thread {
Thread.sleep(2000)
print("Hello ")
}.start()
print("World!")
val value1 = request1(parameter)
val value2 = request2(value1)
val value3 = request2(value2)
updateUI(value3)
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
1. 添加依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2. 启动协程
GlobalScope.launch {
delay(1000L)
println("Hello,World!")
}
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit): Job {...}
CoroutineScope翻译过来就是“协程范围”,指的是协程内的代码运行的时间周期范围,如果超出了指定的协程范围,协程会被取消执行,上面第一段代码中的GlobalScope指的是与应用进程相同的协程范围,也就是在进程没有结束之前协程内的代码都可以运行。
-
context:协程上下文,可以指定协程运行的线程。默认与指定的CoroutineScope中的coroutineContext保持一致,比如GlobalScope默认运行在一个后台工作线程内。也可以通过显示指定参数来更改协程运行的线程,Dispatchers提供了几个值可以指定:Dispatchers.Default、Dispatchers.Main、Dispatchers.IO、Dispatchers.Unconfined。
-
start:协程的启动模式。默认的(也是最常用的)CoroutineStart.DEFAULT是指协程立即执行,除此之外还有CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED。
-
block:协程主体。也就是要在协程内部运行的代码,可以通过lamda表达式的方式方便的编写协程内运行的代码。
-
CoroutineExceptionHandler:除此之外还可以指定CoroutineExceptionHandler来处理协程内部的异常。
3. 调用挂起函数
println("Start")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World")
}
println("End")
println("Start")
Thread {
Thread.sleep(1000L)
println("Hello World")
}.start()
println("End")
//协程代码
println("Start ${Thread.currentThread().name}")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World ${Thread.currentThread().name}")
}
println("End ${Thread.currentThread().name}")
//线程代码
println("Start ${Thread.currentThread().name}")
Thread {
Thread.sleep(1000L)
println("Hello World ${Thread.currentThread().name}")
}.start()
println("End ${Thread.currentThread().name}")
public suspend fun delay(timeMillis: Long) {...}
GlobalScope.launch(Dispatchers.Main) {
println("Hello ${Thread.currentThread().name}")
test()
println("End ${Thread.currentThread().name}")
}
suspend fun test(){
println("World ${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.Main) {
println("Hello ${Thread.currentThread().name}")
test()
println("End ${Thread.currentThread().name}")
}
suspend fun test(){
withContext(Dispatchers.IO){
println("World ${Thread.currentThread().name}")
}
}
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
//用协程改造回调代码
GlobalScope.launch(Dispatchers.Main) {
//三次请求顺序执行
val value1 = request1(parameter)
val value2 = request2(value1)
val value3 = request2(value2)
//用最终结果更新UI
updateUI(value3)
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
//并发请求
GlobalScope.launch(Dispatchers.Main) {
//三次请求并发进行
val value1 = async { request1(parameter1) }
val value2 = async { request2(parameter2) }
val value3 = async { request3(parameter3) }
//所有结果全部返回后更新UI
updateUI(value1.await(), value2.await(), value3.await())
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
//复杂业务逻辑的Kotlin协程实现
GlobalScope.launch(Dispatchers.Main) {
//首先拿到request1的请求结果
val value1 = request1(parameter1)
//将request1的请求结果用于request2和request3两个请求的并发进行
val value2 = async { request2(value1) }
val value3 = async { request2(value1) }
//用request2和request3两个请求结果更新UI
updateUI(value2.await(), value3.await())
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
1. 添加依赖
//添加Retrofit网络库和gsonConverter的依赖,注意一定要2.6.0版本以上
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
//添加Jetpack中架构组件的依赖,注意viewmodel要添加viewmodel-ktx的依赖
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
//添加Glide的依赖用于图片加载
implementation 'com.github.bumptech.glide:glide:4.10.0'
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="10dp"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:text="refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageView3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
</LinearLayout>
3. 编写网络层接口
{
"code": "200",
"imgurl": "https://img02.sogoucdn.com/app/a/100520113/20140811192414"
}
data class ImageDataResponseBody(
val code: String,
val imgurl: String
)
import com.njp.coroutinesdemo.bean.ImageDataResponseBody
import retrofit2.http.GET
import retrofit2.http.Query
//网络接口
interface ApiService {
//声明为suspend方法
@GET("image/sogou/api.php")
suspend fun getImage(@Query("type") type: String = "json"): ImageDataResponseBody
}
import com.njp.coroutinesdemo.bean.ImageDataResponseBody
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
import java.util.concurrent.TimeUnit
//网络层访问统一入口
object NetworkService {
//retorfit实例,在这里做一些统一网络配置,如添加转换器、设置超时时间等
private val retrofit = Retrofit.Builder()
.client(OkHttpClient.Builder().callTimeout(5, TimeUnit.SECONDS).build())
.baseUrl("https://api.ooopn.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
//网络层访问服务
val apiService = retrofit.create<ApiService>()
}
sealed class LoadState(val msg: String) {
class Loading(msg: String = "") : LoadState(msg)
class Success(msg: String = "") : LoadState(msg)
class Fail(msg: String) : LoadState(msg)
}
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.xxx.coroutinesdemo.bean.LoadState
import com.xxx.coroutinesdemo.network.NetworkService
class MainViewModel : ViewModel() {
//存放三张图片的url数据
val imageData = MutableLiveData<List<String>>()
//存放网路加载状态信息
val loadState = MutableLiveData<LoadState>()
//从网络加载数据
fun getData() {...}
}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.bumptech.glide.Glide
import com.xxx.coroutinesdemo.R
import com.xxx.coroutinesdemo.bean.LoadState
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//获取ViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
//对加载状态进行动态观察
viewModel.loadState.observe(this, Observer {
when (it) {
is LoadState.Success -> button.isEnabled = true
is LoadState.Fail -> {
button.isEnabled = true
Toast.makeText(this, it.msg, Toast.LENGTH_SHORT).show()
}
is LoadState.Loading -> {
button.isEnabled = false
}
}
})
//对图片Url数据进行观察
viewModel.imageData.observe(this, Observer {
//用Glide加载三张图片
Glide.with(this)
.load(it[0])
.into(imageView1)
Glide.with(this)
.load(it[1])
.into(imageView2)
Glide.with(this)
.load(it[2])
.into(imageView3)
})
//点击刷新按钮来网络加载
button.setOnClickListener {
viewModel.getData()
}
}
}
5. 实现getData方法
fun getData() {
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
//加载失败的状态
loadState.value = LoadState.Fail(e.message ?: "加载失败")
}) {
//更新加载状态
loadState.value = LoadState.Loading()
//并发请求三张图片的数据
val data1 = async { NetworkService.apiService.getImage() }
val data2 = async { NetworkService.apiService.getImage() }
val data3 = async { NetworkService.apiService.getImage() }
//通过为LiveData设置新的值来触发更新UI
imageData.value = listOf(data1.await(), data2.await(), data3.await()).map {
it.imgurl
}
//更新加载状态
loadState.value = LoadState.Success()
}
}
/**
* ...
* This scope is bound to [Dispatchers.Main]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {...}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
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()
}
}
}
-
block:协程主体; -
onError:错误回调; -
onComplete:完成回调。
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.xxx.coroutinesdemo.bean.LoadState
import com.xxx.coroutinesdemo.launch
import com.xxx.coroutinesdemo.network.NetworkService
import kotlinx.coroutines.async
class MainViewModel : ViewModel() {
val imageData = MutableLiveData<List<String>>()
val loadState = MutableLiveData<LoadState>()
fun getData() {
launch(
{
loadState.value = LoadState.Loading()
val data1 = async { NetworkService.apiService.getImage() }
val data2 = async { NetworkService.apiService.getImage() }
val data3 = async { NetworkService.apiService.getImage() }
imageData.value = listOf(data1.await(), data2.await(), data3.await()).map {
it.imgurl
}
loadState.value = LoadState.Success()
},
{
loadState.value = LoadState.Fail(it.message ?: "加载失败")
}
)
}
}
{
"code": 200,
"data": {...},
"msg": "OK"
}
data class ResponseBody<T>(
val code: Int,
val msg: String,
val data: T
)
object Repository {
//数据脱壳与错误预处理
fun <T> preprocessData(responseBody: ResponseBody<T>): T {
return if (responseBody.code == 200) responseBody.data else throw Throwable(responseBody.msg)
}
suspend fun getImageData(paramter: Paramter1): ImageData {
//调用ApiService定义的接口方法
val responseBody = ApiService.getImage(paramter)
//返回处理后的数据
return preprocessData<ImageData>(responseBody)
}
suspend fun getOtherData(paramter: Paramter2): OtherData {...}
...
}
https://github.com/NaJiPeng/Coroutines-Demo
以上是关于全面了解Kotlin,2021大厂Android高级面试题及答案的主要内容,如果未能解决你的问题,请参考以下文章
Android 全面屏处理(适配挖孔屏刘海屏) kotlin
2021年最新Android大厂面试题来袭!已拿到offer
为啥我会收到错误消息:Android Gradle 插件仅支持 Kotlin Gradle 插件版本 1.3.10 及更高版本