android跨進程通訊(IPC):使用AIDL

AIDL的做用

AIDL (Android Interface Definition Language) 是一種IDL 語言,用於生成能夠在Android設備上兩個進程之間進行進程間通訊(interprocess communication, IPC)的代碼。若是在一個進程中(例如Activity)要調用另外一個進程中(例如Service)對象的操做,就可使用AIDL生成可序列化的參數。
AIDL IPC機制是面向接口的,像COM或Corba同樣,可是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。

選擇AIDL的使用場合

官方文檔特別提醒咱們什麼時候使用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。

如何使用AIDL

1.先創建一個android工程,用做服務端

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

2.建立一個包名用來存放aidl文件

建立一個包名用來存放aidl文件,好比com.ryg.sayhi.aidl,在裏面新建IMyService.aidl文件,若是須要訪問自定義對象,還須要創建對象的aidl文件,這裏咱們因爲使用了自定義對象Student,因此,還須要建立Student.aidl和Student.java。注意,這三個文件,須要都放在com.ryg.sayhi.aidl包裏。下面描述如何寫這三個文件。

IMyService.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代碼以下:
package com.ryg.sayhi.aidl;

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

Student.java代碼以下:
package com.ryg.sayhi.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;
    }

    @Override
    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();
    }
    
    @Override
    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操做數據成員的順序要一致

3.建立服務端service

建立一個service,好比名爲MyService.java,代碼以下:
/**
 * @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;
            }
        }

        @Override
        public void addStudent(Student student) throws RemoteException {
            synchronized (mStudents) {
                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);
        }

    };

    @Override
    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();
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        Log.d(TAG, String.format("on bind,intent = %s", intent.toString()));
        displayNotificationMessage("服務已啓動");
        return mBinder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    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;

        @Override
        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中的方法,這樣就能夠解決這個問題了。

4. 在AndroidMenifest中聲明service

<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。html

5. 新建一個工程,充當客戶端

新建一個客戶端工程,將服務端工程中的com.ryg.sayhi.aidl包整個拷貝到客戶端工程的src下,這個時候,客戶端com.ryg.sayhi.aidl包是和服務端工程徹底同樣的。若是客戶端工程中不採用服務端的包名,客戶端將沒法正常工做,好比你把客戶端中com.ryg.sayhi.aidl改一下名字,你運行程序的時候將會crash,也就是說,客戶端存放aidl文件的包必須和服務端同樣。客戶端bindService的代碼就比較簡單了,以下:
import com.ryg.sayhi.aidl.IMyService;
import com.ryg.sayhi.aidl.Student;

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()
    {
        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mIMyService = null;
        }

        @Override
        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();
            }

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {

    @Override
    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();
    }
    
    @Override
    protected void onDestroy() {
        if (mIMyService != null) {
            unbindService(mServiceConnection);
        }
        super.onDestroy();
    }
}

運行效果

能夠看到,當點擊按鈕1的時候,客戶端bindService到服務端apk,而且調用服務端的接口mIMyService.getStudent()來獲取學生列表,而且把返回列表中第一個學生的信息顯示出來,這就是整個ipc過程,須要注意的是:學生列表是另外一個apk中的數據,經過aidl,咱們才獲得的。另外,若是你在onTransact中返回false,將會發現,獲取的學生列表是空的,這意味着方法調用失敗了,也就是實現了權限認證。
相關文章
相關標籤/搜索