引言:
本文是在阅读完郭神《第一行代码:Android》的第十章后,对书中内容做出的简要总结。作为新手开发者,笔记中难免会存在错误或缺漏。如果在浏览文章中发现任何的问题,欢迎在评论区指出。
Android 多线程
当我们需要进行一些耗时操作时,可以将其放在子线程中进行处理,以避免主线程被阻塞。在Android中也是同理,所以我们需要自己手动创建新的线程。
开启线程
- 创建子类继承
Thread
。实现run()
,并将耗时操作放在其中。最后在主线程中创建子类对象,并调用start()
- 实现
Runnable
接口,并实现其run()
方法。之后在主线程中调用Thread(Runnable).start()
- 使用Kotlin的内置顶层函数
thread()
,并传入实现耗时操作的lambda表达式
异步消息处理机制 (Handler)
相关概念
- Message:线程之间传递的信息,可以携带少量信息(what, arg1, arg2, obj)
- Handler:用于发送和处理消息;发送一般使用
sendMessage()
/post()
,要处理的消息一般会传递到handleMessage()
中 - MessageQueue:用于存放所有通过Handler发送的消息,没有被处理的消息就会一直存在于队列中。每个线程只会有一个MessageQueue对象
- Looper:相当于MessageQueue的管家。当其
loop()
被调用后,会启动一个死循环,将MessageQueue收到的消息不断去除,并传递到Handler的handleMessage()
中。每个线程也只有一个Looper对象
使用方法
- 主线程创建
Handler
对象,并重写handleMessage()
方法 - 子线程中需要进行UI操作时,就创建一个
Message
对象,通过Handler
将消息发送出去 - 消息被添加到
MessageQueue
的队列中等待被处理,而Looper
则会一直尝试从MessageQueue
中取出待处理消息 - 最后分发回
Handler
的handleMessage()
中
因为
Handler
是在主线程中创建的,所以handleMessage()
中的代码也会在主线程中运行
AsyncTask (Deprecated in Java)
其实现原理也是基于异步消息处理机制,只是Android进行了封装。
使用方法
- 创建子类继承
AsyncTask
,并指定泛型参数(Params,Progress,Result) - 重写方法
onPreExecute()
:后台任务开始前调用。可以进行初始化操作doInBackground(Params...)
:在子线程中执行后台耗时操作,需要更新进度时,调用publishProgress()onProgressUpdate(Progress...)
:执行UI操作onPostExecute(Result)
:后台任务结束后调用,返回数据作为参数传入。可进行收尾操作
- 启动任务:
YourAsyncTask().execute()
,此时可传入任意参数,将传递至doInBackground()
中
Service (LocalService)
此处的
Service
均为LocalService
,并非涉及到AIDL的RemoteService
Service
用于实现程序后台运行。当程序切换到后台时,Service
仍然可以保持正常运行。其适合于下列情况:
- 不需要和用户交互
- 要求长期运行的任务
但Service
并不是运行在独立的进程之中,而且依附于创建Service的进程。当创建Service的进程被kill后,Service
便停止运行。同样的,Service
并不会主动开启线程,所以需要手动创建子线程去执行耗时操作,以避免ANR。
注意:
Service
和Activity
一样,需要在AndroidManifest.xml
中进行注册- 后台Service在App进入后台后,有几率被回收。如果需要长期在后台执行任务,请使用前台Service /
WorkManager
使用方法
- 创建子类继承
Service
,并实现抽象方法onBind()
- 重写常用方法
onCreate()
:在Service创建时调用onStartCommand(intent, flag, startId)
:在Service 每次被启动时 调用,可以将操作逻辑放置于此onDestroy()
:在Service被销毁时调用,回收资源
- 启动/停止
Service
- 使用Context类的
startService(Intent)
/stopService(Intent)
- Service的
stopSelf()
- 使用Context类的
与Activity进行通信
使用onBind()
方法,可以让Activity
和Service
进行通信。
使用方法
- 创建子类继承
Binder
,并在其中创建需要实现具体后台操作的方法- 比如:需要进行后台下载,那么就创建
startDownload()
和getProgress()
方法 - 这个子类可以定义在Service子类的内部
- 比如:需要进行后台下载,那么就创建
- 在Service子类中重写
onBind()
方法,并将Binder子类的对象实例作为其返回值 - 创建子类继承
ServiceConnection
,并重写onServiceConnected(ComponentName, IBinder)
和onServiceDisconnected(ComponentName)
方法onServiceConnected()
会在Activity和Service成功绑定后调用。可以在其中将IBinder
对象向下转型为自己定义的Binder子类对象,并在方法中调用对象的后台操作方法onServiceDisconnected()
会在创建Service的进程崩溃/killed之后被调用 (不太常用)- 因为这个子类表示 “Service被Activity绑定/解绑后想要执行的Binder子类的方法(也就是具体的后台操作)”,所以可以将其定义在需要使用Service的Activity中
- 调用Context的
bindService(Intent, ServiceConnection, flags)
/unbindService(ServiceConnection)
Service
在整个Application范围内都是通用的 —— 任何一个Activity
都可以和它绑定,并且在绑定后也都可以获取相同的Binder
子类对象
生命周期
总体流程:onCreate() -> onStartCommand() -> onBind() -> onUnbind() -> onDestroy()
但是根据上面提到的内容,我们可以将Service的调用分为三类。而这三类的Service调用,会产生不同的Service生命周期(也就是说,Service生命周期的总体流程并一定会全部走一遍)
- 仅调用Context.startService() / Context.stopService():onCreate() -> onStartCommand() -> onDestroy()
- 仅调用Context.bindService() / Context.unbindService():onCreate() -> onBind() -> onUnbind() -> onDestroy()
- 四种方法都调用:onCreate() -> onStartCommand() -> onBind() -> onUnbind() -> onDestroy()
注意:
- 每个Serivce只会存在一个实例。所以在类型1中,只有第一次调用startService()才会回调onCreate()和onStartService()。除非停止Service实例,否则之后无论启动多少次Service,都只会回调onStartCommand();同样的,无论是否调用bindService() / unbindService(),Service实例都会一直存在于进程中,直至调用stopService() / stopSelf() / 系统回收内存 / 用户在系统设置中手动停止Service,之后才会回调onDestroy()
- 在类型2中,当Activity第一次绑定Service后,Service实例就会被创建。回调onCreate(),但并不调用onStartCommand()。而Service实例直至调用unbindService()或与其相关联的Context不存在(比如被系统回收)才会被销毁,并回调onDestroy()
- 当四种方法都被调用时(也就是类型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()