Book

Kotlin从零到精通 Android开发——欧阳燊

2018年4月第一版 清华大学出版社

书附录源码github链接

资源下载和内容勘误CSDN链接

书是在学校图书馆借的

20190628

第一章搭建Kotlin开发环境,安装过程基本和书上一致。安装AS的时候可以选择不安装virtual device,暂时都是在用自己的手机调试,没有虚拟设备需求。

安装SDK很方便,也自带了书中提到的Gradle插件。

Anko库配置的那一部分没有实现,猜测是因为我没有装Jetbrains的东西,不过暂时也没影响。

对xml布局有了一定了解,好像VB啊……坑的地方就是用可视化工具design出来的布局和实际run起来的布局很多不同,几乎全崩,还得老老实实写xml……

第二章数据类型,看了基本数据类型及其转换,Nothing Special。

20190629

继续第二章数据类型,关于数组的定义和基本方法有所了解。

插曲:顺手搜了一手怎么打包apk给别人用。

关于字符串,和基本类型的转换很方便,其中字符串的toBoolean方法只能转换字符串“true”和“false”。

数组常用的方法有indexOf,substring,replace,split等,其中split方法的结果用List<String>来存。用这几种基本方法可以完成很多操作。(越看越像VB……

Kotlin中,直接在字符串中加入$变量名即可表示此处引用该变量的值。需要注意的是,这个符号在markdown里看来也是一个神奇的符号,写这一段的时候就乱码了好几次……真正需要注意的是,符号$后面跟变量名,系统会自动匹配最长的变量名;如果在取值之前还要先运算,则需用大括号把运算表达式给括起来;算了不抄书了截个图算了……

20190630

继续第二章,容器:集合Set,队列List,映射Map。莫名想起了算法竞赛的时候,使用集合、队列和映射的时候。

对于变量,val表示该变量不可更改,var表示该变量可更改。容器默认为只读,需要允许修改,就加上Mutable 前缀。所以有MutableSetMutableListMutableMap。三者有共同的方法isEmptyisNotEmptyclearcontainsinteratorcount

容器名称 初始化方法
Set setOf
MutableSet mutableSetOf
List listOf
MutableList mutableListOf
Map mapOf
MutableMap mutableMapOf

集合的遍历,for-in遍历、迭代器遍历、forEach遍历。

其中迭代器遍历使用得带器的hasNext方法判断是否存在下一个节点,用next方法获得下一个节点的元素。

r = goodsMutSet.iterator()
//如果迭代器还存在下一个节点,则继续取出下一个节点的记录
while (iterator.hasNext()) {
val item = iterator.next()
}

forEach遍历内部使用it指代每条记录。

goodsMutSet.forEach { desc = "${desc}名称:${it}\n" }

集合用得不多,主要用List和Map吧。

List

MutableList的add方法把元素加到队尾,set元素修改指定位置的元素,removeAt方法允许删除指定位置的元素。遍历方法和set相同,还多了一种按元素下标循环遍历的方式。

//indices是队列的下标数组。如果队列大小为10,则下标数组的取值为0到9
for (i in goodsMutList.indices) {
val item = goodsMutList[i]
}

还多了排序方法sortB:

//sortBy表示升序排列,后面跟的是排序条件
goodsMutList.sortBy { it.length }
//sortByDescending表示降序排列,后面跟的是排序条件
goodsMutList.sortByDescending { it.length }

Map

常用方法containsKeycontainsValueput(添加元素),remove(通过键名来删除元素)。

初始化时,两种方法。一是Pair(键名,键值),二是键名 to 键值

遍历依旧三种方法,注意的是要访问元素的key属性和value属性获得键名或者键值。比如:

val iterator = goodsMutMap.iterator()
//如果迭代器还存在下一个节点,则继续取出下一个节点的记录
while (iterator.hasNext()) {
val item = iterator.next()
desc = "${desc}厂家:${item.key},名称:${item.value}\n"
}

forEach内部使用key指代每条记录的键,使用value指代每条记录的值。

goodsMutMap.forEach { key, value -> desc = "${desc}厂家:${key},名称:${value}\n" }

第二章结束。

20190702

第三章,控制语句。

条件分支

条件分支最简单,包括两路分支以及多路分支。两路分支的时候,因为if语句允许有返回值,能直接简化为类似于三目运算符的形式。

多路分支中,when语句必须带上else,when/else也允许有返回值。牛逼的是,不用break,处理完一次就直接跳出。还可以引入变量或者具体的运算表达式进行判断,甚至可以是个范围:

tv_answer.text = when (count) {
1,3,5,7,9 -> "凉风有信的谜底是“讽”"
in 13..19 -> "秋月无边的谜底是“二”"
!in 6..10 -> "当里的当,少侠你来猜猜"
else -> "好诗,这真是一首好诗"
}

更牛逼的是,还可以进行类型判断……

var countType:Number
//Long、Double、Float都由Number派生而来
countType = when (count) {
0 -> count.toLong()
1 -> count.toDouble()
else -> count.toFloat()
}
count = ( count + 1 ) % 3;
tv_answer.text = when (countType) {
is Long -> "此恨绵绵无绝期"
is Double -> "树上的鸟儿成双对"
else -> "门泊东吴万里船"
}

循环

遍历循环有for-in循环和利用indices下标数组的循环。下标数组用法同List。

关于条件循环,kotlin在for-in循环中提供了until、step、downTo等关键字,但还是很乱很麻烦,所以更灵活的方案还是用do/while进行条件循环。

循环中可以用continue和break。

//发现该行是空串或者空格串,则忽略该行
if (poem2Array[pos].isNullOrBlank())
continue

我惊呆了,给循环加上@标记,想break几层循环就break几层……

var i:Int = 0
var is_found = false
//给外层循环加个名叫outside的标记
outside@ while (i < poemArray.size) {
var j:Int = 0
var item = poemArray[i];
while ( j < item.length) {
if (item[j] == '一') {
is_found = true
//发现情况,直接跳出outside循环
break@outside
}
j++
}
i++
}

20190704

空安全

//strA.isNullOrEmpty() //非空串与可空串均可调用
//strA.isNullOrBlank() //非空串与可空串均可调用
//strA.isEmpty() //只有非空串可调用
//strA.isBlank() //只有非空串可调用
//strA.isNotEmpty() //只有非空串可调用
//strA.isNotBlank() //只有非空串可调用

区别是全为空格的串算作Blank而不算Empty。

声明可空字符串,加个问号。获取可空串的length要注意避免空指针,Kotlin引入了几种标记。

val strCanNull:String?
length = if (strCanNull!=null) strCanNull.length else -1

//?.表示对象为空时直接返回null,所以返回值的变量必须被声明为可空类型
var length_null:Int? = strB?.length

//?:表示为空时就返回右边的值,即(x!=null)?x.**:y
length = strB?.length?: -1

//!!表示不做非空判断,强制执行后面的表达式,如果对象为空就会扔出空异常
//所以只有在确保为非空时,才能使用!!
length = strB!!.length

等式判断

导入样例代码build时,提醒Unresolved reference: Date(),怀疑是anko的问题,于是配置好了anko。然后发现还是不行,搞了半天import java.util.*,就OK了。

特点:字符串也可用==!=来判断相等否(结构相等)。还有另一种更严格的判断是引用相等:意思是除了值相等以外,还要求引用的地址(即储存地址)相等,表达式用===!==

类型判断:is,!is。检验数组中是否存在某个元素:in,!in

第三章结束。

20190706

第四章——函数运用

函数基本方法

override fun onCreate(savedInstanceState: Bundle?) {}

override在同一行表达重载操作。kotlin默认函数就是公开的,所以省略了关键词”public”。函数若无返回参数,则不用特别说明。关键词”fun”表示函数定义。声明输入参数用“变量名称:变量类型”格式,变量支持空安全机制。

函数声明返回值用fun main():Int这种格式,即使不声明,也会有一个Unit类型的对象返回,可直接省略Unit声明。这只是为了让函数定义完全符合变量定义的形式,若需要具体的输入参数,则一样需要在函数里用return关键字来返回参数值。

默认参数:在声明输入参数时在其后面加上等号及其默认值。

命名参数:给指定的参数赋值。如 getFourBigDefault(second="活字印刷")

可变参数:声明参数时用vararg otherArray: String?vararg表示其后的参数个数不确定,可变参数当成一个数组来解析。So,可以实现可变的数组参数,声明时也要加上vararg前缀,调用时要注意:可输入多个数组变量,每个数组都使用arrayOf定义。

fun getFourBigArray(vararg otherArray: Array<String>):String {
var answer:String = ""
//先遍历每个数组
for (array in otherArray) {
//再遍历某个数组中的所有元素
for (item in array) {
answer = "$answer$item"
}
}
return answer
}

20190710

先整理一下之前搞的关于打包的技巧,参考了**这篇博客**。

在app的build.gradle中加了以下代码。

//release版本输出包名自动追加版本号和版本名称
applicationVariants.all {
variant -> variant.outputs.all {
//只处理生产版本
if (buildType.name == 'release') {
def createTime = new Date().format("YYYY-MM-dd", TimeZone.getTimeZone("GMT+08:00"))
// app包名称
outputFileName = "app_v" + defaultConfig.versionName + "_" + createTime + "_" + buildType.name + ".apk"
}
}
}

几种特殊函数

注:Kotlin允许定义全局函数,即函数可在单独的kt文件中定义,然后其他地方也能直接调用。
泛型函数:在函数名称前面添加<T>,表示以T声明的参数,其参数类型必须在函数调用时指定。把T换成其他的都行,只要前后对应。如:

fun <R> appendString(tag:String, vararg otherInfo: R?):String {}

内联函数fun setArrayNumber(array:Array<Number>) {}不接受Array<Int>或者Array<Double>的入参,得指定泛型变量T来自于基类Number,即将T改为<reified T:Number>,同时在fun前面添加关键字inline。如:

inline fun <reified T : Number> setArrayStr(array:Array<T>) {}
//该函数调用时入参可为Int、Long、Double、Float类型数组。

简化函数:Kotlin把函数当作一种特殊变量,则允许通过等号给函数这个变量进行赋值。所以阶乘函数如下:

fun factorial(n:Int):Int = if (n <= 1) n else n* factorial(n - 1)

尾递归函数:指函数末尾的返回值重复调用了自身函数,要在fun前面加上关键字tailrec。如:

tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

高阶函数:允许将函数表达式作为输入参数传进来,就形成了高阶函数。

//允许将函数表达式作为输入参数传进来,就形成了高阶函数,这里的greater函数就像是个变量。
//greater函数有两个输入参数,返回布尔型的输出参数。
//调用时第二个参数时用大括号包了起来,这是Lambda表达式的匿名函数写法。中间的'->'把匿名函数分为两个部分,前是入参,后是函数体。如果第一个参数大于第二个参数,则认为greater返回true,否则返回false。
fun <T> maxCustom(array: Array<T>, greater: (T, T) -> Boolean): T? {
var max: T? = null
for (item in array)
if (max == null || greater(item, max))
max = item
return max
}
//调用如下:
maxCustom<String>(string_array, { a, b -> a.length > b.length })

插一段关于Kotlin 包和 import 语句使用,复制样例代码到测试工程中时遇到了这个报错,因为import的package和包里声明的package以及文件目录表示的package三者不匹配。

增强系统函数

扩展函数:例子如下:

//扩展函数结合泛型函数,能够更好地扩展函数功能
fun <T> Array<T>.swap(pos1: Int, pos2: Int) {
val tmp = this[pos1] //this表示数组对象自身
this[pos1] = this[pos2]
this[pos2] = tmp
}
//调用:
//把下标为0和3的两个数组元素进行交换
//array可以是整型数组,也可以是双精度数组
array.swap(0, 3)

扩展高阶函数:高阶函数+泛型函数+扩展函数的功能,很强。和前面对比着看。

//给高阶函数进行扩展,形成数组的扩展函数
fun <T> Array<T>.maxCustomize(greater: (T, T) -> Boolean): T? {
var max: T? = null
for (item in this)
if (max == null || greater(item, max))
max = item
return max
}
//调用:
//string_array.maxCustomize({ a, b -> a.length > b.length })

日期时间函数:基本上每个Android工程都需要一个类似的工具类来获得不同格式的字符时间串,使用扩展函数可以实现。例如:

//返回开发者指定格式的日期时间字符串
fun Date.getFormatTime(format: String=""): String {
var ft = format
val sdf = if (!ft.isEmpty()) SimpleDateFormat(ft)
else SimpleDateFormat("yyyyMMddHHmmss")
return sdf.format(this)
}
//调用:
Date().getFormatTime("yyyy年MM月dd日HH时mm分ss秒")
**单例对象**:没学过java,咱也看不懂,他说像是一种阉割了的简化类。

//关键字object用来声明单例对象,就像Java中开发者自己定义的Utils工具类。
//其内部的属性等同于Java中的static静态属性,外部可直接获取属性值。
object DateUtil {
/声明一个当前日期时间的属性,
//返回的日期时间格式形如2017-10-01 10:00:00
val nowDateTime: String
//外部访问DateUtil.nowDateTime时,会自动调用nowDateTime附属的get方法得到它的值
get() {
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
return sdf.format(Date())
}
}
//调用:
DateUtil.nowDateTime

调用变得简洁了很多。第四章结束。

20190711

跳过了书,直接开始搞蓝牙开发。

参考资料:

学习笔记__基于Kotlin的蓝牙通信工具类

Android蓝牙实例(和单片机蓝牙模块通信)

经典蓝牙的官方开发文档(中文)

Kotlin语言中文站

然后放到了github上,**链接**。不过想了一下,比赛还在初期,就不开源了吧。比赛结束再开源,希望能取得一个好成绩。值得一提的是,在AndroidStudio的工程目录自动生成了gitignore文件,忽略了不必要的配置和build文件。

目前实现了app控制手机蓝牙的开关、显示该手机已配对的蓝牙设备、以及对特定的蓝牙设备(即目前用来测试的HC-05蓝牙片子)进行连接/断开连接,同时完成了手机端到HC-05的数据传输。下一步是实现app的数据接收及处理。

20190714注:这个牛逼的测试程序有很多bug,我太年轻了,对蓝牙开发的理解还是不够。

20190714

前两天没有做笔记,因为发现开发蓝牙太麻烦了,还是用已有的框架吧,然后就折腾了两天……

因为经典蓝牙的相关框架实在太少,遂转战BLE,这次准备用BLE了。其余内容在项目文档里写了,链接

旧的测试项目一样。

项目链接

20190717

第六章 6.1没啥好说。