天天看點

android跨程序通信(IPC):使用AIDL

aidl (android interface definition language) 是一種idl 語言,用于生成可以在android裝置上兩個程序之間進行程序間通信(interprocess communication, ipc)的代碼。如果在一個程序中(例如activity)要調用另一個程序中(例如service)對象的操作,就可以使用aidl生成可序列化的參數。

aidl ipc機制是面向接口的,像com或corba一樣,但是更加輕量級。它是使用代理類在用戶端和實作端傳遞資料。

官方文檔特别提醒我們何時使用aidl是必要的:隻有你允許用戶端從不同的應用程式為了程序間的通信而去通路你的service,以及想在你的service處理多線程。如果不需要進行不同應用程式間的并發通信(ipc),you should create your interface by implementing a binder;或者你想進行ipc,但不需要處理多線程的,則implement your interface using a messenger。無論如何,在使用aidl前,必須要了解如何綁定service——bindservice。如何綁定服務,請參考我的另一篇文章:http://blog.csdn.net/singwhatiwanna/article/details/9058143。這裡先假設你已經了解如何使用bindservice。

建立一個android工程,用來充當跨程序通信的服務端。

建立一個包名用來存放aidl檔案,比如com.ryg.sayhi.aidl,在裡面建立imyservice.aidl檔案,如果需要通路自定義對象,還需要建立對象的aidl檔案,這裡我們由于使用了自定義對象student,是以,還需要建立student.aidl和student.java。注意,這三個檔案,需要都放在com.ryg.sayhi.aidl包裡。下面描述如何寫這三個檔案。

imyservice.aidl代碼如下:

[java] view

plaincopy

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

package com.ryg.sayhi.aidl;  

import com.ryg.sayhi.aidl.student;  

interface imyservice {  

    list<student> getstudent();  

    void addstudent(in student student);  

}  

說明:

aidl中支援的參數類型為:基本類型(int,long,char,boolean等),string,charsequence,list,map,其他類型必須使用import導入,即使它們可能在同一個包裡,比如上面的student,盡管它和imyservice在同一個包中,但是還是需要顯示的import進來。

另外,接口中的參數除了aidl支援的類型,其他類型必須辨別其方向:到底是輸入還是輸出抑或兩者兼之,用in,out或者inout來表示,上面的代碼我們用in标記,因為它是輸入型參數。

在gen下面可以看到,eclipse為我們自動生成了一個代理類

public static abstract class stub extends android.os.binder implements com.ryg.sayhi.aidl.imyservice

可見這個stub類就是一個普通的binder,隻不過它實作了我們定義的aidl接口。它還有一個靜态方法

public static com.ryg.sayhi.aidl.imyservice asinterface(android.os.ibinder obj)

這個方法很有用,通過它,我們就可以在用戶端中得到imyservice的執行個體,進而通過執行個體來調用其方法。

student.aidl代碼如下:

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

parcelable student;  

說明:這裡parcelable是個類型,首字母是小寫的,和parcelable接口不是一個東西,要注意。

student.java代碼如下:

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

import java.util.locale;  

import android.os.parcel;  

import android.os.parcelable;  

public final class student implements parcelable {  

    public static final int sex_male = 1;  

    public static final int sex_female = 2;  

    public int sno;  

    public string name;  

    public int sex;  

    public int age;  

    public student() {  

    }  

    public static final parcelable.creator<student> creator = new  

