Service详解_AIDL

为了便于对本文的理解,请先阅读上一篇文章《Service详解_ BoundService》。上篇文章讲解了怎样用BoundService实现应用内其它组件与Service的交互,本文将讲解怎样用AIDL实现进程间通信。

AIDL简介

AIDL(Android接口定义语言)和其它接口定义语言一样,它允许你定义Client和Service约定好的编程接口,以实现两者的进程间通信(IPC)。在安卓系统中,一个进程通常不能访问另一进程的内存,所以需要把他们的对象分解成操作系统可以理解的原语,这样才能把对象传递给对方进程。Android系统已经替我们实现了对象分解到原语、原语组装成对象,我们要做的就是用AIDL定义好接口并正确使用。

AIDL与Binder的使用场景

BoundService不管是Remote Bind还是Local Bind都需要提供一个供Client调用的接口,按照接口的创建方式分为:
1、 用AIDL创建:如果有来自不同进程的Client访问你的Service,并且需要在Service中处理多线程任务,那么需要使用AIDL创建接口。
2、 通过扩展Binder类实现:如果只有来自同一进程的Client访问你的Service,则应该通过Binder创建接口

Server端实现

AIDL接口是用java语法定义,以.aidl文件保存,并保存在Service和Client的src/目录下。编译包含.aidl文件的APP时, Android SDK tools会根据.aidl文件生成IBinder interface,并保存到工程的/gen目录。Service实现IBinder接口,Client绑定到Service后,就可以调用IBinder中的函数进行进程间通信。使用AIDL创建Bounded Service的步骤如下:

1、 创建.aidl文件

.aidl文件是用java编程语言构建的,每一个aidl文件只能定义一个接口,每个接口中可以申明一个或多个方法,这些方法可以携带参数,也可以有返回值。参数和返回值可以是任何类型,甚至是其他的AIDL生成的接口。

默认情况下,AIDL支持下列数据类型:
•java编程语言中的所有基本类型(如int,long,char, boolean等)
•String
•CharSequence
•List

你必须用import来引入上面不支持的数据类型,即使这个数据类型的定义与你的接口在同一个包下。定义Service接口,需要注意以下事项:
•方法可以有0个或多个参数,可以有返回值或为void。
•所有非原始参数都需要用in、out或inout标记数据方向。原始参数默认用in标记。
•所有包含在.aidl文件中的注释(除开import和package之前的注释)都会包含在生成的IBinder接口中。
•aidl接口只支持方法,你不能在AIDL中暴露static字段。
下面是一个.aidl文件的例子

// IRemoteService.aidl
package com.jenny.example.service;

// Declare any non-default types here with import statements

interface IRemoteService {
    int plus(int a, int b);
}

保存.aidl文件到工程的src/目录,然后build工程,SDK tools会在工程的/gen目录生成IBinder interface。生成的文件名与.aidl文件名相同,但是扩展名为.java,如IRemoteService.aidl编译生成IRemoteService.java。

2、实现接口

当你编译你的APP,Android SDK tools会根据.aidl文件生成.java接口文件。生成的接口包括一个名为Stub的子类,是它的父接口的一种抽象的实现(例如,YourInterface.Stub),并且申明了.aidl文件中的所有方法。继承生成的Binder接口(如YourInterface.Stub),并且实现从.aidl文件继承的方法。

下面的例子用匿名实例的方式实现了一个叫IRemoteService的接口,这个接口由IRemoteService.aidl文件定义。

private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
    @Override
    public int plus(int a, int b) throws RemoteException {
        return a + b;
    }
};

现在mBinder是Stub类的一个实例,它定义了Service的RPC接口。在下一步中,将这个实例暴露给Clients,这样Client就可以与Service交互。实现AIDL接口时,需要遵循下面的规则:
•对接口的调用可能在未知线程上执行,因此您需要从一开始就考虑多线程,并正确地将您的Service构建为线程安全的。
•默认情况下,RPC调用是同步的。如果您知道Service需要超过几毫秒才能完成一个请求,那么您不应该从Activity的主线程调用它,因为这样可能导致APP出现ANR,所以您应该从Client的一个单独线程去调用它。
•Service抛出的异常不会被返回给调用者。

3、把接口开放给Client

一旦您实现了Service的接口,就需要将其公开给Client,这样Client就可以绑定到Service了。开放接口需要继承Service,实现onBind()且返回一个Stub类的实例。下面例子中Service把IRemoteService接口开放给Client:

public class RemoteService extends Service {

