学习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"/>

浙公网安备 33010602011771号