[笔记]《第一行代码》:Android多线程和Service

119 阅读7分钟

引言:

本文是在阅读完郭神《第一行代码:Android》的第十章后,对书中内容做出的简要总结。作为新手开发者,笔记中难免会存在错误或缺漏。如果在浏览文章中发现任何的问题,欢迎在评论区指出。


Android 多线程

当我们需要进行一些耗时操作时,可以将其放在子线程中进行处理,以避免主线程被阻塞。在Android中也是同理,所以我们需要自己手动创建新的线程。

开启线程

  1. 创建子类继承Thread。实现run(),并将耗时操作放在其中。最后在主线程中创建子类对象,并调用start()
  2. 实现Runnable接口,并实现其run()方法。之后在主线程中调用Thread(Runnable).start()
  3. 使用Kotlin的内置顶层函数thread(),并传入实现耗时操作的lambda表达式

异步消息处理机制 (Handler)

相关概念

  • Message:线程之间传递的信息,可以携带少量信息(what, arg1, arg2, obj)
  • Handler:用于发送和处理消息;发送一般使用sendMessage() / post(),要处理的消息一般会传递到handleMessage()
  • MessageQueue:用于存放所有通过Handler发送的消息,没有被处理的消息就会一直存在于队列中。每个线程只会有一个MessageQueue对象
  • Looper:相当于MessageQueue的管家。当其loop()被调用后,会启动一个死循环,将MessageQueue收到的消息不断去除,并传递到Handler的handleMessage()中。每个线程也只有一个Looper对象

使用方法

  1. 主线程创建Handler对象,并重写handleMessage()方法
  2. 子线程中需要进行UI操作时,就创建一个Message对象,通过Handler将消息发送出去
  3. 消息被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息
  4. 最后分发回HandlerhandleMessage()

因为Handler是在主线程中创建的,所以handleMessage()中的代码也会在主线程中运行

AsyncTask (Deprecated in Java)

其实现原理也是基于异步消息处理机制,只是Android进行了封装。

使用方法

  1. 创建子类继承AsyncTask,并指定泛型参数(Params,Progress,Result)
  2. 重写方法
    • onPreExecute():后台任务开始前调用。可以进行初始化操作
    • doInBackground(Params...):在子线程中执行后台耗时操作,需要更新进度时,调用publishProgress()
    • onProgressUpdate(Progress...):执行UI操作
    • onPostExecute(Result):后台任务结束后调用,返回数据作为参数传入。可进行收尾操作
  3. 启动任务:YourAsyncTask().execute(),此时可传入任意参数,将传递至doInBackground()

Service (LocalService)

此处的Service均为 LocalService ,并非涉及到AIDL的RemoteService

Service用于实现程序后台运行。当程序切换到后台时,Service仍然可以保持正常运行。其适合于下列情况:

  1. 不需要和用户交互
  2. 要求长期运行的任务

Service并不是运行在独立的进程之中,而且依附于创建Service的进程。当创建Service的进程被kill后,Service便停止运行。同样的,Service并不会主动开启线程,所以需要手动创建子线程去执行耗时操作,以避免ANR。

注意:

  1. ServiceActivity一样,需要在AndroidManifest.xml中进行注册
  2. 后台Service在App进入后台后,有几率被回收。如果需要长期在后台执行任务,请使用前台Service / WorkManager

使用方法

  1. 创建子类继承Service,并实现抽象方法onBind()
  2. 重写常用方法
    • onCreate():在Service创建时调用
    • onStartCommand(intent, flag, startId):在Service 每次被启动时 调用,可以将操作逻辑放置于此
    • onDestroy():在Service被销毁时调用,回收资源
  3. 启动/停止Service
    • 使用Context类的startService(Intent) / stopService(Intent)
    • Service的stopSelf()

与Activity进行通信

使用onBind()方法,可以让ActivityService进行通信。

