Service是Android四大组件之一,一般用来处理后台任务。Service运行于宿主进程的主线程中,所以如果处理耗时的后台任务,需要启动子线程来执行,也可以考虑使用IntentService。IntentService会启动一个工作线程,依次处理到达的工作请求。
Service运行于后台,就需要与前台的Client(一般是Activity)通信,本文整理了几种常见的通信方式供大家参考,如有遗漏,请补充,错误请指正,谢谢。
一、通过PendingIntent通信
按照官方文档的说法,PendingIntent是“A description of an Intent and target action to perform with it”。我们能够通过静态方法,getActivity(Context, int, Intent, int), getActivities(Context, int, Intent[], int), getBroadcast(Context, int, Intent, int), 和 getService(Context, int, Intent, int)创建它。
联系到本文的内容,我们选择使用,getBroadcast创建PendingIntent,如下:
mPendingIntent = PendingIntent.getBroadcast(this, requestCode, new Intent(ACTION), flags);
然后在启动Service时,把PendingIntent通过Intent传给Service。
Intent intent = new Intent(this, CustomService.class);
intent.putExtra("PendingIntent", mPendingIntent);
startService(intent);
因为PendingIntent继承了Parcelable接口,可以当作参数直接传递。
Service中获取传递的PendingIntent对象。
pendingIntent = intent.getParcelableExtra("PendingIntent”);
就可以使用它和Client通信了。
Client端注册一个BroadcastReceiver, 处理PendingIntent的ACTION和requestCode。
Service端使用PendingIntent的send()方法就可以和Client通信了。
此处执行send()和执行sendBroadcast()效果相同。
下面也正是我想说的第二种方法。
二、通过BroadcastReceiver通信
PendingIntent方法中主要也是借助于BroadcastReceiver进行通信的,此处我们要说单纯使用BroadcastReceiver方式进行通信的方法。这里我们使用LocalBroadcastManager注册,发送,解除注册广播。使用LocalBroadcastManager发送广播比发送全局广播更加安全和高效。
首先,在Activity中通过
mBroadcastManager = LocalBroadcastManager.getInstance(this);
获取LocalBroadcastManager的实例。
然后使用
mBroadcastManager.registerReceiver(mLocalReceiver, new IntentFilter(ACTION));
mBroadcastManager.unregisterReceiver(mLocalReceiver);
注册和解除注册广播。
最后,在Service中同样使用
mLocalManager = LocalBroadcastManager.getInstance(getApplicationContext());
获取其实例,并在需要发送广播的地方,使用
mLocalManager.sendBroadcast(intent);
进行广播的发送,这样就可以进行简单的通信了。
三、通过IBinder通信
如果Service和Activity运行在同一个进程,可以考虑使用IBinder通信。
具体的步骤如下:
首先,在Service中创建Binder的实例,并提供公有的方法供Client调用。
public class MyBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
getService方法返回当前Service实例,Client获得该实例之后可以调用Service的公有方法。
在onBind回调方法中把Binder对象的实例返回给Client。
@Override
public IBinder onBind(Intent intent) {
return binder;
}
Activity在bindService的时候,传入ServiceConnection对象。
bindService(intent, connection, BIND_AUTO_CREATE);
ServiceConnection用来监听Service的状态变化,运行于主线程。
它提供了两个回调的方法,onServiceConnected和onServiceDisconnected,分别在连接建立和断开的时候回调。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ((MyService.MyBinder)service).getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
mService = null;
}
};
在onServiceConnected的时候我们获得Service的时候,这样就可以调用公有方法了。记得在通信完成之后unbindService,这样在所有连接断开之后,Service也就停止运行了。
四、通过Messager通信
Messenger是一个指向Handler的引用,通过它可以给Handler发送消息。我们可以基于它实现跨进程的通信(IPC)。
如果你要求跨进程的多任务处理,那你可以选择AIDL实现,但是如果你不要求多线程处理,那Messenger完全可以满足你的需求,接下来我们具体看一下使用Messenger的通信的步骤。
首先,在Service中实现一个Handler,用于接收来自客户端的回调。
mHandlerThread = new HandlerThread("Messenger-Service");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
clients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
clients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
Message message = Message.obtain(null, MSG_SET_VALUE);
message.arg1 = msg.arg1;
for (Messenger messenger : clients) {
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
super.handleMessage(msg);
}
}
};
这里我们使用了HandlerThread的方式,这里所有回调都运行在一个子线程中。如果你的Service不处理耗时操作,可以单纯的实现Handler即可。
其次,把我们的Messenger指向Handler。
mMessenger = new Messenger(mHandler);
在onBind中返回Messenger的IBinder。
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
然后,客户端从返回的IBinder中实例化Messenger,并通过该Messenger向Service的Handler发送消息。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
try {
Message message = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT);
message.replyTo = mMessenger;
mService.send(message);
message = Message.obtain(null, MessengerService.MSG_SET_VALUE);
message.arg1 = this.hashCode();
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Service收到Messenger发送的消息后,在Handler的handleMessage中进行处理。
从上面的代码中我们可以看到,我们把客服端的Messenger作为replyTo发送给了Service,这样Service就可以使用该Messenger向对应客户端发送消息了。
下面附上Client端的全部代码,仅供参考。
public class MainActivity extends AppCompatActivity {
private Messenger mService;
private Messenger mMessenger;
private boolean isBound;
private TextView textView;
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
textView.setText(Integer.toString(msg.arg1));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
try {
Message message = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT);
message.replyTo = mMessenger;
mService.send(message);
message = Message.obtain(null, MessengerService.MSG_SET_VALUE);
message.arg1 = this.hashCode();
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMessenger = new Messenger(new IncomingHandler());
textView = (TextView) findViewById(android.R.id.text1);
}
@Override
protected void onStart() {
super.onStart();
doBindService();
}
@Override
protected void onStop() {
super.onStop();
doUnBindService();
}
private void doBindService() {
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, connection, Service.BIND_AUTO_CREATE);
isBound = true;
}
private void doUnBindService() {
if (isBound) {
try {
if (mService != null) {
Message message = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT);
message.replyTo = mMessenger;
mService.send(message);
}
} catch (RemoteException e) {
e.printStackTrace();
}
unbindService(connection);
isBound = false;
}
}
}
五、通过AIDL通信
最后,我们说一下通过AIDL进行通信的方式。如果你不需要进行IPC通信,那么可以使用我们三中讲到的实现Binder的方式,如果你需要IPC,但不需要处理多线程的请求,那我们四中讲的Messenger方式应该也可以满足要求。如果你即需要IPC,又需要多线程处理,那AIDL可以满足你的要求。
下面我们说一下实现AIDL的步骤,以Android Studio为例。
首先,创建.aidl文件。在AS中,右键单击项目目录,选择新建AIDL文件。新建完成的AIDL文件在与Java同级目录的aidl目录中。这里需要注意:
新建的AIDL文件需要和你的Service的包名相同,否则不能自动生成接口的文件。
我们这里的文件内容如下:
// MyRemoteService.aidl
package com.lkk.demo.aidl;
// Declare any non-default types here with import statements
interface MyRemoteService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
int getPid();
}
然后,实现通信所需的Service,在Service中实现自动生成的接口。
private final MyRemoteService.Stub stub = new MyRemoteService.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int getPid() throws RemoteException {
return Process.myPid();
}
};
在getPid方法中返回本进程的进程ID。
因为MyRemoteService.Stub继承了Binder,所以可以在onBind方法中直接返回stub.
public IBinder onBind(Intent intent) {
return stub;
}
现在可以在remote进程中连接本Service,并调动Stub暴露的方法了。
我们在上边的service的Manifest中声明可以启动它的action
然后新建一个项目,按照上边的方法新建一个.aidl文件,AIDL的文件名和包名必须和上边的完全一致,否则会出错。把上边AIDL文件的内容原封不动的拷贝到此文件中。
下面就可以Bind Service了。
Intent intent = new Intent("com.lkk.demo.AIDL_SERVICE");
intent.setPackage("lkk.download.com.aidldemo");
bindService(intent, connection, BIND_AUTO_CREATE);
这里通过隐式的方式启动Service,必须设置包名,这是5.0之后的新要求。
此处的connection为:
private MyRemoteService service;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
service = MyRemoteService.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
service = null;
}
};
这里我们必须使用MyRemoteService.Stub.asInterface把返回值转换成我们想要的接口,这样就可以调用它暴露的方法了。
textView.setText(String.valueOf(service.getPid()));
以上内容是我现在所能了解到的关于Service和Client通信的方法了,这里一一记录下来,以备后续查看,如果能帮到大家那是最好不过了。
内容上可能存在着错误和不足,希望大家不吝赐教,如果有其他没有涉及到的更好的方法可以comment一下,谢谢。