同进程使用Service(一)
比如,你做了一个音乐播放 app,里面有一个 MusicService 负责后台播放音乐,对外提供 play(),pause() 的接口
你在一个 Activity 里想调用这个 Service 的 play(),怎么搞?
必须在这个 Activity 里拿到刚才启动的 MusicService 的实例,但这是不可能的。Service 实例是由安卓OS维护的,你拿不到。(启动 Service 可不是 new MusicService() new 出来的,而是调用安卓系统的 startService()/bindService(),这些函数不给你返回被启动的Service实例)
我们可以想个办法把 Service 的实例返回去:首先要了解 Service 和客户端的回调机制,这里涉及一个重要的接口类:IBinder。
一、Service机制
服务端都继承自 Service 类,Service 类有个 onBind() 方法。当有客户端来使用 Service 的时候,安卓OS会调用该 Service 的 onBinde()。不过因为 onBind() 的返回类型并不是 Service,所以我们不能直接把 Service 的实例返回去。
并且,就算你返回去了也没用,因为客户端不是 onBind() 的调用者,安卓OS才是。客户端调的是 bindService()。
客户端调用 bindService() 去绑定 Service,以便能够使用 Service 的功能。函数原型是:boolean bindService(Intent intent, ServiceConnection conn, int flags)
,
第一个参数 intent 指定要绑定的是哪个 Service,我们这里就是: new Intent(this, MusicService.class);
第三个参数 flags 一般用 BIND_AUTO_CREATE
即可。表示如果被绑定的Service还没启动,则安卓OS会自动启动它。
重点是第二个参数 conn。
它是 ServiceConnection 类型的
当 Service 绑定好了之后(Service 的 onBind() 返回了),安卓OS会回调 conn.onServiceConnected(ComponentName name, IBinder service)
安卓OS会在这个回调里,传来一个 IBinder 类型的对象(第二个参数),它就是 Service 端的 onBind() 方法 return 的东西。
但这里还是没有拿到 Service 的实例,因为 onBind() 返回的并不是 Service 实例,而是一个 IBinder。
不过既然知道了服务端和客户端之间交互的流程,就可以想办法让客户端拿到 Service 实例了。
二、让客户端拿到Service的实例
Service 端通过重写 onBind() 方法,让它返回一个可以拿到 Service 实例的 IBinder。比如给这个 IBinder 添加一个方法,getService().
public class MusicService extends Service {
public class MyBinder extends Binder {
MusicService getService() {
return MusicService.this ;
}
}
@Override
public IBinder onBind (Intent intent) {
return new MyBinder();
}
public void play () {
Log.d("testing" , "playing" );
}
}
客户端准备一个 ServiceConnection,用来绑定 Service 并接收 Service onBind() 返回的那个 IBinder。
重写这个 ServiceConnection 的 onServiceConnected() 方法,把安卓OS回传过来的 IBinder 用起来,得到 Service 的实例。
import com.zsl.musicserver.MusicService.MyBinder;
public class MainActivity extends Activity {
MusicService mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected (ComponentName className,IBinder service) {
MyBinder binder = (MyBinder) service;
mService = binder.getService();
}
@Override
public void onServiceDisconnected (ComponentName name) {
}
};
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this , MusicService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
public void onButtonClick (View v) {
mService.play();
}
}
以上程序写在同一个工程里可以正确运行,点击 play 按钮后 logcat 输出 playing。
三、小结
Service 重写 onBind() 方法,返回一个 IBinder 的子类。
客户端用 conn 的 onServiceConnected() 方法接收到这个返回的对象。
这样,客户端和 Service 的联系就建立起来了。
四、问题
首先,上面这种做法很难做到跨进程,先不说原理,就从代码层面看也不行
上例中客户端需要拿到Service的实例,这就要求客户端和服务端必须在一个工程里,如果是两个 app 根本没法搞:
MusicService mService;
理论上服务端的 MusicService 类对客户端应该是透明的,客户端只需要使用 Service 的功能就可以了。
所以我们稍微更进一步,把 MusicService 抽象出一个接口:MusicInterface。服务端和客户端都有接口类 MusicInterface,但分别实现,客户端是给服务端发消息,服务端真的干活。
同进程使用Service(二)
这一节跟上一节没有本质区别,只是在上节基础上做了一点包装。
让 Service 的 onBind() 函数返回的不光是一个 IBinder,还是一个 MusicInterface。
MusicInterface 是个接口,统一封装了 MusicService 的业务函数。客户端通过该接口使用业务函数。
package com.zsl.musicservice;
public interface MusicInterface {
void play();
void pause();
}
服务端
onBind() 返回的东西是 extends Binder implements MusicInterface
。
下面例子中,MusicService 的内部类 MusicServiceProxy,就是用来返回给客户端的 proxy。
package com.zsl.musicservice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MusicService extends Service {
class MusicServiceProxy extends Binder implements MusicInterface {
@Override
public void play () {
MusicService.this .play();
}
@Override
public void pause () {
MusicService.this .pause();
}
}
public void play () {
Log.d("zsl" , "Playing" );
}