学习Android之Service

 Service是什么

 是Android中实现程序后台运行的解决方案。

 注意:Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。

 实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程中的。所以,我们需要在Service内部手动创建子线程,并在 这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

 定义一个Service

 可以在包名下右键New一个Service。

class MyService : Service() {

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }
}

 

 onBind方法是Service中唯一的抽象方法。

 处理事情的逻辑还需要重写一些方法:

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }

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

 

 这3个方法最常用。

onCreate()方法会在Service创建的时候调用;

onStartCommand()方法会在每次Service启动的调用;

onDestory()方法会在Service销毁的时候调用。

 一般,如果需要Service一启动就立刻执行任务,就将逻辑写在onStartCommand()方法中。当Service销毁时,在onDestory()方法中回收不再使用的资源。

 另外,每个Service都需要在AndroidManifest.xml文件中进行注册才能生效。

 

 启动和停止Service

 借助Intent来实现。

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

 

 另外,可以在Service内部调用stopSelf()方法自我停止。

 

 但是从Android 8.0系统开始,应用的后台功能被削弱,现在只有当应用保持在前台可见状态的情况下,Service才能稳定运行,一旦进入应用后台,Service随时都有可能被系统回收。

 如果需要长期在后台执行一些任务,可以使用前台Service和WorkManager。

 

 Acitivty和Service通信

 虽然Service是在Activity里启动的,但是在启动了Service之后,Activity与Service基本就没有什么关系了。

 如果我们需要用Activity来控制住Service就需要借助onBind()方法了;

 比如: 我们希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。

 修改MyService中的代码,如下所示:

    private val mBinder = DownloadBinder()

    class DownloadBinder : Binder() {
        fun startDownload() {
            Log.d("MySeriver", "startDownload: ")
        }
        fun getProgress(): Int {
            Log.d("MySeriver", "getProgress: ")
            return 0
        }
    }

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

 

 这里新建了一个DownloadBinder类继承Binder,在内部提供方法。接着在MyService中创建了DownloadBinder的实例,在onBind()方法里返回了这个实例。

 那么在Activity中如何调用呢?

 

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.getProgress()
        }
        override fun onServiceDisconnected(name: ComponentName) {
            TODO("Not yet implemented")
        }
    }

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

        bindServiceBtn.setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE)   //绑定Service
        }
        unbindServiceBtn.setOnClickListener {
            unbindService(connection)   //解绑Service
        }
    }
}

 

 首先创建ServiceConnection的匿名类实现,重写里面的两个方法。第一个方法是在Activity和Service成功绑定的时候调用,第二个只有在Service的创建进程崩溃或者被杀掉的时候调用,这个方法不太常用。

 我们又通过向下转型得到了DownloadBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。

 现在我们可以在Activity中调用DownloadBinder中的任何public方法。这里在onServiceConnected()方法中调用了DownloadBinder的startDownload()和getProgress()方法。

 但是Activity和Service的绑定是在按钮的点击事件里面完成的。调用bindService()方法进行绑定,接收三个参数:

  第一个参数:刚刚构建出的Intent对象,

  第二个参数:是前面创建出的ServiceConnection的实例,

  第三个参数:是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service。

 解除绑定调用unbindService()方法即可。

 

 Service的生命周期

 Service有onCreate()、onStartCommand()、onBind()和onDestroy()等方法。

 一旦调用了startService()方法,相应的Service就会启动,并回调onStartCommand()方法,如果这个Service之前还没有创建过,就会先执行onCreate()方法,再执行onStartCommand()方法。

 注意:虽然每调用一次startService()方法,onStartCommand()方法就会执行一次,但实际上每个Service只会存在一个实例。所以不管调用了多少次onStartCommand()方法,只需要调用一次stopService()或stopSelf()方法,Service就会停止。

 还可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和Service进行通信了。

 注意:如果对一个Service既调用了startService()方法,又调用了bindService()方法的,这种情况下根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

 

 前台Service

 从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。

 前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息。

 如何创建前台Service,修改MyService中的代码,如下所示:

    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", "前台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("this is content title")
            .setContentText("this is content text")
            .setSmallIcon(androidx.core.R.drawable.notification_icon_background)
            .setLargeIcon(BitmapFactory.decodeResource(resources, androidx.constraintlayout.widget.R.drawable.abc_btn_default_mtrl_shape))
            .setContentIntent(pi)
            .build()
        startForeground(1, notification)
    }

 用了创建通知的方法,只不过构建Notification对象后并没有使用NotificationManager将通知显示出来,而是调用了startForeground()方法,它接收两个参数:

  第一个参数:通知的id;

  第二个参数:构建的Notification对象。

 调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来。

 另外,Android 9.0系统开始,前台Service需要去AndroidManifest.xml文件中进行权限声明:

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

 

 启动前台Service后,即使退出应用程序,MyService也会一直运行,如果杀掉应用,MyService就会停止运行了。

 

 IntentService

 Service中的代码都是默认运行在主线程中的,如果直接在Service中处理一些耗时逻辑,就很容易ANR(Application Not Responding)。

 这个时候就需要用到Android多线程编程技术了。可以在Service的每个具体方法里开启一个子线程,在里面处理耗时逻辑。

 写法如下:

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

 

 但是,这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()方法,或者被系统回收,Service才会停止。

 所以,如果想要实现让一个Service在执行完毕后自动停止的功能,就要在thread中处理完逻辑的后面加上stopSelf()方法。

        thread {
            // 处理具体的逻辑
            stopSelf()
        }

 

 但是偶尔会忘记开启线程或者调用stopSelf()。所以Android就专门提供了一个IntentServiec类,可以创建一个异步的、自动停止的Service、

 新建一个MyIntentService类继承自IntentService,代码如下所示:

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(p0: Intent?) {
        // 打印当前线程id
        Log.d("MyIntentService", "Thread id is ${Thread.currentThread().name}")
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "onDestroy executed")
    }
}

 

 这里需要先调用父类的构造函数,并传入一个字符串,只是在调用的时候使用的。

 然后在子类中实现onHandleIntent()这个抽象方法,这个方法中可以处理一些耗时逻辑,因为它已经运行在子线程中了。

 它是会在运行完成后自动停止的,onDestroy()方法是为了验证它有没有自动停止。

 现在来启动这个IntentService,修改MainActivity中的代码,如下所示:

        startIntentServiceBtn.setOnClickListener {
            // 打印主线程ID
            Log.d("MainActivity", "Thread id is ${Thread.currentThread().name}")
            val intent = Intent(this, MyIntentService::class.java)
            startService(intent)
        }

 

 其实IntentService的启动方式和普通的Service没什么两样。

 对了,Service都是需要去AndroidManifest.xml里注册的:

        <service android:name=".MyIntentService"
            android:enabled="true"
            android:exported="true"/>

 

posted @ 2022-03-14 14:14  PeacefulGemini  阅读(212)  评论(0)    收藏  举报
回顶部