Service概述
Service是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。
它可由其它应用组件(如Activity)启动,一旦被启动将在后台一直运行,即使启动服务的组件已销毁也不受影响。非常适合用于执行那些不需要和用户交互而且还需要长期运行的任务。
组件可以绑定到服务,从而进行交互。
适用场景:播放音乐;处理网络事务;在后台记录位置信息等。
Service分类
-
按运行地点可分为本地服务(LocalService)和远程服务(RemoteService)
本地服务:依附在主进程上而不是独立进程,所以不能进行耗时操作,可以在service里面创建一个Thread来执行耗时任务。主进程被kill后,服务便终止。
远程服务:即独立服务,对应进程名为所在包名加上指定的android:process字符串。因为是独立进程,所以Activity所在进程被kill时,该服务不受其它进程影响。可进行跨进程通信(IPC)。
-
按运行类型可分为前台服务(Foreground Service)和后台服务
前台服务:会在通知栏显示Ongoing的Notification。当服务被终止时,通知栏的Notification也会消失,这样对于用户有一定的通知作用。常见如音乐播放服务。
后台服务:默认的服务即为后台服务,不会显示在Notification。当服务被终止时,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步等。
-
按使用方式可分为:
使用startService:主要用于启动一个服务执行后台任务,不进行通信。对应的停止服务为stopService。
使用bindService:该方式启动的服务可进行通信,调用者挂掉后,服务也会跟着挂掉。对应的停止服务为unbindService。
使用startService和bindService:停止服务stopService和unbindService也必须使用。
Service的生命周期
onCreate(): 首次创建服务时调用。如果服务已运行,则不会调用。只会在第一次创建Service时调用,适合完成一些初始化工作。
onStartCommand(): 当另一个组件通过调用startService()请求启动服务时,系统将调用该方法。
onDestroy(): 当服务不再使用且将被销毁时,系统调用该方法。
onBind(): 当另一个组件通过调用bindService()与服务绑定时,系统将调用该方法。该方法是抽象方法,所以onBind()方法必须重写。
onUnbind(): 当另一个组件通过调用unbindService()与服务解绑时,系统将调用该方法。
onRebind(): 当startService和bindService都启用,之后调用unbindService解绑服务时,另一个组件想要与该服务绑定,并且之前onUnbind()方法返回true,该方法将被调用。
生命周期调用:
启动-->停止Service:startService() > onCreate() > onStartCommand() > onStartCommand() (多次启动)--> stopService() > onDestroy()
绑定-->解绑Service:bindService() > onCreate() > onBind() --> unbindService() > onUnbind() > onDestroy()
启动绑定-->解绑停止Service:startService() > onCreate() > onStartCommand() > bindService() > onBind() --> unbindService() > onUnbind() > stopService() > onDestroy()
解绑绑定Service:unbindService() > onUnbind(ture) > bindService() > onRebind()
代码实现
MyService.java代码如下:
public class MyService extends Service {
public static final String TAG = "Joy";
//通过binder实现调用者client与Service之间的通信
private MyBinder binder = new MyBinder();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"oncreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand startId = "+startId);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy");
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG,"onUnbind");
return super.onUnbind(intent);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return binder;
}
public class MyBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
//该方法提供给绑定者调用
public int getNumber() {
return new Random().nextInt();
}
}
- 使用startService启动service
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
startService(intent);
startService(intent);
stopService(intent);
startService(intent);
}
}
log如下:
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: oncreate
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 1
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 2
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onDestroy
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: oncreate
02-27 16:22:02.518 26058-26058/com.android.myapplication D/Joy: onStartCommand startId = 1
总结:可以看出多次调用startService不会重复执行onCreate回调,但每次都会执行onStartCommand回调,且只需要调用一次stopService方法便可以停止服务,无论之前被调用了几次启动服务方法。
- 使用bindService启动service
public class MainActivity extends AppCompatActivity {
public static final String TAG = "Joy";
private Button bind;
private Button unbind;
private MyService myService = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind = findViewById(R.id.bind);
unbind = findViewById(R.id.unbind);
bind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService(new Intent(MainActivity.this, MyService.class), conn, Context.BIND_AUTO_CREATE);
}
});
unbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(conn);
}
});
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyService.MyBinder myBinder = (MyService.MyBinder) service;
myService = myBinder.getService();
int num = myService.getNumber();
Log.d(TAG,"onServiceConnected");
Log.d(TAG,"num = "+num);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected");
}
};
}
启动界面如下:
点击BIND SERVICE按钮log如下:
02-27 16:43:47.166 16555-16555/com.android.myapplication D/Joy: oncreate
02-27 16:43:47.166 16555-16555/com.android.myapplication D/Joy: onBind
02-27 16:43:47.176 16555-16555/com.android.myapplication D/Joy: onServiceConnected
02-27 16:43:47.176 16555-16555/com.android.myapplication D/Joy: num = 445451174
点击UNBIND SERVICE按钮或者按返回键log如下:
02-27 16:44:37.036 16555-16555/com.android.myapplication D/Joy: onUnbind
02-27 16:44:37.036 16555-16555/com.android.myapplication D/Joy: onDestroy
总结:
- 绑定者可以调用服务里面的方法。
- 即使多次点击BIND SERVER按钮,也不会多次触发onBind()方法。
- Service中需要创建一个实现IBinder的内部类。在onBind()方法中需返回一个IBinder实例,不然onServiceConnected()方法不会调用。
- onServiceDisconnected() 在service正常关闭的情况下是不会被调用的,该方法只在Service被异常中止或者被杀死的时候调用。
-
Remote Service启动服务
因为远程服务是在另一个进程,它不受其它进程影响,可以为其它应用程序提供调用的接口--实际上就是进程间通信(IPC)。Android提供了AIDL工具来帮助进程间接口的建立。
前台服务
前台服务是那些被认为用户所认可的且在系统内存不足时不允许系统杀死的服务。前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下,表示只有在这个服务被终止或从前台主动移除通知后才能被解除。
使用原因:
在一般情况下,Service都是在后台运行,它们的系统优先级相对较低。当系统内存不足时,在后台运行的Service就有可能被回收。所以可以选择将需要保持运行的Service设置为前台服务。代码实现 :
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand startId = "+startId);
Notification.Builder builder = new Notification.Builder(this.getApplicationContext());
Intent intent1 = new Intent(this,MainActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this,0,intent1,0))
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.mipmap.ic_launcher))
.setContentTitle("title")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("content")
.setWhen(System.currentTimeMillis());
Notification notification = builder.build();
notification.defaults = Notification.DEFAULT_SOUND;
startForeground(110,notification);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
stopForeground(true);
Log.d(TAG,"onDestroy");
}
重写onStartCommand()方法,使用startForeground(int, Notification)方法来启动service。需要在onDestroy()方法中通过stopForeground(true)方法来取消前台运行状态。
IntentService
Service不能直接处理耗时的任务,一般可以使用Service+Thread来实现。而IntentService可以处理异步任务请求。
- IntentService是Service的子类,内部有一个工作线程来处理耗时操作。
- 当任务执行完成后,IntentService会自动停止,不需要手动结束。
- 如果多次启动IntentService,每个耗时操作会以工作队列的方式在IntentService的onHandleIntent() 方法中执行,依次执行,使用串行的方式,执行完自动结束。无需处理多线程问题。
相关代码:
MyIntentService.java代码:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
long endTime = System.currentTimeMillis() + 20 *1000;
Log.d("MyIntentService","onHandleIntent");
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
Log.d("MyIntentService","finished");
}
}
MainActivity.java利用startService启动:
final Intent intent = new Intent(MainActivity.this, MyIntentService.class);
startService(intent);
log如下:
02-27 15:36:18.061 23525-24995/com.android.myapplication D/MyIntentService: onHandleIntent
0227-04 15:36:38.061 23525-24995/com.android.myapplication D/MyIntentService: finished
因MyIntentService继承了IntentService,所以不需要实现onBind(), onStartCommand()方法,只需实现onHandleIntent()即可。
不建议通过bindService来启动IntentService,因为IntentService源码中onBind()默认返回null。
Service与Thread的区别
Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。Thread可以用来执行一些异步的操作。
Service:Service是android的一种机制,当它是Local Service,对应的Service是运行在主进程的 main 线程上的。如果是Remote Service,对应的Service是运行在独立进程的 main 线程上。
Thread的运行是独立于Activity的,当Activity被finish后,如果Thread没有被主动停止或run()方法没有执行完毕,Thread会一直执行。因此当Activity被finish后,Thread引用则不被持有,且没有办法在别的Activity对同一个Thread进行控制。
假如有需求的话,可以创建并启动一个Service,在Service里面创建、运行并控制该Thread,即可实现对Thread的控制。