使用方法

  1. 创建子类继承Binder,并在其中创建需要实现具体后台操作的方法
    • 比如:需要进行后台下载,那么就创建startDownload()getProgress()方法
    • 这个子类可以定义在Service子类的内部
  2. 在Service子类中重写onBind()方法,并将Binder子类的对象实例作为其返回值
  3. 创建子类继承ServiceConnection,并重写onServiceConnected(ComponentName, IBinder)onServiceDisconnected(ComponentName)方法
    • onServiceConnected()会在Activity和Service成功绑定后调用。可以在其中将IBinder对象向下转型为自己定义的Binder子类对象,并在方法中调用对象的后台操作方法
    • onServiceDisconnected()会在创建Service的进程崩溃/killed之后被调用 (不太常用)
    • 因为这个子类表示 “Service被Activity绑定/解绑后想要执行的Binder子类的方法(也就是具体的后台操作)”,所以可以将其定义在需要使用Service的Activity中
  4. 调用Context的bindService(Intent, ServiceConnection, flags) / unbindService(ServiceConnection)

Service在整个Application范围内都是通用的 —— 任何一个Activity都可以和它绑定,并且在绑定后也都可以获取相同的Binder子类对象

生命周期

总体流程:onCreate() -> onStartCommand() -> onBind() -> onUnbind() -> onDestroy()

但是根据上面提到的内容,我们可以将Service的调用分为三类。而这三类的Service调用,会产生不同的Service生命周期(也就是说,Service生命周期的总体流程并一定会全部走一遍)

  1. 仅调用Context.startService() / Context.stopService():onCreate() -> onStartCommand() -> onDestroy()
  2. 仅调用Context.bindService() / Context.unbindService():onCreate() -> onBind() -> onUnbind() -> onDestroy()
  3. 四种方法都调用:onCreate() -> onStartCommand() -> onBind() -> onUnbind() -> onDestroy()

注意:

  1. 每个Serivce只会存在一个实例。所以在类型1中,只有第一次调用startService()才会回调onCreate()和onStartService()。除非停止Service实例,否则之后无论启动多少次Service,都只会回调onStartCommand();同样的,无论是否调用bindService() / unbindService(),Service实例都会一直存在于进程中,直至调用stopService() / stopSelf() / 系统回收内存 / 用户在系统设置中手动停止Service,之后才会回调onDestroy()
  2. 在类型2中,当Activity第一次绑定Service后,Service实例就会被创建。回调onCreate(),但并不调用onStartCommand()。而Service实例直至调用unbindService()或与其相关联的Context不存在(比如被系统回收)才会被销毁,并回调onDestroy()
  3. 当四种方法都被调用时(也就是类型3),只有在unbindService()和stopService()都被调用后,Service实例才会被销毁,并回调onDestroy()。如果仅调用unbindService(),那么只会回调onUnbind()

Service技巧

前台Service

在Android 8之后,只有App保持在前台时才能保证Service不被回收。如果要保证应用在后台时Serivice也能保持运行,可以使用前台Service。其与后台Service的区别在于,前台Service会在状态栏常驻显示,样式类似于普通通知,但用户不能清除。(其实就是告知用户前台Service正在运行,防止软件恶意使用Service占用手机资源)

class Myservice: Service() {
    ...
    override fun onCreate() {
        super.onCreate()
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel("my_service", "foreground service notification", NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        val intent = Intent(this, MainActivity::class.java)
        val pi = PendintIntent.getActivity(this, 0, intent, 0)
        val notification = NotificationCompat.Builder(this, "my_service")
            .setContentTitle("this is content title")
            .setContentText("this is content text")
            .setSmallIcon(R.drawable.small_icon)
            .setLargeIcon(BitmapFactory.decodeResource(resource, R.drawablee.larger_icon))
            .setContentIntent(pi)
            .build()
        startForeground(1, nofitication)
    }
}

Android 9之后,前台Service必须在AndroidManifest中进行权限声明。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicetest">

    <user-permission android:name="android.permission.FOREGROUND_SERVICE" />
    ...

</manifest>

使用IntentService (Deprecated)

Service默认是运行在主线程中的,如果执行大量耗时操作则容易导致ANR。所以可以在Service中的具体方法中开启子线程去处理耗时的业务逻辑;当完成耗时操作后,调用stopSelf()结束Service。

除了自己手动操作外,还可以考虑继承IntentService类,并实现会运行在子线程的onHandleIntent()抽象方法。并且当子线程运行完毕后,系统会自动调用onDestroy()