Service的存在主要是为了让Application来bindService,然后通过binder来调用Service中的服务函数,然而在某些情况下,Service也需要回调Application,让其完成某些事情。
举一个简单的例子,我们有一个下载服务(Service),有一个客户端(Client),通常情况下的流程是这样的:
在服务端完成下载之后,是需要通过onDownloadComplete这个回调函数,让客户端在下载完成,做一些其他的事情,例如播放下载文件之类的。
因为是跨进程的通信,所以我们要实现两个application,一个Application实现Service,另一个Application实现Client。重点关注跨进程的Service与Client之间的交互。
我们的交互是这样的:
Client实现一个回调函数集合:IAidlCallback,里面有1个函数:
1. onDownloadCompleted();
Service实现一个服务函数集合:IAidlService, 里面有3个函数:
1. registerCallback()
2. download();
3. unregisterCallback()
我们建立一个Application,名字叫MyDownloadService,其中只有一个Service(作为服务提供端):
<service
android:permission="com.android.permission.MY_SERVICE"
android:name=".MyService"
android:exported="true" >
<intent-filter>
<action android:name="com.android.myservice.MyService" />
intent-filter>
service>
然后我们建立一个Application,名字叫MyDownloadClient,其中只有一个Activity(作为服务调用端)
<uses-permission android:name="com.android.permission.MY_SERVICE" />
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
注意在MyDownloadClient的manifest中记得使用uses-permisson声明Service所需要的permission。
需要在MyDownloadService和MyDownloadClient这两个应用中都创建同样的接口文件:
1. MyDownloadService中:
./MyDownloadService/src/com/android/myservice/aidl/IAidlService.aidl
./MyDownloadService/src/com/android/myservice/aidl/IAidlCallback.aidl
2. MyDownloadClient中:
./MyDownloadClient/src/com/android/myservice/aidl/IAidlService.aidl
./MyDownloadClient/src/com/android/myservice/aidl/IAidlCallback.aidl
接口文件必须是一样的,在这种情况下,跨进程的调用才能正确寻址。
IAidlService.aidl:
package com.android.myservice.aidl;
import com.android.myservice.aidl.IAidlCallback;
interface IAidlService {
void registerCallback(IAidlCallback cb);
void download();
void unregisterCallback();
}
IAidlCallback.aidl:
package com.android.myservice.aidl;
interface IAidlCallback {
void onDownloadCompleted();
}
因为AIDL文件必须是相同的,跨进程函数调用才能正确地寻址
–> 所以AIDL文件的包名都是一致的
“package com.android.myservice.aidl”;
–> 所以AIDL文件的src相对路径也必须是一致的:
/src/com/android/myservice/aidl/
在Android.mk中,我们的LOCAL_SRC_FILES除了加上java文件之外,需要特别地加上aidl文件。这样,mm编译过程中,编译器会通过aidl文件生成java文件:
IAidlService.aidl -> IAidlService.java
IAidlCallback.aidl -> IAidlCallback.java
以MyService为例,看Android.mk的写法:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_MODULE_TAGS := optional
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += \
src/com/android/myservice/aidl/IAidlService.aidl \
src/com/android/myservice/aidl/IAidlCallback.aidl
LOCAL_PACKAGE_NAME := MyService
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
编译后就会生成MyService.apk.
MyDownloadService中的MyService.java:
package com.android.myservice;
import com.android.myservice.aidl.IAidlCallback;
import com.android.myservice.aidl.IAidlService;
public class MyService extends Service {
private static final String TAG = "MyService";
private IAidlCallback mCallback = null;
private IAidlService.Stub mBinder = new IAidlService.Stub() {
@Override
public void registerCallback(IAidlCallback cb) throws RemoteException {
mCallback = cb;
}
@Override
public void download() throws RemoteException {
doDownload();
}
@Override
public void unregisterCallback() throws RemoteException {
mCallback = null;
}
};
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate");
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy");
super.onDestroy();
}
private void doDownload() {
Log.d(TAG, "doDownload");
Log.d(TAG, "Download Start");
Log.d(TAG, "Downloading");
Lod.d(TAG, "Download Complete")
mCallback.onDownloadCompleted();
}
在这里,Service实现了IAidlService.Stub中的每一个接口函数,包括将Client提供的回调函数注册到Service当中,从Service当中注销,以及比较特殊的download接口函数,这个函数会使用回调函数onDownloadCompleted让Client完成下载完成后要做的事情。
MyDownloadClient中的MainActivity.java:
package com.android.myClient;
import com.android.myservice.aidl.IAidlCallback;
import com.android.myservice.aidl.IAidlService;
public class MainActivity extends Activity {
private static final String TAG = "MyClient";
private int mDice = 0;
private IAidlService mService = null;
private IAidlCallback mCallback = new IAidlCallback.Stub() {
@Override
public void onDownloadComplete() throws RemoteException {
// TODO Auto-generated method stub
Log.d(TAG, "onDownloadComplete");
doPrecedureAfterDownload();
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
mService = IAidlService.Stub.asInterface(service);
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
Log.d(TAG, "onServiceDisconnected");
}
};
private void bindService() {
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.myservice","com.android.myservice.MyService"));
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
this.setContentView(R.layout.activity_main);
bindService();
Button downloadButton = (Button) this.findViewById(R.id.download);
downloadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
mService.download();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
protected void onDestroy() {
unbindService(mServiceConnection);
}
private void doPrecedureAfterDownload() {
Log.i(TAG, "doPrecedureAfterDownload");
}
从Client的MainActivity文件中,需要做的事情主要是实现IAidlCallback.Stub中的接口函数(回调函数集),在ServiceConnection bind成功之后,获得Service对象mService,以后可以使用该mService来调用Service的接口函数,然后注册回调函数到Service中,最后注册button的click listener,在onClick的时候,通过mService调用download接口,然后等download完成之后,Service端变会通过mCallback回调onDownloadCompleted函数,让Client端执行doPrecedureAfterDownload()。
总结起来,需要注意的是以下6点:
1. AIDL接口必须是一样的,这就意味着AIDL的包名一定是一样的,本例中的”com.android.myservice.aidl”, 也就意味着,在各个使用该AIDL接口的project里面的src路径都必须是一样的/src/com/android/myservice/aidl/;
2. AIDL接口文件在eclipse下面设置好之后,通过build project,会在gen下面生成各自的IAidlService.java 和 IAidlCallback.java文件;
3. Service实现的是Service的接口, 本例的IAidlService.aidl,在MyService中,实现IAidlService.Stub mBinder, = new IAidlService.Stub() { ** } 然后在onBind的时候,将这个mBinder返回;
4. Client实现的是Callback的接口,本里的IAidlCallback.aidl, 在MainActivity中,实现IAidlCallback mCallback = new IAidlCallback.Stub() { ** }, 在将该callback注册到Service之后,Service便可以通过该callback引用来调用Client这边提供的IAidlCallback的回调函数;
5. Client实现ServiceConnection, 这个ServiceConnection是用来bindService用的,需要实现onServiceConnected, onServiceDisconnected,这两个钩子函数是让Client可以在bindService/unbindService之后,系统成功连接/断开Service的时候,调用这些钩子函数,并且在钩子函数中做一些初始化(init)或者去初始化(deinit)的操作,最重要的是要通过:mService = IAidlService.Stub.asInterface(service); 语句获得IAidlService的引用,使得以后可以直接使用mService调用Service接口,本例中在获得mService自后,马上使用了mService.registerCallback(mCallback); 注册回调函数,这些都属于初始化(init)的操作。这种模板方法模式的应用,与click listener的onClick函数有异曲同工之妙;