    private static final String TAG = "RemoteService";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
        @Override
        public int plus(int a, int b) throws RemoteException {
            return a + b;
        }
    };
}

现在,当一个Client(如Activity)调用bindService()连接到该Service,Client的onServiceConnected()回调就会接收到Service的onBind()方法返回的mBinder实例,然后Client调用YourServiceInterface.Stub.asInterface(service)把IBinder映射为YourServiceInterface类型。

Client端实现

下面是Client调用远程AIDL接口的步骤:
1、 把.aidl文件拷贝到Client的src/目录下。
2、 申明一个IBinder接口的实例。

private IRemoteService mRemoteService;

3、 实现ServiceConnection。

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
        mRemoteService = IRemoteService.Stub.asInterface(iBinder);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        Toast.makeText(mContext, "onServiceDisconnected", Toast.LENGTH_SHORT).show();
    }
};

4、 调用Context.bindService(),并且传入ServiceConnection的实例。

Intent intent = new Intent();
intent.setAction("com.jenny.example.service.service.RemoteService");
intent.setPackage("com.jenny.example.service");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

5、 在onServiceConnected()中,收到IBinder实例。调用YourInterfaceName.Stub.asInterface((IBinder)service),把IBinder转换为YourInterface类型。

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
    mRemoteService = IRemoteService.Stub.asInterface(iBinder);
}

6、 调用接口定义的方法。

try {
    int result = mRemoteService.plus(1, 3);
    Log.i(TAG, "plus result="+result);
    Toast.makeText(mContext, "plus result="+result, Toast.LENGTH_SHORT).show();
}catch (RemoteException e) {
    Log.e(TAG, "RemoteException, "+e.getMessage());
}

7、 用Context.unbindService()断开连接。

unbindService(mConnection);

关于调用IPC Service的几点建议:
•对象是跨进程引用计数的。
•可以将匿名对象作为方法参数发送。

通过IPC传递对象

您可以通过IPC接口把一个类从一个进程传递到另一个进程。然而,这个类必须支持Parcelable接口,因为它允许Android系统将对象分解成可以跨越进程的原语。创建一个支持Parcelable协议的类的步骤如下:
1、 让你的类实现Parcelable接口。
2、 实现writeToParcel方法,把当前对象的状态写入Parcel。
3、 在你的类中添加一个叫CREATOR的static字段,并实现Parcelable.Creator接口。
4、 最后,创建一个aidl文件声明您的Parcelable类(如下Rect.aidl)。

如下是Rect.aidl文件:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

如下是实现Parcelable类的Rect.java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

要注意从其他进程读取数据的安全限制。在此例子中,Rect从Parcel读取4个值,但是需要确保这些值在可接受的范围值内。有关如何使应用程序免受恶意软件攻击的更多信息,请参见 Security and Permissions

AIDL接口的线程安全

调用AIDL接口是直接调用的函数,不能确定接口的实现是在哪个线程中进行的,Client可能同时发起多个调用,所以接口的实现必须要是线程安全的。如RemoteService实现了一个plus函数供Client调用:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
    @Override
    public int plus(int a, int b) throws RemoteException {
        Log.i(TAG, "threadId="+Thread.currentThread().getId()+", threadName="+Thread.currentThread().getName());
        return a + b;
    }
};

Client为一个Activity,点击界面中的plus按钮,调用RemoteService的函数:

public void onClick(View view) {
    switch (view.getId()) {
        case R.id.plus_btn:
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Log.i(TAG, "threadId="+Thread.currentThread().getId()+", threadName="+Thread.currentThread().getName());
                        int result = mRemoteService.plus(1, 3);
Log.i(TAG, "plus result="+result);
                    }catch (RemoteException e) {
                        Log.e(TAG, "RemoteException, "+e.getMessage());
                    }
                }
            });
            thread.start();
            break;
    }
}

1、 如果Client和Service在同一进程中,那么接口的调用与实现在同一个线程中。例如, BindRemoteServiceActivity是Client,和RemoteService都运行在Service的进程中,点击BindRemoteServiceActivity中的plus按钮打印出如下日志:
这里写图片描述
2、 如果Client和Service不在同一进程中,那么接口的调用在Client进程中,接口的实现运行在Service的进程中,且在系统维护的线程池创建的线程中。例如,ClientMainActivity是Client,和RemoteService运行在不同进程中,点击ClientMainActivity中的plus按钮打印出如下日志:
这里写图片描述

示例代码

本文档所有示例代码下载路径:http://download.csdn.net/download/jennyliliyang/10155100

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值