《第一行代码》第十章 后台默默的劳动者 探究Service

2020-04-28  本文已影响0人  番茄tomato
  • 本篇参考资料《第一行代码 第三版》 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

二. Service的使用

上一篇下一篇

猜你喜欢

热点阅读