【第一行代码学习笔记】第十章 后台:Service

第10章:Service


10.1 Service的概念

Service用来使程序在后台运行。它依赖于创建Service时所在的应用程序进程。当某个应用进程被杀掉时,所有依赖于该进程的Service都会停止。

所有代码默认运行在主线程中,Service不会自动开启线程。我们需要在Service内部手动创建子线程,并在这里执行具体任务。

10.2 Android多线程编程

Java多线程编程中,当执行一些耗时操作时,如果不将其放在子线程里运行,就会导致主线程被阻塞(例如发起一条网络请求,而网速过慢时)。

10.2.1 线程的基本用法

有这么几种和java离类似的语法。

/* 第一种:继承自Thread类 */
class MyThread: Thread {
    override fun run() {
        // 编写具体逻辑
    }
}
// 启动线程
MyThread.start() 

/* 第二种:实现Runnable接口 */
class MyThread: Runnable {
    override fun run() {
    // 编写具体逻辑
    }
}
// 启动线程
val myThread = MyThread()
Thread(myThread).start()

/* 第三种:使用Lambda */
Thread {
    // 编写具体逻辑
}.start()

Kotlin提供了一种很简单的开启线程的方式:

thread {
    // 编写具体逻辑
}

10.2.2 在子线程中更新UI

Android的UI是线程不安全的。如果要更新UI元素,必须在主线程中更新。如果直接在子线程中更新UI,则会直接崩溃。这时就需要异步消息处理机制了!

线程不安全:在没有提供加锁机制保护的情况下,多个线程对共享资源进行写操作,可能导致数据不一致或脏数据的问题。如果多个线程同时修改同一份资源,可能会出现竞态条件,导致程序功能不能正确完成。

class MainActivity: AppCompatActivity() {

    lateinit var binding : ActivityMainBinding
    val updateText = 1 // 用于表示TextView这个动作

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.changeTextBtn.setOnClickListener {
            thread {
                val msg = Message()
                msg.what = updateText
                handle.sendMessage(msg) // 发出消息
            }
        }
    }

    val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) { // 接收到消息后做什么
            // 进行UI操作
            when (msg.what) {
                binding.updateText -> textView.text = "Nice to meet you"
            }
        }
    }

}

10.2.3 异步消息处理机制的原理

  1. Message:
    Message是在线程之间传递的消息,可以携带少量信息。可以使用Message的what字段;使用arg1字段和arg2字段携带整形数据;使用obj字段携带一个Object对象。

  2. Handler:
    主要用于发送和处理消息的。发送是Handler.sendMessage();发出的消息最后会传递到Handler的handleMessage()方法中

  3. MessageQueue:
    消息队列,存放所有通过Handler发送的消息。每个线程中只会有一个MessageQueue对象。

  4. Looper:
    调用Looper的loop()方法后会进入一个循环,每当发现MessageQueue中存在一条消息就会将其取出,并传递到handleMessage()方法中。每个线程中只会有一个Looper对象。

10.2.3 AsyncTask

AsncTask有三个泛型参数:

  • Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;
  • Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用这里指定的类型作为进度单位;
  • Result:后台任务执行完毕后如果需要返回结果,则指定这里的泛型作为返回值类型;

经常需要重写的方法有一下四个:

  • onPreExecyte():后台任务开始执行前调用,用于初始化。例如显示一个进度条对话框等;
  • doInBackground(Params...):此方法中所有代码都会在子线程中运行,应该在这里进行耗时任务。但不能进行UI更新。更新UI需要调用publishProgress(Progress...)方法。
  • onProgressUpdate(Progress...):调用publishProgress(Progress...)方法后,onProgressUpdate()会很快被调用。该方法中携带的参数就是后台任务中传递过来的。在此方法中可以对UI进行更新。
  • onPostExecute(Result):当后台任务执行完毕并通过return语句进行返回时,这个方法很快会被调用。返回的数据会作为参数传递到此方法中,可以进行一些UI操作。

简单来说就是:在doInBackground(Params...)中执行具体的耗时任务;在onProgressUpdate(Progress...)中进行UI操作;在onPostExecute(Result)中执行一些任务的收尾工作。

想要启动任务,只需调用.execute()方法即可。

10.3 Service的基本用法

10.3.1 定义Service

class MyService : Service() {
    // 用Activity控制Service
    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }
    // 第一次创建Service时执行
    override fun onCreate() {
        super.onCreate()
    }
    // 每次运行Service时执行
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }
    // 关闭Service时执行
    override fun onDestroy() {
        super.onDestroy()
    }
}

