【客户端学习】Kotlin Android 学习笔记
Kotlin Android 学习笔记
Android 应用的基本组件
1. Activity 和 View
Activity 是 Android 应用中负责与用户交互的组件
View 组件是所有 UI 控件、容器控件的基类
2. Service
Service 通常位于后台运行,它一般不需要与用户交互,因此 Service 组件没有图形用户界面
3. BroadcastReceiver
类似于事件编程中的监听器,监听 Android 应用中的其他组件,相当于一个全局的事件监听器
4. ContentProvider
用于 Android 应用间进行实时的数据交换,通常结合 ContentResolver 来使用
5. Intent 和 IntentFilter
实现不同组件之间通信,可以启动 Activity 和 Service,也可以触发 BroadcastReceiver
视图组件与容器组件
Android 应用的绝大部分 UI 组件都放在 android.widget 和 android.view 包及它们的子包中,所有 UI 组件都继承了 View 类
View 类重要的子类 ViewGroup 通常是作为其他组件的容器使用
布局管理器
**1. 线性布局 **
可以控制横向排列或纵向排列,不会换行显示
**2. 表格布局 **
3. 帧布局
Android 的 View 和 UI 组件不是线程安全的,所以 Android 不允许开发者启动线程访问用户界面的 UI 组件
如果直接使用对象表达式(相当于 Java 的匿名内部类)定义 Handler 类的实例,如果该 Handler 直接使用主线程的 Looper 或 MessageQueue,就可能导致内存泄漏。因此,为 Handler 派生一个子类,并让该子类实例持有它所在 Activity 的弱引用(WeakReference)可以更好地避免内存泄漏
4. 相对布局
5. 网格布局
6. 绝对布局 ( deprecated )
在 Android 中一般支持如下常用的布局单位:
- px: 每个 px 对应屏幕上一个点
- dp: 基于屏幕密度的抽象单位。在每英寸 160 点的显示器上,1dp = 1px。但随着屏幕密度的改变,dp 与 px 的换算会发生改变,主要用于控件的固定尺寸
- sp: 主要处理字体的大小,可以根据用户字体大小首选项进行缩放
- in: 标准长度单位
- mm: 标准长度单位
- pt: 标准长度单位,1/72 英寸
7. Android 8 的约束布局
Activity 状态
1. 运行状态:Activity 处于栈顶
2. 暂停状态:Activity 不再处于栈顶,但依然可见(不是每个 Activity 都会占满整个屏幕,比如对话框形式的 Activity 只会占用屏幕中间的部分区域)
3. 停止状态:不处于栈顶,完全不可见
4. 销毁状态:从返回栈中移除
Activity 生存期
onCreate()
:第一次被创建,应该在此完成初始化操作,如加载布局、绑定事件
onStart()
:由不可见变为可见
onResume()
:准备好和用户进行交互。此时它一定位于栈顶,并且处于运行状态
onPause()
:准备去启动或者恢复另一个 Activity,一般用来把一些消耗 CPU 的资源释放掉,以及保存一些关键数据。这个方法的执行速度一定要快
onStop()
:在 Activity 完全不可见时调用。和onPause()
的主要区别在于,如果启动的新 Activity 是一个对话框式的 Activity,那么onPause()
方法会执行,而onStop()
方法不会执行
onDestroy()
:在 Activity 被销毁之前调用,之后 Activity 的状态变为销毁状态
onRestart()
:在 Activity 由停止状态变为运行状态之前调用
可以将 Activity 分为以下 3 种生存期:
完整生存期:onCreate()
初始化 --> onDestroy()
释放内存
可见生存期:onStart()
--> onStop()
前台生存期:onResume()
--> onPause()
Activity 被回收了怎么办
使用onSaveInstanceState()
方法保存临时数据,在onCreate()
方法中取出数据
ListView
android.R.layout.simple_list_item_1
是一个 Android 内置的布局文件,里面只有一个 TextView,可用于简单地显示一段文字LayoutInflator
的inflate()
方法接受 3 个参数,第三个参数指定成false
,表示只让我们在父布局中声明的layout
属性生效,但不会为这个 View 添加父布局。因为一旦 View 有了父布局之后,它就不能再添加到 ListView 中了- kotlin-android-extensions 插件在 ListView 的适配器中是无法工作的,它的主要应用场景是在 Activity 以及 Fragment 当中
RecyclerView
onCreateViewHolder()
:用于创建 ViewHolder 实例(加载 item 布局)onBindViewHolder()
:对 RecyclerView 子项的数据进行赋值getItemCount()
:返回数据源长度
TextView
android:ellipsize
:用于设定当文本内容超出控件宽度时文本的缩略方式,指定成end
表示在尾部进行省略
Fragment
动态添加 Fragment 主要分为 5 步:
- 创建待添加 Fragment 的实例
- 获取 FragmentManager,在 Activity 中可以直接调用
getSupportFragmentManager()
方法获取 - 开启一个事务,通过调用
beginTransaction()
方法开启 - 向容器内添加或替换 Fragment,一般使用
replace()
方法实现,需要传入容器的 id 和待添加的 Fragment 实例 - 提交事务,调用
commit()
方法完成
调用 FragmentManager 的findFragmentById()
方法,可以在 Activity 中得到相应 Fragment 的实例,然后就可以轻松调用 Fragment 中的方法了。同时,kotlin-android-extensions 也提供了简便方法
在每个 Fragment 中都可以通过调用getActivity()
方法来得到和当前 Fragment 关联的 Activity 实例
附加的回调方法:
onAttach()
:当 Fragment 和 Activity 建立关联时调用
onCreateView()
:为 Fragment 创建视图(加载布局)时调用
onActivityCreated()
:确保与 Fragment 相关联的 Activity 已经创建完毕时调用
onDestroyView()
:当与 Fragment 关联的视图被移除时调用
onDetach()
:当 Fragment 和 Activity 解除关联时调用
广播机制
- 标准广播:完全异步执行的广播,不可被截断
- 有序广播:同步执行的广播,可被截断
静态注册的广播:可以用来实现开机启动等功能,Android 8.0 起不能监听隐式广播(指没有指定发送给哪个应用程序的广播)
注意:不要在onReceive()
方法中添加过多的逻辑或者进行任何的耗时操作,因为 BroadcastReceiver 中是不允许开启线程的,当onReceive()
方法运行了较长时间而没有结束时,程序就会出现错误
SharedPreferences
我们可以使用 KTX 提供的如下写法来向SharedPreferences
存储数据:
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
putString("name", "Tom")
putInt("age", 28)
putBoolean("married", false)
}
KTX 也可以支持如下写法来创建ContentValues
val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
"pages" to 720, "price" to 20.85)
db.insert("Book", null, values)
Android 异步消息处理机制
- Message:在线程间传递的消息,可以带有少量信息
- Handler:主要用于发送和处理消息,发送使用
sendMessage()
、post()
等,接收消息使用handleMessage()
- MessageQueue:消息队列,存放所有通过 Handler 发送的消息
- Looper:MessageQueue 的管家,调用
loop()
开启无限循环,不断取出消息、处理消息
Service
onCreate()
和onStartCommand()
的区别:前者在 Service 第一次创建时调用,而后者在每次启动 Service 时都会调用
一个 Service 只要被启动或者被绑定之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service 才能销毁。若对一个 Service 既调用了startService()
方法又调用了bindService()
方法,就需要同时调用stopService()
和unbindService()
方法,onDestroy()
方法才会执行
Jetpack
ViewModel:帮助 Activity 分担一部分工作,专门用于存放与界面相关的数据。它的生命周期和 Activity 不同,可以保证在手机屏幕发生旋转的时候不会被重新创建
创建 ViewModel 实例的语法:ViewModelProviders.of(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)
WorkManager:一个处理定时任务的工具,可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务依然得到执行。它和 Service 并不相同,也没有直接的联系
Data Binding:在使用时,记得设置lifeCycleOwner
,这里踩过坑
Kotlin 相关语法
internal:用于限制跨 module 方法调用的关键字
if语句: Kotlin 中的 if 语句是可以有返回值的,返回值就是 if 语句中每个条件最后一行代码的返回值
创建区间:..
用于创建闭区间,until
用于创建左闭右开的区间
如果一个 Java 方法的参数列表中不存在一个以上 Java 单抽象方法接口参数,我们还可以将接口名进行省略,例子如下:
省略前
Thread(Runnable {
println("Thread is running")
}).start()
省略后
Thread({
println("Thread is running")
}).start()
lateinit:延迟初始化,可结合!::adapter.isInitialized
来判断变量是否已经初始化
sealed class:密封类,使用后 Kotlin 编译器会自动检查该密封类有哪些子类,并强制要求将每一个子类所对应的条件全部处理、
内联函数:由于在编译的时候会被进行代码替换,因此它没有真正的参数属性。非内联的函数类型参数可以自由地传递给其他函数,而内联的函数类型参数只允许传递给另外一个内联函数。同时,内联函数所引用的 Lambda 表达式中可以使用return
关键字来进行函数返回,而非内联函数只能进行局部返回
如果我们在高阶函数中创建了另外的 Lambda 或者匿名类的实现,并且在这些实现中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误。这种情况下,可以使用crossinline
保证在内联函数的 Lambda 表达式中一定不会使用return
关键字
use 函数:会保证在 Lambda 表达式中的代码全部执行完之后自动将外层的流关闭
基于 JVM 的语言,泛型功能都是通过类型擦除机制实现的。泛型的实际类型在运行时已经被擦除。在 Kotlin 中,有一种泛型实化的写法,方式如下:
inline fun <reified T> getGenericType() = T::class.java
泛型协变:假如定义了一个MyClass<T>
的泛型类,其中 A 是 B 的子类型,同时MyClass<A>
又是MyClass<B>
的子类型,那么我们就可以称MyClass
在T
这个泛型上是协变的
若想要使MyClass<A>
成为MyClass<B>
的子类型,需要让MyClass<T>
类中的所有方法都不接收T
类型参数。换句话说,T
只能出现在 out 位置上,而不能出现在in位置上
泛型逆变:假如定义了一个MyClass<T>
的泛型类,其中 A 是 B 的子类型,同时MyClass<B>
又是MyClass<A>
的子类型,那么我们就可以称MyClass
在T
这个泛型上是逆变的
这种情况下,T
只能出现在 in 位置上,而不能出现在 out 位置上
协程:轻量级线程,在单线程模式下模拟多线程编程的效果
GlobalScope.launch
:创建的永远是顶层协程
runBlocking
:创建协程作用域,可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。通常只应该在测试环境中使用,在正式环境中使用容易产生杏能问题(在主线程中使用,可能造成界面卡死)
suspend
:将任意函数声明成挂起函数
coroutineScope
:继承外部的协程作用域并创建一个子作用域。跟runBlocking
类似,可以保证其作用域内的所有代码和子协程在全部执行完之前,会一直阻塞当前协程。但它只会阻塞当前协程,既不影响其他协程,也不影响任何线程,不会造成任何性能上的问题
实际项目中比较常用的写法:
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
// 处理具体逻辑
}
job.cancel()
async
、await
:获取运算结果
fun main() {
runBlocking {
val result = async {
5 + 5
}.await()
println(result)
}
}
withContext
:大致可以理解成async
函数的一种简化版写法
fun main {
runBlocking {
val result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
}
线程参数:Dispatchers.Default
、Dispatchers.IO
和Dispatchers.Main
Dispatchers.Default
表示一种默认低并发的线程策略,适合计算密集型任务Dispatchers.IO
会使用较高并发的线程策略,大多数时间用于阻塞/等待时使这个Dispatchers.Main
:在 Android 主线程中执行
suspendCoroutine
:接收一个 Lambda 表达式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行 Lambda 表达式中的代码。Lambda 表达式的参数列表上会传入一个Continuation
参数,调用它的resume()
或resumeWithException()
方法可以让协程恢复执行
Java 知识补充
@Inject 注解:可以出现在三种类成员之前,表示该成员需注入依赖项。按运行时的处理顺序这三种成员类型是:构造方法、方法、属性
布局巧用
include:不同页面中有相同的布局,可以将通用部分提取出来到一个单独的 layout 文件里,再使用<include>
标签引入到相应的页面布局文件里。主要通过include
的layout
属性引用
merge:可用于减少视图层级来优化布局,可以配合include
使用。如果include
标签的父布局和include
布局的根容器是相同类型的,那么根容器的可以使用merge
代替
ViewStub:需要的时候再去加载,不需要的时候不用加载,节约内存使用