            parcelable.creator<student>() {  

                public student createfromparcel(parcel in) {  

                    return new student(in);  

                }  

                public student[] newarray(int size) {  

                    return new student[size];  

            };  

    private student(parcel in) {  

        readfromparcel(in);  

    @override  

    public int describecontents() {  

        return 0;  

    public void writetoparcel(parcel dest, int flags) {  

        dest.writeint(sno);  

        dest.writestring(name);  

        dest.writeint(sex);  

        dest.writeint(age);  

    public void readfromparcel(parcel in) {  

        sno = in.readint();  

        name = in.readstring();  

        sex = in.readint();  

        age = in.readint();  

    public string tostring() {  

        return string.format(locale.english, "student[ %d, %s, %d, %d ]", sno, name, sex, age);  

說明:通過aidl傳輸非基本類型的對象,被傳輸的對象需要序列化,序列化功能java有提供,但是android sdk提供了更輕量級更友善的方法,即實作parcelable接口,關于android的序列化,我會在以後寫文章介紹。這裡隻要簡單了解一下就行,大意是要實作如下函數

readfromparcel : 從parcel中讀取對象

writetoparcel :将對象寫入parcel

describecontents:傳回0即可

parcelable.creator<student> creator:這個照着上面的代碼抄就可以

需要注意的是,readfromparcel和writetoparcel操作資料成員的順序要一緻

建立一個service,比如名為myservice.java,代碼如下:

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

/** 

 * @author scott 

 */  

public class myservice extends service  

{  

    private final static string tag = "myservice";  

    private static final string package_sayhi = "com.example.test";  

    private notificationmanager mnotificationmanager;  

    private boolean mcanrun = true;  

    private list<student> mstudents = new arraylist<student>();  

    //這裡實作了aidl中的抽象函數  

    private final imyservice.stub mbinder = new imyservice.stub() {  

        @override  

        public list<student> getstudent() throws remoteexception {  

            synchronized (mstudents) {  

                return mstudents;  

            }  

        }  

        public void addstudent(student student) throws remoteexception {  

                if (!mstudents.contains(student)) {  

                    mstudents.add(student);  

        //在這裡可以做權限認證,return false意味着用戶端的調用就會失敗,比如下面,隻允許包名為com.example.test的用戶端通過,  

        //其他apk将無法完成調用過程  

        public boolean ontransact(int code, parcel data, parcel reply, int flags)  

                throws remoteexception {  

            string packagename = null;  

            string[] packages = myservice.this.getpackagemanager().  

                    getpackagesforuid(getcallinguid());  

            if (packages != null && packages.length > 0) {  

                packagename = packages[0];  

            log.d(tag, "ontransact: " + packagename);  

            if (!package_sayhi.equals(packagename)) {  

                return false;  

            return super.ontransact(code, data, reply, flags);  

    };  

    public void oncreate()  

    {  

        thread thr = new thread(null, new serviceworker(), "backgroundservice");  

        thr.start();  

        synchronized (mstudents) {  

            for (int i = 1; i < 6; i++) {  

                student student = new student();  

                student.name = "student#" + i;  

                student.age = i * 5;  

                mstudents.add(student);  

        mnotificationmanager = (notificationmanager) getsystemservice(notification_service);  

        super.oncreate();  

    public ibinder onbind(intent intent)  

        log.d(tag, string.format("on bind,intent = %s", intent.tostring()));  

        displaynotificationmessage("服務已啟動");  

        return mbinder;  

    public int onstartcommand(intent intent, int flags, int startid)  

        return super.onstartcommand(intent, flags, startid);  

    public void ondestroy()  

        mcanrun = false;  

        super.ondestroy();  

    private void displaynotificationmessage(string message)  

        notification notification = new notification(r.drawable.icon, message,  

                system.currenttimemillis());  

        notification.flags = notification.flag_auto_cancel;  

        notification.defaults |= notification.default_all;  

        pendingintent contentintent = pendingintent.getactivity(this, 0,  

                new intent(this, myactivity.class), 0);  

        notification.setlatesteventinfo(this, "我的通知", message,  

                contentintent);  

        mnotificationmanager.notify(r.id.app_notification_id + 1, notification);  

    class serviceworker implements runnable  

        long counter = 0;  

        public void run()  

        {  

            // do background processing here.....  

            while (mcanrun)  

            {  

                log.d("scott", "" + counter);  

                counter++;  

                try  

                {  

                    thread.sleep(2000);  

                } catch (interruptedexception e)  

                    e.printstacktrace();  

說明:為了表示service的确在活着,我通過打log的方式,每2s列印一次計數。上述代碼的關鍵在于onbind函數,當用戶端bind上來的時候,将imyservice.stub mbinder傳回給用戶端,這個mbinder是aidl的存根,其實作了之前定義的aidl接口中的抽象函數。

問題:問題來了,有可能你的service隻想讓某個特定的apk使用,而不是所有apk都能使用,這個時候,你需要重寫stub中的ontransact方法,根據調用者的uid來獲得其資訊,然後做權限認證,如果傳回true,則調用成功,否則調用會失敗。對于其他apk,你隻要在ontransact中傳回false就可以讓其無法調用imyservice中的方法,這樣就可以解決這個問題了。

[html] view

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

<service  

    android:name="com.ryg.sayhi.myservice"  

    android:process=":remote"  

    android:exported="true" >  

    <intent-filter>  

        <category android:name="android.intent.category.default" />  

        <action android:name="com.ryg.sayhi.myservice" />  

    </intent-filter>  

</service>  

說明:上述的 <action android:name="com.ryg.sayhi.myservice" />是為了能讓其他apk隐式bindservice,通過隐式調用的方式來起activity或者service,需要把category設為default,這是因為,隐式調用的時候,intent中的category預設會被設定為default。

建立一個用戶端工程,将服務端工程中的com.ryg.sayhi.aidl包整個拷貝到用戶端工程的src下,這個時候,用戶端com.ryg.sayhi.aidl包是和服務端工程完全一樣的。如果用戶端工程中不采用服務端的包名,用戶端将無法正常工作,比如你把用戶端中com.ryg.sayhi.aidl改一下名字,你運作程式的時候将會crash,也就是說,用戶端存放aidl檔案的包必須和服務端一樣。用戶端bindservice的代碼就比較簡單了,如下:

android跨程式通信(IPC):使用AIDL
android跨程式通信(IPC):使用AIDL

import com.ryg.sayhi.aidl.imyservice;  

public class mainactivity extends activity implements onclicklistener {  

    private static final string action_bind_service = "com.ryg.sayhi.myservice";  

    private imyservice mimyservice;  

    private serviceconnection mserviceconnection = new serviceconnection()  

        public void onservicedisconnected(componentname name)  

            mimyservice = null;  

        public void onserviceconnected(componentname name, ibinder service)  

            //通過服務端onbind方法傳回的binder對象得到imyservice的執行個體,得到執行個體就可以調用它的方法了  

            mimyservice = imyservice.stub.asinterface(service);  

            try {  

                student student = mimyservice.getstudent().get(0);  

                showdialog(student.tostring());  

            } catch (remoteexception e) {  

                e.printstacktrace();  

    protected void oncreate(bundle savedinstancestate) {  

        super.oncreate(savedinstancestate);  

        setcontentview(r.layout.activity_main);  

        button button1 = (button) findviewbyid(r.id.button1);  

        button1.setonclicklistener(new onclicklistener() {  

    public void onclick(view view) {  

        if (view.getid() == r.id.button1) {  

            intent intentservice = new intent(action_bind_service);  

            intentservice.setflags(intent.flag_activity_new_task);  

            mainactivity.this.bindservice(intentservice, mserviceconnection, bind_auto_create);  

    public void showdialog(string message)  

        new alertdialog.builder(mainactivity.this)  

                .settitle("scott")  

                .setmessage(message)  

                .setpositivebutton("确定", null)  

                .show();  

    protected void ondestroy() {  

        if (mimyservice != null) {  

            unbindservice(mserviceconnection);  

可以看到,當點選按鈕1的時候,用戶端bindservice到服務端apk,并且調用服務端的接口mimyservice.getstudent()來擷取學生清單,并且把傳回清單中第一個學生的資訊顯示出來,這就是整個ipc過程,需要注意的是:學生清單是另一個apk中的資料,通過aidl,我們才得到的。另外,如果你在ontransact中傳回false,将會發現,擷取的學生清單是空的,這意味着方法調用失敗了,也就是實作了權限認證。

android跨程式通信(IPC):使用AIDL

繼續閱讀