虽然是Android四大组件之一,但从开发Android以来基本上没怎么用过,真是惭愧,今天有时间来倒腾倒腾Service,作为一个假Android,还是总结下好 😄,省得后面需要了又到处去找。
一说起Service,印象里只有在需要处理耗时的后台任务时才会想起它,实际开发中应该也是这种情况居多吧。好了,废话不多说。下面开始整理:
按照大的分类来说Service可以分为两种:Start Service
和 Bound Service
,他们都要继承自Service
基类。
1. Start Service
1.先上代码,首先,自定义Service:
public class MyService extends Service {
private static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "in onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.w(TAG, "in onStartCommand");
Log.w(TAG, "MyService: " + this);
String name = intent.getStringExtra("name");
Log.w(TAG, "name: " + name);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
自定义Service只需要实现基类中的onBind(...)
方法就可以了,这个方法的返回值只对Bound Service
有用,这里直接返回nll就可以了。
后面的onCreate()
,onStartCommand(...)
,onDestroy(...)
都是生命周期方法,一个service start的时候会先调用onCreate,再调用onStartCommand。
注意:同一个Service,只能用startService(...)启动一次,后面再启动也不会执行onCreate()
方法了,直接执行onStartCommand(...)
,同理,一个service不管被启动多少次,只需要调用一次stopService()(或者service自己调用stopSelf())
即可终止service。
然后看下onStartCommand(...)这个方法
onStartCommand(Intent intent, int flags, int startId)
intent
:包含外部Client启动service时传递的一些参数。
flags
: 默认是0,对应的常量是START_STICKY_COMPATIBILITY
startId
: 当多次start该Service的时候,startId会递增。
返回值:
START_NOT_STICKY
: 当service因系统内存不足被kill后,即时未来系统内存足够了也不会主动去重新创建该service,除非程序中再次调用startService()启动该service.
START_STICKY
: 和START_NOT_STICKY不同,被kill后,未来系统内存足够后会自动重建该service,重建后会调用onStartCommand(),但其中的intent参数会是null,pendingintent除外。
START_REDELIVER_INTENT
: 和START_STICKY唯一的不同是,重建后onstartCommand()方法中的intent为最后一次startService()中的intent.
最常用的就这三种,其他的类型需要的时候再去翻一下即可。
最后需要注意的是,如果用户强制kill了service,onDestroy()
是不会执行的。
2. AndroidManifest.xml中注册
必须在AndroidManifest.xml
中进行注册,如果不注册的话也不会崩溃异常,所以一定要注意:
<service
android:name=".MyService"
android:enabled="true"
android:exported="false"
android:icon="@drawable/ic_launcher_background"
android:isolatedProcess="true"
android:label="@string/app_name"
android:permission="@string/app_name"
android:process=":Myservice"/>
如果service要运行在单独的进程中,需要在process
中指明此进程名称
当此service需要对其他App开放时,exported
设为true即可。
3. 启动service
Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
startService(serviceIntent);
调用startService(...)即可启动service。这里,当Client可通过intent传递参数给service,当service需要传递参数给Client的时候,可以使用广播,Client只要注册该广播就可以了。
以上就是Start Service
的简单用法了。
2. Bound Service
Bound Service
与 Start Service
不同的是它的生命周期依赖于Client,当Client销毁的时候,它也就不存在了。另外,Bound Service
可以通过Binder
对象来和Client进行通信。
Bound Service
的使用过程是这样的:
1. 自定义Service继承自Service
,并实现onBind()
方法,返回具体的Binder
对象
2.Client通过ServiceConnection接口自定义一个ServiceConnection,实现onServerConnected(),并获取Service端Binder实例,通过binder实例进行service端其他公共方法的调用。
3.Client端通过bindService(...)方法将Service绑定到该Client上
4.Client在恰当的时候(比如在onDestory的时候),通过unBindService()解绑service.
另外,在Bound Service的具体使用过程中,根据onBind中返回的Binder对象的定义方式不同,又具体分为三种方式,每种方式又不同的特点,适应不同的场景。
2.1 继承Binder类
这是Bound Service最常用也是最简单的一种
局限:
Client和Service必须同属于一个进程,不能实现进程间通信(IPC)。否则会出现类似于 "android.os.BinderProxy cannot be cast to xxxx." 的错误
自定义Service:
public class BoundService1 extends Service {
private static final String TAG = "BoundService1";
/////// 自定义binder
private MyBinder mBinder = new MyBinder();
public class MyBinder extends Binder {
BoundService1 getService() {
return BoundService1.this;
}
}
/////// 自定义binder
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.w(TAG, "in onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.w(TAG, "in onUnbind" );
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.w(TAG, "in onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
}
}
AndroidManifest.xml中注册:
<service android:name=".BoundService1"/>
Client中使用:
public class BActivity extends AppCompatActivity {
private static final String TAG = "BActivity";
private Button bindServiceBtn;
private Button unBindServiceBtn;
private Button startIntentService;
private Intent serviceIntent;
private ServiceConnection sc = new MyServiceConnection();
private MyBinder mBinder;
private BoundService1 mBindService;
private boolean mBound;
//////////////自定义ServiceConnection
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w(TAG, "in MyServiceConnection onServiceConnected");
mBinder = (MyBinder) binder;
mBindService = mBinder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(TAG, "in MyServiceConnection onServiceDisconnected");
mBound = false;
}
}
//////////////自定义ServiceConnection
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
bindServiceBtn = findViewById(R.id.bind_service_btn);
unBindServiceBtn = findViewById(R.id.unBind_service_btn);
startIntentService = findViewById(R.id.start_intent_service_btn);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(BActivity.this, BoundService1.class);
bindService(intent, sc, Context.BIND_AUTO_CREATE);
}
});
unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
excuteUnBindService();
}
});
startIntentService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(BActivity.this, MyIntentService.class);
startService(intent);
}
});
}
private void excuteUnBindService() {
if (mBound) {
unbindService(sc);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
excuteUnBindService();
}
}
同样这里即使多次点击绑定,也是会绑定一次,BoundService1中的生命周期方法也只会执行一次绑定。
2.2 使用Messenger
通过Messenger方式返回Binder对象可以不用考虑Client-Service是否属于同一个进程的问题,并且,可以实现Client-Service之间的双向通信。
局限:
不支持严格意义上的多线程并发处理,实际上是以队列去处理
自定义Service:
public class BoundService2 extends Service {
private static final String TAG = "BoundService2";
public static final int MSG_FROM_CLIENT_TO_SERVICE = 1;
public static final int MSG_FROM_SERVICE_TO_CLIENT = 2;
private Messenger mClientMessenger;
private Messenger mServiceMessenger = new Messenger(new ServiceHandler());
class ServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Log.w(TAG, "thread name: " + Thread.currentThread().getName());
switch (msg.what) {
case MSG_FROM_CLIENT_TO_SERVICE:
Log.w(TAG, "receive msg from client");
mClientMessenger = msg.replyTo;
// service发送消息给client
Message toClientMsg = Message.obtain(null, MSG_FROM_SERVICE_TO_CLIENT);
try {
Log.w(TAG, "service begin send msg to client");
mClientMessenger.send(toClientMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.w(TAG, "in onBind");
return mServiceMessenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.w(TAG, "in onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.w(TAG, "in onDestroy");
super.onDestroy();
}
}
AndroidManifest.xml中注册:
<service android:name=".BoundService2"/>
Client中的使用:
public class CActivity extends AppCompatActivity {
private static final String TAG = "CActivity";
private Button bindServiceBtn;
private Button unBindServiceBtn;
private Button sendMsgToService;
private ServiceConnection sc = new MyServiceConnection();
private boolean mBound;
private Messenger mServiceMessenger;
private Handler mClientHandler = new MyClientHandler();
private Messenger mClientMessenger = new Messenger(mClientHandler);
/////////// Message handler && ServiceConnection
private class MyClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == BoundService2.MSG_FROM_SERVICE_TO_CLIENT) {
Log.w(TAG, "receive msg from service.");
}
}
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w(TAG, "in MyServiceConnection onServiceConnected.");
mServiceMessenger = new Messenger(binder);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(TAG, "in MyServiceConnection onServiceDisconnected.");
mBound = false;
}
}
/////////// Message handler && ServiceConnection
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
bindServiceBtn = findViewById(R.id.bind_service_btn);
unBindServiceBtn = findViewById(R.id.unBind_service_btn);
sendMsgToService = findViewById(R.id.send_msg_to_service);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(CActivity.this, BoundService2.class);
bindService(intent, sc, Context.BIND_AUTO_CREATE);
}
});
unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
excuteUnBindService();
}
});
sendMsgToService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sayHello();
}
});
}
public void sayHello() {
if (!mBound) {
return;
}
Message msg = Message.obtain(null, BoundService2.MSG_FROM_CLIENT_TO_SERVICE,0,0);
msg.replyTo = mClientMessenger;
try {
mServiceMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void excuteUnBindService() {
if (mBound) {
unbindService(sc);
mBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.w(TAG, "in onDestroy");
excuteUnBindService();
}
}
这里在ServiceConnection中的onServiceConnected(...)方法中根据service端传来的binder参数实例化一个Messenger对象,就可以使用它发送消息给Service了,
另外,在给service发送消息的时候,在消息中夹带一个Client端的Messenger实例:
Message msg = Message.obtain(null, BoundService2.MSG_FROM_CLIENT_TO_SERVICE,0,0);
msg.replyTo = mClientMessenger;
在service端获取到该Messenger实例,就可以在service端发送消息回Client端了。
3. AIDL (Android Interface Definition Language)
一般情况下,Messenger这种方式都是可以满足需求的,当然,通过自定义AIDL方式相对更加灵活。这种方式需要自己在项目中自定义xxx.aidl文件,然后系统会自动在gen目录下生成相应的接口类文件,接下来整个的流程与Messenger方式差别不大。
首先在服务端的src目录下按照下图中的样子创建
在aidl.aidl下新建一个MyAIDLService.aidl文件,文件内容如下:
// MyAIDLService.aidl
package aidl.aidl;
// Declare any non-default types here with import statements
interface MyAIDLService {
// 这个AIDL service 对外的接口只有一个,就是获取字符串
String getString();
}
注意这里的package后面的名称,与后面客户端的名称要一致,不然会出错。这个文件中我们只定义了一个对外的接口String getString()
。这时候Build一下,编译器会自动在Build->generated->source->aidl->debug->aidl->aidl下生成对应的接口文件
接下来,在服务端创建一个MyService:
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
class MyBinder extends MyAIDLService.Stub {
@Override
public String getString() throws RemoteException {
return "我是从服务器返回的";
}
}
}
这里只需要注意,onBind(...)中返回一个MyAIDLService.Stub的实例。
然后到AndroidManifest.xml中注册一下就可以了:
<service android:name=".MyService">
<intent-filter>
<action android:name="com.server.aidl.service.MyService"/>
</intent-filter>
</service>
下面,来实现客户端的调用:
首先,将服务端的MyAIDLService.aidl文件拷贝过来,并要保持包名和服务端一致,如下图所示:
接下来,直接调用就可以了:
public class MainActivity extends AppCompatActivity {
private Button bindServiceBtn, unBindServiceBtn;
private TextView tvData;
private MyAIDLService mMyAIDLService;
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
// 这里获取Service的方式有点儿区别
mMyAIDLService = MyAIDLService.Stub.asInterface(binder);
try {
String str = mMyAIDLService.getString();
tvData.setText(str);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMyAIDLService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindServiceBtn = findViewById(R.id.bind_service);
unBindServiceBtn = findViewById(R.id.unBind_service);
tvData = findViewById(R.id.tv_from_server);
bindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("com.server.aidl.service.MyService");
// 从Android 5.0开始,隐式Intent绑定服务的方式已经不能使用了,这里需要设置Service所在服务端的包名
intent.setPackage("com.server.aidl.service.serviceaidlserver");
bindService(intent, sc, BIND_AUTO_CREATE);
}
});
unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(sc);
}
});
}
}
这样,AIDL的方式也介绍完了。
最后, Bound Service
还有很重要的一点和Start Service
不同,就是解绑的时候一定要确认已经绑定的才能解绑,否则会崩溃,而Start Service不需要。
另外,四大组件中BroadcastReceiver不能作为Bound Service的Client,因为它的生命周期很短,而Bound Service的生命周期和Client的生命周期是绑定的。因此,在Android中,不允许BroadcastReceiver去bindService(...),当有此类需求时,可以用start service去代替。
需要补充的一点是:所有的Service都是运行在其所在进程的主线程上,如果Service和Client同属一个进程,则它就会运行在主线程,也就是UI线程上,此时要注意不要在Service中执行耗时任务,如果要执行耗时任务,就要在Service中新建线程,避免阻塞UI导致ANR;Service一旦创建,需要停止时都需要显示调用相应的方法(started service调用stopService()或Service自身调用stopSelf(),bound service需要unBindService()),否则started service将一直处于运行状态,对于bound service,则只有等到Client生命周期结束后才会销毁。
补充:
介绍一下IntentService
IntentService是系统提供给我们的一个继承自Service的特殊类,它的特殊性在于:
- 默认实现了onBind()方法,直接返回null
- 定义了抽象方法onHandleIntent(), 用户需要实现此方法,该方法用来处理耗时任务,并已经在新的线程中,用户无需再新建线程。
- 当耗时任务结束(onHandleIntent()方法执行完毕),此IntentService自动结束,无需人为调用方法使其结束
- IntentService处于任务时,也是按照队列的方式一个一个去处理,而非真正意义上的多线程并发方式.
简单定义:
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super(TAG);
}
public MyIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.w(TAG, "in onHandleIntent");
Log.w(TAG, "thread name: " + Thread.currentThread().getName());
}
}
注册:
<service android:name=".MyIntentService"/>
使用:
Intent intent = new Intent(BActivity.this, MyIntentService.class);
startService(intent);