在上一篇中我们学习了 Android Service 相关的许多基础但是重要的内容,基本涵盖大部分平日里的开发工作。今天我们继续学习一下稍微高级一点的用法,即:远程 Service 用法,使用远程 Service 可以实现安卓跨进程通信的功能。下面我们就开始学习一下吧。
什么是远程 Service
所谓的远程 Service 就是与调用者不在同一进程的 Service 即可叫做远程 Service。那是不是也有近程 Service?其实不叫近程 Service,专业叫法叫做本地 Service,就是调用者与 Service 在同一进程。
调用远程 Service 的桥梁 - AIDL 接口定义语言
由于远程服务与调用者不在同一进程,我们不能再像调用本地服务一样的方式去调用远程服务,
那么如何才能让 Activity 与一个远程 Service 建立关联呢?这就要使用 AIDL 来进行跨进程通信了(IPC)。
AIDL(Android Interface Definition Language)是 Android 接口定义语言的意思,它可以用于让某个 Service 与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个 Service 的功能。(本篇着重讲解与远程服务通信的过程,不涉及 AIDL 接口定义语言的细节讲解)
废话少说,下面我们通过一个简单的 Demo 来讲解一下与远程 Service 通信的过程。
首先我们新建一个远程服务 Demo 的工程,新建 RemoteService.aidl 文件,代码如下:
- package com.wl.remoteservice;
- interface RemoteService {
- int call(int a, int b);
- }
我们点击完保存之后,在 gen 目录下会生成一个与之对应的 java 文件,如下:
关于文件中内容我们暂时不细究,会在下篇的时候在研究一下。
然后编写 Service 类,新建 WLService 继承 Service,如下:
- public class WLService extends Service {
- private static final String TAG = "HEART";
- @Override
- public IBinder onBind(Intent intent) {
- Log.i(TAG, "onBind");
- return new MyBinder();
- }
- private int methodInRemoteService(int a,int b){
- return a+b;
- }
- private class MyBinder extends RemoteService.Stub{
- @Override
- public int call(int a,int b) {
- return methodInRemoteService(a,b);
- }
- }
- @Override
- public void onCreate() {
- Log.i(TAG, "onCreate");
- super.onCreate();
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i(TAG, "onUnbind");
- return super.onUnbind(intent);
- }
- @Override
- public void onDestroy() {
- Log.i(TAG, "onDestroy");
- super.onDestroy();
- }
- }
这里我们定义了一个内部类 MyBinder 继承自 RemoteService.Stub,那这个 stub 是什么玩意呢?点进去看一下,如下
1 public static abstract class Stub extends android.os.Binder implements com.wl.remoteservice.RemoteService
看定义我们就明白了,原来 MyBinder 就是 Binder 子类并且实现了 RemoteService 接口,所以在 onBind 的时候返回其实例也就容易理解了。
接下来,在清单文件注册 Service。这里我们需要在另一个应用程序启动 WLService,但是在另一个应用程序中去绑定 Service 的时候并没有 WLService 这个类,这时就必须使用到隐式 Intent 了。现在修改 AndroidManifest.xml 中的代码,给 WLService 加上一个 action,如下所示:
- <service android:name="com.wl.remoteservice.WLService">
- <intent-filter>
- <action android:name="com.wanglei.remoteservice" />
- </intent-filter>
- </service>
然后我们新建一个项目用以调用上面项目中的远程服务,新项目就叫做:调用远程服务。
首先需要将 RemoteService.aidl 拷贝过来,记住需要连同原包路径一同拷贝过来,如下:
接下来,修改布局文件:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- tools:context=".MainActivity" >
- <Button
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:onClick="bind"
- android:text="绑定服务" />
- <Button
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:onClick="unbind"
- android:text="解除绑定服务" />
- <Button
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:onClick="click"
- android:text="调用远程服务方法" />
- </LinearLayout>
然后编写 MainActivity 中代码,如下:
- public class MainActivity extends Activity {
- private static final String TAG = "HEART";
- private RemoteService mRemoteService;
- private MyConn conn;
- @Override protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- public void bind(View view) {
- Intent intent = new Intent();
- intent.setAction("com.wanglei.remoteservice");
- conn = new MyConn();
- bindService(intent, conn, BIND_AUTO_CREATE); //异步的操作
- }
- public void unbind(View view) {
- unbindService(conn);
- }
- public void click(View view) {
- if (null != mRemoteService) {
- try {
- int result = mRemoteService.call(10, 20);
- Log.i(TAG, "result = " + result);
- } catch(RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- private class MyConn implements ServiceConnection {@Override public void onServiceConnected(ComponentName name, IBinder service) {
- mRemoteService = RemoteService.Stub.asInterface(service);
- }
- @Override public void onServiceDisconnected(ComponentName name) {
- }
- }
- }
会发现和上篇讲的 bind 服务差别不大,这里我们需要在另一程序里面绑定服务所以需要用隐式意图开启。
这里我们需要注意一下,在安卓 5.0 出来以后,服务意图必须用显式调用,调用 bind 服务的时候会报如下警告
我们找一下源码,看看哪里报的这个警告,终于在 sdk/sources/android-21/android/app/ContextImpl.Java 目录下找到报警告的代码:
- private void validateServiceIntent(Intent service) {
- if (service.getComponent() == null && service.getPackage() == null) {
- if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
- IllegalArgumentException ex = new IllegalArgumentException(
- "Service Intent must be explicit: " + service);
- throw ex;
- } else {
- Log.w(TAG, "Implicit intents with startService are not safe: " + service
- + " " + Debug.getCallers(2, 3));
- }
- }
- }
好了找到报错的原因了,那怎么解决呢?观察源码发现最外层 if 判断的时候有个 service.getPackage() == null,我们只需要使其不为 null 不就可以了,这也是谷歌推荐的解决方法
我们将绑定服务的方法改为如下:
- public void bind(View view) {
- Intent intent = new Intent();
- intent.setAction("com.wanglei.remoteservice");
- intent.setPackage("com.wl.remoteservice"); //设置为Service所在包名
- conn = new MyConn();
- bindService(intent, conn, BIND_AUTO_CREATE); //异步的操作
- }
这样修改就好了,当然还有另外一种解决方法,将隐式意图变为显示意图,方法如下:
- public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
- // Retrieve all services that can match the given intent
- PackageManager pm = context.getPackageManager();
- List < ResolveInfo > resolveInfo = pm.queryIntentServices(implicitIntent, 0);
- // Make sure only one match was found
- if (resolveInfo == null || resolveInfo.size() != 1) {
- return null;
- }
- // Get component info and create ComponentName
- ResolveInfo serviceInfo = resolveInfo.get(0);
- String packageName = serviceInfo.serviceInfo.packageName;
- String className = serviceInfo.serviceInfo.name;
- ComponentName component = new ComponentName(packageName, className);
- // Create a new intent. Use the old one for extras and such reuse
- Intent explicitIntent = new Intent(implicitIntent);
- // Set the component to be explicit
- explicitIntent.setComponent(component);
- return explicitIntent;
- }
好了,通过以上两种方式就可以解决了,到此我们两个项目都已经编写完毕,先运行远程服务 demo 项目,在运行调用远程服务项目,点击绑定远程服务按钮,然后点击调用远程服务按钮,打印如下:
到此为止,我们成功调用了远程服务中的方法,跨进程通信成功实现,不过有些刚接触的同学可能还有些混乱,怎么觉得那么混乱,比平时用的组建多那么多步骤,那我们总结一下调用远程服务的步骤:
1. 编写 aidl 文件
2. 创建远程服务类,在服务的内部创建一个内部类提供一个方法,可以间接调用服务的方法
3. 实现服务的 onbind 方法,返回服务内部类实例
4. 拷贝 aidl 文件到另一工程联同包名
5. 在 activity 绑定服务。bindService();
6. 在服务成功绑定的时候 会执行一个方法 onServiceConnected 传递过来一个 IBinder 对象进行类型转换
7. 调用远程服务里面的方法。
好了,到这里关于服务的所有最重要的部分都已经讲解完了,通过 (上)(中) 两篇相信你对服务有了一个全面的认识。
来源: http://www.cnblogs.com/leipDao/p/7365570.html