Android四大组件之Service

一 基本概念

  • Service是Android中实现程序后台运行的解决方案,适用于执行不需要和用户交互而且还要求长期运行的任务
  • Service的运行不依赖于任何用户界面,即使程序被切换至后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行
  • Service是依赖于创建Service时所在的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行
  • Service是运行在主线程中的,也就是说我们需要在Service的内部手动创建子线程,并在里面执行具体的任务,否则可能阻塞主线程

二 Android多线程

  • 基本的线程实现方式与Java是一致的
  1. 继承Thread类
class MyThread: Thread(){
    override fun run(){
    }
}
MyThread().start()

2.实现Runnable接口

class MyThread: Runnable{
    override fun run(){
    }
}
val myThread = MyThread()
Thread(myThread).start()

当然 如果不想专门定义一个类去实现Runnable接口,也可以使用Lambda的方式,这种写法更常见:

Thread{
    //耗时操作
}.start()

kotlin还提供了一种更加简单的开启线程的方式:

thread{
    //具体逻辑
}

thread是一个kotlin内置的顶层函数,我们只需要在Lambda表达式中编写具体的逻辑就可以了,连start()方法thread函数在内部帮我们全部都处理好了
以上都与java一致,接下来看看Android多线程与Java多线程不同的地方:

  • 异步消息处理机制:Handler
class MainActivity : AppCompatActivity() {

    private lateinit var changeTextBtn: Button
    private lateinit var mTextView: TextView
    val handler = object : Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            when(msg.what){
                1 -> mTextView.text = "Hello World"
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initView()
        setListener()
    }

    private fun setListener() {
        changeTextBtn.setOnClickListener {
            thread {
                val msg = Message()
                msg.what = 1
                handler.sendMessage(msg)
            }
        }
    }

    private fun initView() {
        changeTextBtn = findViewById(R.id.changTextBtn)
        mTextView = findViewById(R.id.text)
    }
}
  • AsyncTask
    这是Android提供的一个线程切换工具类,它的原理也是使用的handler,只不过做了封装。
    AsyncTask是一个抽象类,如果我们想使用它,就必须创建一个子类去继承它,在继承时我们可以为AsyncTask类指定3个泛型参数,这三个参数的用途如下:
  • Params
    在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress
    在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
  • Result
    当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
class DownloadTask : AsyncTask<Unit, Int, Boolean>(){
    ...
}

这里把AsyncTask的第一个泛型参数指定为Unit,表示在执行AsyncTask的时候不需要传入参数给后台任务;第二个泛型参数指定为Int,表示使用整型数据来作为进度显示单位;第三个泛型指定为Boolean,则表示使用布尔型数据来反馈执行结果。

  1. onPreExecute()
    这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
    2.doInBackground(Params...)
    这个方法中的代码都会在子线程运行,在这里做耗时操作,任务一旦完成,就可以通过return语句返回任务执行结果,当然如果第三个泛型参数指定为Unit就可以不用返回,这里面是不能进行UI操作的,如果需要更新UI元素,比如反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
    3.onProgressUpdate(Progress...)
    当在后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdate(Progress...)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以更新UI。
    4.onPostExecute(Result)
    当后台任务执行完毕并通过return语句返回时,这个方法就会很快被调用,返回的数据会作为参数传递到此方法中,可以进行一些UI操作,比如根据结果关闭进度对话框等
    如果想要启动这个异步任务,只需要DownloadTask().execute(),当然也可以传入任意数量的参数,这些参数将会传递到DownloadTask的doInBackground()方法。

三 Service基本用法

class MyService : Service(){

    private val mBinder = DownloadBinder()

    class DownloadBinder : Binder(){
        fun startDownload(){
            Log.e("MyService", "MyService DownloadBinder ---startDownload")
        }

        fun getProgress(): Int{
            Log.e("MyService", "MyService DownloadBinder ---getProgress")
            return 0
        }
    }

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

    override fun onCreate(){
        super.onCreate()
        Log.e("MyService", "MyService---onCreate")
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int{
        Log.e("MyService", "MyService---onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy(){
         super.onDestroy()
         Log.e("MyService", "MyService---onDestroy")
    }
}
  • onCreate()会在Service创建的时候调用
  • onStartCommand()会在每次Service启动的时候调用
  • onDestroy()会在Service销毁时调用
  • 启动Service
    第一次启动:onCreate()onStartCommand()
    第二次启动:onStartCommand()
val intent = Intent(this, MyService::class.java)
startService(intent)
  • 停止Service---回调onDestroy()
val intent = Intent(this, MyService::class.java)
stopService(intent)

当然Service也可以自我停止,只需在Service内部调用stopSelf()方法即可。
Android8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台,Service随时都可能被系统回收。

  • Activity与Service通信
  • 绑定Service
class MainActivity : AppCompatActivity(){

    laterinit var downloadBinder: MyService.DownloadBinder
    private val connection = object : ServiceConnection{
        
        override fun onServiceConnected(name: ComponentName, service: IBinder){
            downloadBinder = service as MyService.DownloadBinder
            downloadBinder.startDownload()
            ...
        }
        
        override fun onServiceDisconnected(name: ComponentName){}
    }
    
    override fun onCreate(savedInstanceState: Bundle?){
        val intent = Intent(this, MyService::class.java)
        //绑定Service
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy(){
        super.onDestroy()
        unbindService(connection)
    }
}

onServiceConnected()方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用。这里传入Context.BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()不会执行
假设既调用了startService()又调用了bindService(),根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁,所以,这种情况下要同时调用stopService()unbindService()方法,onDestroy()方法才会执行。

四 使用前台Service

  • 如果不想应用进入后台后,Service随时都有可能被系统回收的风险,而是希望Service能够一直保持运行状态,就可以考虑使用前台Service。
  • 前台Service和普通Service最大的区别就在于前台Service一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,用户可以清楚的知道当前什么应用正在运行。
  • 由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,因此系统不会倾向于回收前台Service。
  • 创建前台Service
class MyService : Service(){
    ...
    override fun onCreate(){
        super.onCreate()
        Log.e("MyService", "MyService---onCreate")
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.0){
            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,0)
        val notification = NotificationCompat.Builder(this, "my_service")
            .setContentTitle("title")
            .setContentText("text")
            .setSmallIcon(R.drawable.small_icon)
            .setLargeIcon()
            .setContentIntent(pi)
            .build()
        startForeground(1, notification)
    }
    ...
}

startForeground()方法接收两个参数:第一个参数是通知的id;第二个参数是则是构建的Notification对象
调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来
另外,从Android9.0系统开始,使用前台Service必须在AndroidManifest.xml文件中进行权限声明:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  • Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()或被系统回收,Service才会停止
  • 如果想要创建一个异步的会自动停止的Service,Android专门提供了一个IntentService类:
class MyIntentService : IntentService("MyIntentService"){
    override fun onHandleIntent(intent: Intent?){
        //打印当前线程id
        Log.e("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }

    override fun onDestroy(){
        super.onDestroy()
        Log.e("MyIntentService", "onDestroy")
    }
}

这里首先必须要调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用。
然后可以在onHandleIntent()方法里处理一些耗时逻辑,而不用担心ANR,因为这个方法已经是在子线程中运行的了,并且,根据IntentService的特性,这个Service在onHandleIntent()方法运行结束后是会自动停止的,接下来证明一下根据打印结果:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("MainActivity", "Thread id is ${Thread.currentThread().name}")
        val intent = Intent(this, MyIntentService::class.java)
        startService(intent)
    }
}

当然不要忘记在manifest里面注册一下

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容