10.3.2 启动和停止Service

通过startService()stopService()方法传入intent来启动和停止Service:

    binding.startServiceBtn.setOnClickListener {
        val intent = Intent(this,  MyService::class.java)
        startService(intent)
    }
    binding.stopServiceBtn.setOnClickListener {
        val intent = Intent(this,  MyService::class.java)
        stopService(intent)
    }

10.3.3 Activity和Service通信

借助onBind()方法可以通过Activity来指挥Service。
修改MyService.kt中的代码,添加一个下载功能:

    class MyService : Service() {

        private val mBinder = DownloadBinder()

        class DownloadBinder: Binder() {
            // 开始下载
            fun startDownload() {
                Log.d("MainActivity", "StartDownload")
            }
            // 查看下载进度
            fun getProcess(): Int {
                Log.d("MainActivity", "getProcess executed.")
                return 0
            }
        }

        override fun onBind(intent: Intent): IBinder {
            return mBinder
        }
}

接着在MainActivity中分别设置两个将Activity与Service绑定/解绑的按钮:

class MainActivity : AppCompatActivity() {
    ...
    lateinit var downloadBinder: MyService.DownloadBinder

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            downloadBinder = service as MyService.DownloadBinder // 向下转型
            downloadBinder.startDownload()
            downloadBinder.getProcess()
        }
        override fun onServiceDisconnected(name: ComponentName?) {
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.startServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
        }
        binding.stopServiceBtn.setOnClickListener {
            unbindService(connection) // 解绑Service
        }
    }
}

其中bindService(intent, connection, flags)方法接收三个参数。第三个参数是标志位,Context.BIND_AUTO_CREATE表示在Activity和Service绑定后自动创建Service,即执行MyService中的onCreate。

10.4 Service的生命周期

在任何位置调用Context的startService()方法,就会调用onStartCommand()方法。若这个Service还未被创建过,那么会先执行onCreate()方法。

每个Service只会存在一个实例,因此无论调用多少次startService(),只需调用一次stopService()或者stopSelf(),Service就会停止。

另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时会回调Service中的onBind()方法。若这个Service还未被创建过,那么会先执行onCreate()方法。

调用stopService()unBindService()都会执行onDestroy()。如果既调用了startService()方法,又调用了bindService(),那么根据Android机制,一个Service只要被启动或者被绑定了之后都会处于运行状态。因此此时想要结束Service,就要同时调用stopService()unBindService()方法。

10.5 Service的更多技巧

10.5.1 前台Service

普通Servic随时可能会被系统回收。如果希望Service保持2运行,就可以考虑使用前台Service。前台Service和普通Service的最大区别就是,它会一直有一个正在运行的图标在系统的状态栏显示。

创建一个前台Service:

    override fun onCreate() {
        super.onCreate()

        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channel = NotificationChannel("my_service", "前台Service通知", NotificationManager.IMPORTANCE_DEFAULT)
        manager.createNotificationChannel(channel)

        val intent = Intent(this, MainActivity::class.java)
        val pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        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)
            .build()
        startForeground(114514, notification) // startForeground(id, Notification); 调用此方法使MyService成为前台Service
    }

同时需要在AndroidManifest.xml中声明相关权限:

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

书上只声明了第一行,这是使用前台Service的权限。但是如果不声明第二行的通知权限,应用就无法发布通知。因此必须添加这行代码之后,再在手机设置中讲ServiceTest的通知权限打开(如果不声明,设置中该应用的通知权限无法更改)。

10.5.2 使用IntentService

注意:该方法现已被废弃。最好使用WorkManager

前面说过Service中如果处理一些耗时逻辑,很容易导致ANR (Application Not Responding) 的情况。此时就需要用到多线程编程的技术,开启一个子线程:

class MyService : Service() {
    ...
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            // 处理具体逻辑
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

如果想要实现让Service在执行完毕后自动停止,应该在thread里加一个stopSelf()方法:

class MyService : Service() {
    ...
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            // 处理具体逻辑
            stopSelf()
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

但是总有人会忘记开启子线程,或者忘记调用stopSelf()方法。怎么办呢?

Android专门提供了一个IntentService类:

class MyIntentService : IntentService("MyintentService") { // 这个字符串可以随意制定,只在调试时有用
    override fun onHandleIntent(intent: Intent?) {
        Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "onDestroy")
    }
}

这样就可以集开启线程和自动停止于一身。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值