定义
Service作为Android四大组件之一,在我们实际开发中是扮演着非常重要的角色。很多Android初学者都会有疑惑,究竟Service和Thread有什么区别,使用场景是什么?其实很简单,Thread大家应该不陌生,就是用于执行一些耗时操作时使用主线程不阻塞,或者用于当前界面中后台执行一个任务。Thread是依赖于Activity的,即依赖于APP的界面。而Service是一种计算型组件,一般用于后台执行一系列长期运行的计算任务,但它并不依赖于界面,即使Activity被销毁,或者APP直接退出了,只要进程还在,Service就可以继续运行。注意:Service是可以运行在主线程中,所以在Service里如果执行耗时操作也会出现ANR的。但在Service中创建一个子线程,然后在这里去处理耗时逻辑就没问题了。Service的使用场景可以联想到一些APP的下载功能,即使APP退出了,但下载仍然继续;又如天气APP,即使退出了程序,但还会有通知提醒天气情况,等。
Service有两种状态:启动状态和绑定状态。启动状态时的Service不需要与外界交互,绑定状态的Service可以方便的和Service组件进行通信。
启动状态
启动Service的方法和启动Activity很类似,都需要借助Intent来实现,来看看实例:
新建一个MyService.java并使它继承Service和重写onCreate()、onStartcommand、onBind()和onDestory():
public class MyService extends Service {
@Override
publicvoid onCreate() {
super.onCreate();
}
@Override
publicint onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
publicvoid onDestroy() {
super.onDestroy();
}
@Override
publicIBinder onBind(Intent intent) {
return null;
}
}
项目中的每一个Service都必须在AndroidManifest.xml中注册才能使用,接着编辑AndroidManifest.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="project.test.com.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".MyService"/>
</application>
</manifest>
activity_main.xml布局文件中,我们加入两个按钮,分别用于启动Service和停止Service:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/ btn_stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
</LinearLayout>
最后,在Activity中加入上面两个按钮的点击事件方法即可:
// 启动Service
private void startMyService() {
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
}
//停止Service
private void stopMyService() {
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
}
这样,一个简单的Service实例就完成了,虽然这个实例中没有做什么有用的事件,当程序运行后,点击“Start Service”按钮,会启动Service,这时Service中的onCreate()和onStartCommand()方法会被依次调用,如果再点击一次“Start Service”按钮时,onCrate()方法不再被调用,但onStartCommand()方法会再被调用一次。当我们点击“Stop Service”按钮时,就将Service停止,会调用onDestory()方法。
上面提到修改AndroidManifest.xml中声明Service,如果想让其他应用程序也可以使用我们的服务,则可以添加一个带Action的IntentFilter(IntentFilter的相关知识在上一节Activity的IntentFilter匹配规则有介绍过):
<service android:name=".MyService">
<intent-filter>
<action android:name="com.test.project.MyService" />
</intent-filter>
</service>
这样,如果外部要启动这个Service,可以这样:
Intent startIntent = new Intent(“com.test.project.MyService”);
startService(startIntent);
绑定状态
Service的启动状态就是在Activity中执行startService后,交由Service自己去做自己的事,然而跟Activity并没有什么关联。而Service的绑定状态,主要用于Service和其他组件的交互。需要注意的是,Service的这两种状态是可以共存的,即Service即可以处于启动状态也可以同时处于绑定状态。通过bindService方法即可以绑定的方式启动Service。
修改MyService.java,在onBind()方法中返回一个继承自Binder类内部类,类中添加一个要做事情的方法:
public class MyService extends Service {
private MyBinder mBinder = new MyBinder();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
returnsuper.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
class MyBinder extends Binder {
public void startDo() {
//TODO…
}
}
}
修改activity_main.xml,添加两个按钮,分别是Bind Service和Unbind Service:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" />
<Button
android:id="@+id/btn_stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" />
<Button
android:id="@+id/btn_bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service" />
<Button
android:id="@+id/btn_unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service" />
</LinearLayout>
最后,修改Activity,创建一个ServiceConnection的内部类,并重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。接着在刚新加入的两个按钮的点击事件实现bindService和unbindService,其中bindService第二个参数是ServiceConnection的实例,第三个参数是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。关键代码如下:
private MyService.MyBinder myBinder;
private ServiceConnection connection = newServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service){
myBinder = (MyService.MyBinder) service;
myBinder.startDo();
}
};
// 绑定Service
private void bindMyService() {
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent,connection, BIND_AUTO_CREATE);
}
// 解绑Service
private void unbindMyService() {
unbindService(connection);
}
注意:
前面说过,启动状态和绑定状态是可以共存的,如果既点击了“Start Service”按钮,又点击了“Bind Service”按钮后,想要销毁Service,必须要点击“Unbind Service”铵钮和“Stop Service”铵钮,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。
前台Service
我们开头提到Service是默默地在后台进行一系列长期运行的计算任务,其实Service也分前台Service和后台Service。一般地当系统出现内存不足情况时,就有可能会回收掉系统优化级相对较低的正在后台运行的Service。如果你希望Service可以一值保持不被系统杀死回收,那么就可以考虑使用前台Service。前台Service就是那些被用户允许的在状态栏有一个正在运行的图标显示。比如我们常见到的音乐APP,当你退出了程序后,音乐还仍继续播放,而且在下拉状态栏后还能见到一个常驻的像通知消息一样的可操作音乐播放的拦。
如何创建前台Service
创建前台Service还是比较简单的,只要在Service的onCreate或onStartCommand回调中,使用startForeground()方法替换我们平时正常NotificationManager.notify()方法来显示一个通知消息即可,示例:
@Override
public void onCreate() {
super.onCreate();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this,
MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews remoteViews = new RemoteViews(getPackageName(),
R.layout.layout_notification);
remoteViews.setTextViewText(R.id.btn1, "这是一个按钮");
Notification notification = new Notification();
notification.icon = R.mipmap.ic_launcher;
notification.tickerText = "hello world";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
startForeground(1, notification);
}
远程Service
我们开头也提到,Service是可以运行在主线程中,所以在Service里如果执行耗时操作也会出现ANR的。所以在使用Service处理耗时逻辑时要创建一个子线程来处理。其实,还可以有另外的方法,那就是使用远程Service,将一个普通的Service转换成远程Service其实非常简单,只需要把它放在另外的进程中去,即在注册Service的时候将它的android:process属性指定成:xxx就可以了。需要注意的是,虽然这样就可以不用使用子线程来处理事情,但因为Service已经跟他调用的组件已经不在同一个进程里,所以像我们上面示例中,如果执行“StartService”则照常无样,但如果执行“Bind Service”则会出现可怕的崩溃。这是因为Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。那么远程Service就不能绑定了吗?当然不是,只是会比较麻烦点,使用AIDL来进行跨进程通信就可以解决。关于AIDL使用,我们后面再述。
粘性服务、非粘性服务
关于粘性服务,这里需要提到 Service 的 onStartCommand 返回值
- START_STICKY:“粘性的”。使用这个返回值时,我们启动的服务跟应用程序"粘"在一起,如果在执行完 onStartCommand 后,服务被异常 kill 掉,系统会自动重启该服务。当再次启动服务时,传入的 Intent 参数将为null;
- START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完 onStartCommand 后,服务被异常kill掉,系统不会自动重启该服务。
- START_REDELIVER_INTENT:“粘性的”且重传 Intent。使用这个返回值时,如果在执行完 onStartCommand 后,服务被异常 kill 掉,系统会自动重启该服务,并将 Intent 的值传入。