以上所学的都是关于服务最基本的一些用法和概念,当然也是最常用的。不过,仅仅满足于此显然是不够的,关于的更多高级使用技巧还在等着我们呢,下面就赶快去看一看吧。
10.5.1 使用前台Service
前面已经说过,从Android 8.0 系统开始,只有当应用保持在前台可见状态的情况下,Service 才能保证稳定运行,一旦应用进入后台之后,Service 随时都有可能被系统回收。而如果你希望Service 能够一直保持运行状态,就可以考虑使用前台Service。前台Service 和普通Service 最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于我们在应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态兰清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。
那么我们就来看一下如何才能创建一个前台服务吧,其实并不复杂,修改MyService中的代码,如下所示:
可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。没错!这就是我们在第9章中学习的创建通知的方法,并且我还将small_icon 和 large_icon 这两张图从NotificationTest 项目中复制过来。只不过这次在构建出Notification对象后并没有使用NotificationManager 来将通知显示出来,而是调用了startForeground()方法。
这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法的第一个参数;
第二个参数则是构建出的Notification对象。调用startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来。
另外,从Android9.0系统开始,使用前台Service 必须在Androidmanifest.xml 文件进行权限声明才行,如下所示:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台服务的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容。
现在即使你退出应用程序,MyService 也会一直处于运行状态,而且不用担心会被系统回收。当然,MyService所对应的通知也会一直显示在状态栏上面。如果用户不希望我们的程序一直运行,也可以选择手动杀掉应用,这样MyService 就会跟着一起停止运行了。
前台服务的用法就这么简单,只要你在第9章中将通知的用法掌握好了,学习本节的知识一定会特别轻松。
10.5.2 使用IntentService
话说回来,在本章一开始的时候我们就已经知道,Service 中的代码都是默认运行在主线程当中的,如果直接在Service 里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service 的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此,一个比较标准的Service 就可以写成如下形式:
class MyService : Service() {
...
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MyService","onStartCommand executed")
thread {
// 处理具体的逻辑
}
return super.onStartCommand(intent, flags, startId)
}
}
但是,这种Service 一旦启动,就会一直处于运行状态,必须调用stopService() 或 stopSelf() 方法,或者被系统回收,Service 才会停止。所以,如果想要实现一个让Service 在执行完毕后自动停止的功能,就可以这样写:
class MyService : Service() {
...
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MyService","onStartCommand executed")
thread {
// 处理具体的逻辑
stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}
}
虽说这种写法并不复杂,但是总会有一些程序员忘记开启线程,或者忘记调用stopSelf() 方法。为了可以简单地创建一个异步的、会自动停止的Service ,Android专门提供了一个IntentService类,这个类就很好地解决了前面所提到的两种尴尬,下面我们就来看一下它的用法。
新建一个MyIntentService类继承自IntentService,代码如下所示:
class MyIntentService : IntentService("MyIntentService") {
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")
}
}
这里首先要求必须先调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用。然后要在子类中去实现onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些耗时逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHandleIntent() 方法中打印了当前线程的名。另外根据 IntentService 的特性,这个Service在运行结束后应该是会自动停止的,所以我们又重写了onDestroy() 方法,在这里也打印了一行日志,以证实Service是不是停止掉了。
接下来修改activity_main.xml中的代码,加入一个用于启动MyIntentService这个服务的按钮,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/startService"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service"
/>
<Button
android:id="@+id/stopService"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service"
/>
<Button
android:id="@+id/bindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service"
/>
<Button
android:id="@+id/unbindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service"
/>
<Button
android:id="@+id/startIntentServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start IntentService"
/>
</LinearLayout>
然后修改MainActivity中的代码,如下所示:
startIntentServiceBtn.setOnClickListener {
Log.d("MainActivity","Thread is ${Thread.currentThread().name}")
val intent = Intent(this,MyIntentService::class.java)
startService(intent)
}
可以看到,我们在Start IntentService 按钮的点击事件里面去启动MyIntentService这个服务,并在这里打印了一下主线程名,稍后用于和IntentService进行比对。你会发现,其实IntentService的用法和普通的Service没什么两样。
最后不要忘记,服务都是需要在AndroidManifest.xml里注册的,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
<service
android:name=".MyIntentService"
android:exported="true"
android:enabled="true"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
当然你也可以使用Android Studio提供的快捷方式来创建IntentService,不过由于这样会自动生成一些我们用不到的代码,因此这里我采用了手动创建的方式。
现在重新运行一下程序,点击Start IntentService按钮后,观察logcat中的打印日志:
可以看到,不仅MyIntentService 和 MainActivity 所在的线程名不一样,而且onDestroy() 方法也得到了执行,,说明MyIntentService在运行完毕后确实自动停止了。集开启线程和自动停止于一身,IntentService还是博得了不少程序员的喜爱。