- 本篇参考资料《第一行代码 第三版》 2020.4月出版
- 本篇文章只是本人看书的理解和整理的笔记,更完整的内容还在书上
- 尊重原作者 请购买正版图书
一开始就知道有四大组件,结果学习Android开发这么久,一直还只用到了Activity,这里需要进行恶补,从Service开始
四大组件之一 Service
Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。Service的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行。
另外,不要被Service的后台概念所迷惑,实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。
一. Android多线程编程
当我们需要执行一些耗时操作,比如文件的读写,网络请求等等,为了避免主线程被阻塞影响用户使用,一般会放到子线程中执行,
这里推荐学习框架Rxjava https://www.jianshu.com/p/b002d8ea2648
郭神在书上为了方便理解只讲了最简单的多线程实现方式,以下为书中内容笔记
1.1 线程的基本用法
比如我们想打印1~10 每次打印间隔1秒
方式一:使用继承Thread
方式:
fun main(){
val myThread=MyThread()
myThread.start()
}
class MyThread:Thread(){
override fun run() {
super.run()
for(i in 0..10){
println(i)
sleep(1000)
}
}
}
方式二:使用实现接口Runnable
方式:
fun main() {
val myThread= MyThread()
Thread(myThread).start()
}
class MyThread : Runnable {
override fun run() {
for (i in 0..10) {
println(i)
sleep(1000)
}
}
}
可以看到这里是Thread
的构造函数接收了一个Runnable
参数,创建了一个Thread
对象,再调用start
方法,就开始了子线程
方式三:使用Lambda表达式 (不需要再专门定义一个类)
fun main() {
Thread{
for (i in 0..10) {
println(i)
sleep(1000)
}
}.start()
}
以上三种再java中也是同样的用法,但是接下来,Kotlin为我们提供了一种更简单的开启子线程的方式
方式四:使用thread
函数
fun main() {
thread{
for (i in 0..10) {
println(i)
sleep(1000)
}
}
}
可以看到连start也不需要调用了,thread函数内部全部帮我们处理好了
1.2 尝试子线程中更新UI
Android的UI是线程不安全的,也就是说,如果想要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常。对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。
接下来我们试试在子线程中更新UI,点击按钮,启动一个子线程,在子线程中设置TextView的内容:
布局:
<Button
android:id="@+id/changeTextBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Text"
/>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="Hello World!"
/>
Activity内
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
changeTextBtn.setOnClickListener {
thread {
textView.text = "Nice to meet you !"
}
}
}
}
注意!!:这里没有使用findViewById()
,这是Kotlin一个非常优秀的设计,可以直接
import kotlinx.android.synthetic.main.activity_main.*
导入这个界面对应的布局layout文件,View的id就可以作为对象直接操作
当然你非要用findViewById()
也可也
private lateinit var button:Button
...
button = findViewById(R.id.button)
使用 Kotlin Android Extensions 直接生成对应的 View 作为属性。不需要 findViewById,不需要定义变量,直接使用。使用时需要注意访问的 View 属于哪个 Layout,因为智能提示的候选项会提供所有布局中的 View 供你选择,然后帮你 import 对应包以便你访问这个 View;假如 import 的多个同一层级的 layout 中具有相同的 id,则这个 id 对应的 View 将无法访问。
接下来我们运行这个app,并点击按钮,可以看到程序崩溃了,出现了一条报错信息:
Only the original thread that created a view hierarchy can touch its views.
这就证明Android不可以在子线程中操作UI,那么对于这种情况我们,Android提供了一套异步消息处理机制,完美的解决了在子线程中修改UI的问题
1.3 Android 异步消息处理机制
使用handler接收异步处理的信息,可以在handler中修改ui,将上边的代码改成如下:
class MainActivity : AppCompatActivity() {
//msg识别码
val upDataText=10011
val handler=object:Handler(){
//接收信息
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
//判断信息识别码 根据不同的识别码进行不同动作
when(msg.what){
//修改ui
upDataText-> {
//读取参数对象 并使用as关键字转强制换为Student1类型
//msg.obj可能为null 注意使用?进行非空判断
var result:Student1?=msg.obj as? Student1
//在ui中显示
//如果是空的 则显示null
textView.text=result?.sex?:"null"
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
changeTextBtn.setOnClickListener {
thread {
val msg=Message()
//信息识别码
msg.what=upDataText
//msg传递一个参数 对象
msg.obj=Student1("girl")
//发出信息
handler.sendMessage(msg)
}
}
}
}
在以上的代码中,我们将异步线程处理结果,封装成一个message传递到handler。message主要属性第一个是msg.what
是msg的识别码,用户handler判断不同信息并采取不同动作。msg.obj
可以传递一些对象参数,msg.arg1可以传递一些整型参数。在handler中,使用as
关键字将msg.obj强制转换为具体的类型,这样就可以进行操作了。(注意使用?非空判断)
1.4 使用AsyncTask来进行异步操作
https://www.jianshu.com/p/9724355bf01b