更多Android高级工程师进阶学习资料
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
6.1 高阶函数定义
前面是参数,后面是返回值类型(String,int) -> Unit
.
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
return operation(num1, num2)
}
fun main() {
val result1 = num1AndNum2(1, 2) { n1, n2 ->
n1 + n2
}
val result2 = num1AndNum2(2, 5) { n1, n2 ->
n1 - n2
}
println(result1)
println(result2)
}
高阶函数的完整语法:
//给StringBuilder增加扩展方法build,需要传入一个高阶函数,这个高阶函数只作用于StringBuilder,所以需要写成StringBuilder.() -> Unit,这样可以在里面访问到StringBuilder的上下文
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
val result = StringBuilder().build {
append(“Start\n”)
for (s in list) {
append(s).append(“\n”)
}
append(“end”)
}
6.2 内联函数
其实默认情况下,高阶函数会被Kotlin编译器转换成实现了Function接口的匿名类,这其实会造成额外的内存和性能开销.
为了解决这个问题,Kotlin提供了Kotlin提供了内联函数,将使用Lambda表达式带来的运行时开销完全消除.
直接在定义函数的地方前面加上inline关键词即可.Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方.所以可以完全消除Lambda表达式所带来的运行时开销.
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
return operation(num1, num2)
}
7.1 泛型
- 给方法加泛型
fun method(param: T) : T {
return param;
}
- 给类加泛型
class MyClass {
fun method(param: T) : T {
return param;
}
}
7.2 委托
委托是一种设计模式,它的基本理念是: 操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理.
好处: 如果一个类,让大部分的方法实现都是调用辅助对象中的方法,而少部分的方法是自己实现的,这种情况,就是委托模式的意义所在.
- 类委托
kotlin支持类委托给另一个辅助对象,语法层面就支持,拥有辅助对象的所有功能,还能自己去实现或构建独有功能. 直接通过关键字by完成,简化自己去编写模板代码
//MySet中有所有Set接口中的功能,和HashSet保持一致, 并且isEmpty是自己实现的
class MySet(val helperSet: HashSet) : Set by helperSet {
fun hello() = println(“hello”)
//演示,
override fun isEmpty(): Boolean {
return true
}
}
- 属性委托
将一个属性的具体实现委托给另一个类去完成.
8.1 泛型实化
Kotlin有内联函数,而内联函数是提替换到被调用的地方,所以不存在泛型擦除(泛型对于类型的约束只在编译期存在)问题.
泛型实化书写,首先得是内联函数,其次得加reified关键字,然后可以在函数内获取当前指定泛型的实际类型
inline fun getGenericType() = T::class.java
val result1 = getGenericType()
val result2 = getGenericType()
线程需要依靠操作系统的调度才能实现不同线程之间的切换.而协程可以在编程语言的层面实现不同协程之间的切换,从而大大提升并发编程的运行效率.
协程允许在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制,和操作系统无关.
-
Kotlin for Java的协程并不属于广义的协程,而是一个线程框架
-
可以用看起来同步的代码写出实质上异步的操作
-
suspend并不是拿来切线程的,而是用来做标记和提醒的,调用的时候需要放协程里面才行.
-
协程是怎么切线程的:看Kotlin编译成的class对应的Java代码发现,其实就是将代码分块,某一块代码执行在1个线程,另一块代码执行在另一个线程中.在编译的时候就搞了这样的操作.
9.1 创建协程作用域
GlobalScope.launch可以创建一个协程的作用域,传递给launch函数的代码块(Lambda表达式)就是在协程中运行的.
GlobalScope.launch {
println(“codes run is coroutine scope”)
//这里不是主线程 DefaultDispatcher-worker-1
println(Thread.currentThread().name)
}
GlobalScope.launch创建的是顶层协程,当应用程序结束时也会跟着结束.
可以在协程中加入delay()函数,delay()函数可以让当前协程延迟指定时间后再运行.
delay是非阻塞式的挂起函数,它只会挂起当前协程.而Thread.sleep()会阻塞当前的线程,该线程的所有协程都会被阻塞.
GlobalScope.launch {
println(“codes run is coroutine scope”)
//这里不是主线程
println(Thread.currentThread().name)
delay(1000)
println(“延迟之后的输出”)
}
runBlocking也能创建一个协程的作用域,它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程.runBlocking函数通常只应该在测试环境下使用,在正式环境中容易产生性能上的问题.
9.2 创建多个协程
fun main() {
//创建多个协程
runBlocking {
launch {
println(“launch1 ${Thread.currentThread().name}”)
delay(1000)
println(“launch1 finished”)
}
launch {
println(“launch2 ${Thread.currentThread().name}”)
delay(1000)
println(“launch2 finished”)
}
}
}
这里的launch函数和刚才的GlobalScope.launch不一样,这个launch只能在协程的作用域下面调用,且会创建一个子协程.子协程的特点是如果外层作用域的协程结束了,那么该作用域下的所有子协程也会一同结束.
上面的输出如下:
launch1 main
launch2 main
launch1 finished
launch2 finished
日志是交叉打印的,很明显,这是并发执行的.但是线程却是相同的线程.这里由编程语言来决定如何在多个协程之间进行调度,让谁挂起,让谁运行.调度过程不需要操作系统参与,这使得协程并发效率出奇得高.
9.3 suspend 挂起
当需要将部分代码提取到一个单独的函数中,这个函数是没有协程作用域的.Kotlin提供一个suspend关键字,使用它可以将任意函数声明成挂起函数,挂起函数之间是可以互相调用的.
suspend只能声明挂起函数,而不能提供协程作用域,在里面调用launch(必须在协程作用域调用才行)是不得行的.要想有协程作用域,可以使用coroutineScope.
coroutineScope也是一个挂起函数,因此可以在其他任何挂起函数中调用.coroutineScope会继承外部的协程作用域并创建一个子作用域. 于是可以这样用:
suspend fun printDot() {
coroutineScope {
launch {
println(“.”)
delay(1000)
println(“延迟之后的输出”)
}
}
}
coroutineScope有点类似runBlocking,保证其作用域内的所有代码和子线程全部执行完之前,会一直阻塞当前协程.
但是runBlocking会阻塞当前线程,影响较大.而coroutineScope只会阻塞当前协程,不会影响其他协程,也不会影响其他线程.
可创建新的协程作用域:
-
GlobalScope.launch 可在任何地方调用
-
runBlocking 可在任何地方调用
-
lanuch
-
coruotineScope
9.4 更多的作用域构建器
runBlocking会阻塞线程,只能在测试环境下使用.而GlobalScope.launch是顶层协程,比如在Activity中使用来请求网络,还没请求回来的时候,Activity已关闭,这时需要手动管理(去取消)这个顶层协程,比较麻烦. 调用下面的代码会取消顶层协程.
val job = GlobalScope.launch { }
job.cancel()
但是实际项目中,一般会用CoroutineScope
val job = Job()
//返回的是CoroutineScope对象 这里是调用的CoroutineScope方法
val scope = CoroutineScope(job)
scope.launch {
}
所有使用CoroutineScope对象的launch创建的协程统统会被job所管理(都是在它的作用域下面).大大降低协程维护成本.
9.5 创建协程,并获取其执行结果
使用async函数,就可以获取协程的执行结果.它会创建一个子协程,并返回Deferred对象,然后我们调用其await方法即可知道结果.下面是简单计算一下5+5
runBlocking {
val result = async {
delay(100)
5 + 5
}.await()
println(result)
}
注意,调用await方法之后会阻塞当前协程,直到子协程拿到结果,才会执行后面的代码(对应上面是println语句).为了提高效率,可以先拿到返回Deferred对象,最后需要结果的时候才调用await方法
runBlocking {
val start = System.currentTimeMillis()
val deferred1 = async {
delay(1000)
5 + 5
}
val deferred2 = async {
delay(1000)
6 + 6
}
println(“结果是 ${deferred1.await() + deferred2.await()}”)
val end = System.currentTimeMillis()
println(“花费时间: ${end - start}”)
}
9.6 withContext
withContext大致是async函数的简化版,它是一个挂起函数,返回结果是withContext函数体内最后一行代码.相当于val result = async{5+5}.await()
runBlocking {
val result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
调用withContext函数后,函数体内的代码会被立即执行,同时需要指定一个线程参数.这个参数有如下几个值:
-
Dispatchers.Default 会开启子线程,并使用一种较低并发的线程策略.适合计算密集型任务.
-
Dispatchers.IO 会开启子线程,并使用一种较高并发的线程策略.网络请求比较合适
-
Dispatchers.Main 不会开启子线程,而是在Android主线程执行代码.
9.7 使用协程简化回调
suspendCoroutine 函数可以将当前协程立即挂起,然后在一个普通的线程执行lambda表达式中的代码.Lambda表达式的参数是一个Continuation参数,调用它的resume方法或resumeWithException可以让协程恢复执行.
来看一段代码:
suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
continuation.resume(response)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
}
GlobalScope.launch {
val response = request(“https://www.baidu.com/”)
Log.d(“xfhy”, “网络请求结果 : $response”)
}
将网络请求的代码用suspendCoroutine包装一下,免得每次去手动生成一个匿名类,然后在里面拿到结果的时候调用continuation的resume方法将结果返回,这样在外面即可拿到结果. 使用request方法请求网络,只需要写一句代码即可.上面为了实例,没有加try…catch.
Linux内核
Android平台的基础是Linux内核.
硬件抽象层(HAL)
硬件抽象层提供标准界面,向更高级别的Java API框架显示设备硬件功能.
Android Runtime
对于运行Android 5.0(API 21) 或更高版本的设备,每个应用都在棋自己的进程中运行,并且有其自己的Android Runtime(ART)实例.
原生C/C++库
尾声
评论里面有些同学有疑问关于如何学习material design控件,我的建议是去GitHub搜,有很多同行给的例子,这些栗子足够入门。
有朋友说要是动真格的话,需要NDK以及JVM等的知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。
至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。
一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。
初学者,一句话,多练。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
行给的例子,这些栗子足够入门。
有朋友说要是动真格的话,需要NDK以及JVM等的知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。
至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。
一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。
初学者,一句话,多练。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!