Service
Service是Android中实现程序后台运行的解决方案,即使程序被切换到后台,或者用户打开了另一个应用程序,Service仍然能够保持正常运行。但Sevice依赖于其创建时所在的应用程序进程,当该程序被杀掉时,所有依赖于它的Service也会停止运行。
Android多线程编程
线程的基本用法
thread{//编写具体的逻辑}
在子线程中更新UI
Android的UI是线程不安全的,只能在主线程中进行更新操作,否则会出现异常。
因此Android提供了异步消息处理机制。
class MainActivity : AppCompatActivity() {
val updateText = 1//用于表示更新TextView这个动作
//新建一个Handler对象,并重写handleMessage方法。
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
// 在这里可以进行UI操作
when (msg.what) {
updateText -> textView.text = "Nice to meet you"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
changeTextBtn.setOnClickListener {
thread {
val msg = Message()
msg.what = updateText
handler.sendMessage(msg) // 将Message对象发送出去
}
}
}
}
解析异步消息处理机制
Android中异步消息处理主要由4个部分组成:
- Message:是在线程之间传递的消息,它可以携带少量信息,用于在不同线程之间传递数据。除了what字段以外,还可以使用arg1和arg2字段携带整型数据,使用obj字段携带Object对象。
- Handler:用于发送和处理消息。发送一般是sendMessage和post等方法,而信息经过一系列处理后,最终会传递到handlerMessage方法中。
- MessageQueue:消息队列,用于存放所有通过Handler传递的消息。每个线程中只会有一个MessageQueue对象。
- Looper:是MessageQueue的管家,调用loop方法后,就会进入一个无限循环,每当发现一个消息就会将它取出,并传递到handleMessage方法中。每个线程只会有一个Looper对象。
使用AsyncTask
AsyncTask是一个抽象类,创建一个子类去继承它。继承时可以指定三个泛型参数。
- Params:执行AsyncTask时需要传入的参数,可用于后台任务使用。
- Progress:可以使用这里指定的泛型作为进度单位来显示当前进度。
- Result:指定泛型作为返回值。
然后还需要重写四个方法:
- onPreExecute:在后台任务开始执行之前调用,用于进行初始化操作。
- doInBackground(Params…):这个方法中的所有代码都会在子线程中运行,应该在这里处理耗时任务,任务完成后可以通过return返回执行结果。在这个方法中不可以进行UI操作,如果要反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。
- onProgressUpdata(Progress…):当publishProgress(Progress…)方法被调用后,onProgressUpdata(Progress…)很快就会被调用,参数由后台传递。在这个方法中可以对UI进行操作。
- onPostExecute(Result…):当后台任务执行完毕并通过return语句返回时,这个方法很快就会被调用。返回数据会作为参数传递到此方法中,可以利用返回数据进行一些UI操作。
要想启动这个任务只需获取实例并调用execute方法。
Service的基本用法
定义一个Service
- 新建一个类继承自Service,并重写onBind方法。
- 在AndroidManifest中注册。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
启动和停止Service
在Activity中主要是借助Intent来实现。
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
stopService(intent) // 停止Service
在Service内部可以调用stopSelf方法来自我停止运行。
当Service启动时会调用内部的onCreate和onStartCommand方法,onCreate在第一次被创建时调用,而onStartCommand在每次启动Service时都会调用。当Service停止时会调用onDestroy方法。
Activity和Service进行通信
class MyService : Service() {
private val mBinder = DownloadBinder()//创建实例
//新建DownloadBinder类继承自Binder,并提供两个方法。
class DownloadBinder : Binder() {
fun startDownload() {
Log.d("MyService", "startDownload executed")
}
fun getProgress(): Int {
Log.d("MyService", "getProgress executed")
return 0
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder//返回实例
}
...
}
之后再Activity中使用MyService
class MainActivity : AppCompatActivity() {
//
lateinit var downloadBinder: MyService.DownloadBinder
//创建一个ServiceConnection的匿名类实现,并重写了onServiceConnected和onServiceDisconnected方法
private val connection = object : ServiceConnection {
//在Activity和Service成功绑定时调用
override fun onServiceConnected(name: ComponentName, service: IBinder) {
//通过向下转型得到了DownloadBinder实例
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}
//在Service创建进程崩溃或被杀掉时调用
override fun onServiceDisconnected(name: ComponentName) {
Log.d("MyService", "onServiceDisconnected")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
bindServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
// 绑定Service,三个参数分别为为Intent,ServiceConnection实例,标志位
// BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
unbindServiceBtn.setOnClickListener {
unbindService(connection) // 解绑Service
}
...
}
Service的生命周期
第一次调用startService时会创建Service,onCreate会执行,然后执行onStartCommand方法。此后调用startService时,相应的Service会启动,onStartCommand方法会执行。
调用bindService可以获取一个Service的持久连接,如果Service没被创建过onCreate会先执行,然后onBind方法会执行。
调用startService再调用stopService时,onDestroy会执行。调用bindService再调用unbindService时,onDestroy也会执行。
当startService和bindService都被调用时,就要同时调用stopService和unbindService方法onDestroy才会执行。
Service的更多技巧
使用前台Service
由于应用进入后台后,Service随时都有可能被系统回收,为了让Service能够一直保持运行状态,就可以使用前台Service。它于普通Service最大的区别就是一直会有一个正在运行的图标再系统状态栏显示,非常类似于通知的效果。
修改onCreate中的代码为
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate executed")
// 创建一个NotificationManager实例对通知进行管理
// 通过调用getSystemService方法获取,该方法接受一个字符串用于确定获取系统的哪个服务。
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// 先进行版本判断,因为NotificationChannel是Android8.0中新增的API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 使用NotificationChannel类构建一个通知渠道
// 三个参数分别为渠道ID、渠道名称、重要等级
// 重要等级主要有四种:IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW、IMPORTANCE_MIN对应重要程度从高到低
val channel = NotificationChannel("my_service", "前台Service通知", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent(this, MainActivity::class.java)
// 创建一个PendingIntent实例可以使用getActivity、getBroadcast或getService方法
// 四个参数分别为context、一般用不到传0即可、Intent、用于确定行为有四个值可选
// FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT
// 通常情况下传入0即可
val pi = PendingIntent.getActivity(this, 0, intent, 0)
// 使用NotificationCompat.Builder来构造一个Notification对象
// 第一个参数是context,第二个是渠道ID
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(resources, R.drawable.large_icon))//通知大图标
.setContentIntent(pi)// 传入PendingIntent,用户点击时就会执行相应的逻辑
.build()
// 调用startForeground方法接收两个参数通知ID和Notification对象
// 之后就会让Service变成前台Service,并在系统状态栏显示出来
startForeground(1, notification)
}
之后在AndroidManifest中进行权限声明
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
使用IntentService
IntentService可以自动停止。
// 继承时要求必须调用父类的构造函数并传入字符串,该字符串只在调试时有用
class MyIntentService : IntentService("MyIntentService") {
// 实现onHandleIntent抽象方法,可以来处理一些耗时的逻辑
override fun onHandleIntent(intent: Intent?) {
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
}
override fun onDestroy() {
super.onDestroy()
Log.d("MyIntentService", "onDestroy executed")
}
}