Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> android使用AIDL實現跨進程通訊(IPC)

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

編輯:關於android開發

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


前言:在還沒有做任何一件事情之前,千萬不要覺得這件事情很難,因為還沒有開始做內心就已經對這件事情產生了恐懼,這將會阻止你的進步,也許當你動手開始做了這件事後發現其實並不是很難。

一、 AIDL概述

含義:AIDL(Android Interface Definition Language),是android接口定義語言,這種語言定義了一個客戶端和服務器通訊接口的一個標准、規范。

為什麼要有AIDL?

?我們都知道android中的四大組件Activity,Broadcast,Content Provider,Service,前面我們應該都接觸過除了Service的其他三個組件的進程間通訊的例子,比如:一個應用可以通過顯示意圖啟動另外一個Activity,一個應用發送一個廣播,然後被其他應用所接受,一個應用對外提供一個Content Provider,然後其他應用使用ContentResolver獲取它提供的數據。這些都是進程間通訊的例子(通常情況下每個應用運行在一個獨立的Linux進程中),那麼Service這個組件也同樣也可以實現垮進程通訊,這就是本篇文章要介紹的AIDL服務。AIDL的出現除了讓Service實現垮進程提供服務外,還有一個重要的原因就是:

Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL

這是google官方文檔對AIDL的一個提示,意思是說“只有當你允許來自不同應用通過你的service實現進程通訊,並且需要在你的service中處理多線程的情況下才用AIDL,如果你不需要實現不同應用間即時的進程通訊,那麼,你應該創建一個接口實現Binder,或者,如果你想實現進程通訊但是不需要處理多線程,那麼用一個Messenger實現你的接口,但是,無論如何,你都得先理解本地的服務在你實現AIDL之前“

通過上面的這句話我們就非常清楚了AIDL的作用就是讓兩個不同的應用間通過Service進行通信(進程通訊IPC),並且遠程的Service可以處理多線程。簡單來講就是,兩個應用,一個應用對外提供一個遠程Service,其他的應用可以並發地訪問這個Service,即:C/S模式。

二、 AIDL簡單示例

實現步驟:

創建一個AIDL文件(擴展名為.aidl); 服務端實現該AIDL文件生成的Java接口(系統會自動生成對應的Java接口); 暴露一個接口給客戶端(通過建立一個Service,在onBind()方法中返回一個Stub類的實例); 客戶端連接綁定該遠程服務。

按照這個步驟,咋們通過代碼理解AIDL的使用(這裡基於Android Studio這個工具,eclipse裡面也類似(更簡單))。

1. 創建一個AIDL文件

?首先我們需要新建一個Module,這個Module是一個服務端應用,可以直接通過as提供的直接新建AIDL文件,這樣它會自動生成aidl文件夾和默認的一個AIDL文件(生成的AIDL默認的包名是應用的包名),這個改包名有點麻煩,因為客戶端和服務端都必須要有相同的AIDL文件(包名也必須相同),所以,下面我們通過全手動的方式建立AIDL文件,在main文件夾下(project視圖)下面建立一個aidl文件夾,然後在這個文件夾下面建一個包名,包名可以隨意,但是客戶端和服務器端的AIDL文件包名必須一致,接下來在這個包名下面建一個文件,這裡叫IRemoteService.aidl

aidl文件有它自己的語法(aidl:接口定義語言):

每個aidl文件只能定義一個接口(單一接口); 默認Java中的基本數據類型都支持,如:int, long, char, boolean; 默認支持String和CharSequence; 默認支持List(可以選擇加泛型,需要引入List所在的包),但是List中存儲的數據也只能是Java基本數據類型,而且另外一邊(客戶端或服務端)接受的是ArrayList類型; 默認支持Map(可以選擇加泛型,但泛型只能是基本數據類型或String和CharSequence,需要引入Map所在的包),但同樣,Map中存儲的數據也只能是Java基本數據類型,而且另外一邊(客戶端或服務端)接受的是HashMap類型; 所有aidl文件中的注釋都會出現在自動生成的IBinder接口java文件中(除了導入包語句之前的注釋); aidl接口只支持方法,不支持變量。

aidl接口文件寫法和Java接口的寫法非常類似,就是不要Public修飾符,所以,我們這樣寫:

IRemoteService.aidl

// IRemoteService.aidl
package com.lt.aidl;
// Declare any non-default types here with import statements
interface IRemoteService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** get name by id */
    String getName(int id);
}

Ctrl + S保存後我們可以看到編譯器會自動給我們生成(如果沒自動生成,可以重新構建一下這個項目)一個對應的IRemoteService.java文件,這個文件在Packages視圖下可以看到:

這裡寫圖片描述

這個自動生成的java文件我們不需要看懂,所以,不管它,接下來我們進行第二步和第三步,也就是服務端實現該AIDL文件生成的Java接口(系統會自動生成對應的Java接口)。

2(3). 服務端實現該AIDL文件生成的Java接口

?打開這個AIDL生成的Java接口,我們可以發現,裡面有一個內部靜態抽象類Stub,這個類繼承了Binder並實現了這個接口,所以,我們可以直接使用這個Stub類完成遠程服務的搭建。

新建一個Sevice,在onBind方法中返回實現了AIDL Java接口的那個Binder類(new一個Stub類正好),這個類將作為這個Service代理類:

package com.lt.remoteservice.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.lt.aidl.IRemoteService;

/**
 * Created by lt on 2016/3/6.
 */
public class RemoteService extends Service{

    private String[] names = {"呂布","關羽","趙子龍","張飛"};

    /**
     * 返回一個RemoteService代理對象IBinder給客戶端使用
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);
        }

        @Override
        public int getPid() throws RemoteException {
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("RemoteService getPid ");
            return android.os.Process.myPid();
        }

        @Override
        public String getName(int id) throws RemoteException {
            return names[id];
        }
    };
}

注意:不要忘了在清單文件中注冊該Service,並且我們還需要提供一個包含action屬性的intent-filter(客戶端通常是通過隱式意圖來啟動該服務),這個action屬性值可以任意。

4. 客戶端綁定該服務

?新建另一個Module,同樣需要建立aidl文件,這個文件要和服務器端的aidl文件一模一樣(注意包名),這裡直接復制服務器端的aidl文件夾在這個Module中。

為了直觀展示通訊效果,我們做一個交互界面,activity_main.xml

<code class="language-xml hljs "><linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent">

    <edittext android:id="@+id/editText" android:hint="輸入查詢ID" android:layout_width="match_parent" android:layout_height="wrap_content">

    </edittext></linearlayout></code><button android:onclick="search" android:layout_width="wrap_content" android:text="查詢" android:layout_height="wrap_content"><code class="language-xml hljs ">

    <linearlayout android:layout_width="wrap_content" android:orientation="horizontal" android:layout_height="wrap_content">
        <textview android:text="結果:" android:layout_width="wrap_content" android:layout_height="wrap_content">

        <textview android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content">
    </textview></textview></linearlayout>
</code></button>

重點來了,客戶端綁定遠程服務,先建立一個服務連接

 private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 這裡的IBinder對象service是代理對象,所以必須調用下面的方法轉換成AIDL接口對象
            mRemoteService = IRemoteService.Stub.asInterface(service);
            int pid = 0;
            try {
                pid = mRemoteService.getPid();
                int currentPid = android.os.Process.myPid();
                System.out.println("currentPID: " + currentPid +"  remotePID: " + pid);
                mRemoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "有夢就要去追,加油!");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            System.out.println("bind success! " + mRemoteService.toString());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteService = null;
            System.out.println(mRemoteService.toString() +" disconnected! ");
        }
    };

注意:

連接的兩個方法,一個是服務連接成功後回調,一個是服務端開連接時回調; 連接成功後在onServiceConnected方法中返回了一個IBinder對象service,這個Service對象必須通過IRemoteService.Stub.asInterface(service);轉換成AIDL對象,這個對象將作為遠程服務的代理對象。

通過隱式意圖綁定遠程服務

// 連接綁定遠程服務
Intent intent = new Intent();
// action值為遠程服務的action,即上面我們在服務端應用清單文件的action
intent.setAction("lt.test.aidl");
intent.setPackage("com.lt.remoteservice");
isConnSuccess = bindService(intent, conn, Context.BIND_AUTO_CREATE);

注意:

android 5.0 中對service隱式啟動做了限制,必須通過設置action和package; package是指要啟動的那個服務所在的包名,這裡即服務器端的那個應用的包名; 綁定服務有可能會失敗(如客戶端和服務器端的AIDL文件不一致)。

界面交互,響應button事件:

public void search(View view){
    if(isConnSuccess){
        // 連接成功
        int id = Integer.valueOf(mEditText.getText().toString());
        try {
            String name = mRemoteService.getName(id);
            mTv_result.setText(name);
        }catch (RemoteException ex) {
            ex.printStackTrace();
        }
    }else{
        System.out.println("連接失敗!");
    }
}

客戶端activity完整代碼:

package com.lt.remoteclient;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.lt.aidl.IRemoteService;

public class MainActivity extends Activity {

    private IRemoteService mRemoteService;
    private TextView mTv_result;
    private EditText mEditText;
    private boolean isConnSuccess;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditText = (EditText) findViewById(R.id.editText);
        mTv_result = (TextView) findViewById(R.id.tv_result);

        // 連接綁定遠程服務
        Intent intent = new Intent();
        intent.setAction("lt.test.aidl");
        intent.setPackage("com.lt.remoteservice");
        isConnSuccess = bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    public void search(View view){
        if(isConnSuccess){
            // 連接成功
            int id = Integer.valueOf(mEditText.getText().toString());
            try {
                String name = mRemoteService.getName(id);
                mTv_result.setText(name);
            }catch (RemoteException ex) {
                ex.printStackTrace();
            }
        }else{
            System.out.println("連接失敗!");
        }
    }

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 這裡的IBinder對象service是代理對象,所以必須調用下面的方法轉換成AIDL接口對象
            mRemoteService = IRemoteService.Stub.asInterface(service);
            int pid = 0;
            try {
                pid = mRemoteService.getPid();
                int currentPid = android.os.Process.myPid();
                System.out.println("currentPID: " + currentPid +"  remotePID: " + pid);
                mRemoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "有夢就要去追,加油!");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            System.out.println("bind success! " + mRemoteService.toString());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteService = null;
            System.out.println(mRemoteService.toString() +" disconnected! ");
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

運行服務端應用,然後在運行客戶端應用,測試結果:

這裡寫圖片描述

觀察後台日志打印:

這裡寫圖片描述

可以看到,服務端和客戶端應用處在兩個不同的進程中,並且客戶端可以像傳遞基本類型的數據,同時客戶端也可以從遠程服務端應用取得數據(查詢結果),這就通過遠程Service完成了兩個應用間的通訊(垮進程通訊IPC)。但是,出現了一個問題,上面我們只是傳遞了基本類型的數據,而沒有傳遞對象這種非基本類型的數據,難道不能傳遞非基本類型數據嗎?答案是當然可以,下面我們實現垮進程傳遞一個對象。

三、 AIDL垮進程傳遞對象

?要通過AIDL垮進程傳遞非基本數據類型對象,那麼這個對象需要實現Parcelable接口( android系統內部會將這個對象類型分解成基本數據類型,然後在進程間傳遞)。

實現步驟(新建一個類):

實現Parcelable接口; 覆蓋writeTowriteToParcel(Parcel dest, int flags)Parcel方法,這個方法攜帶了當前對象的狀態和將它寫入Parcel中; 添加一個靜態成員變量CREATOR,這個變量是一個實現了Parcelable.Creator interface.接口的對象; 建立一個這個類對應的aidl文件,即文件名和類名一樣,擴展名為.aidl

1(2)(3). 新建一個類實現Parcelable接口

Person.java

package com.lt.aidl;

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

/**
 * Created by lt on 2016/3/8.
 */
public class Person implements Parcelable{

    private String name;
    private int age;

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

    private void readFromParcel(Parcel in) {
        this.name = in.readString();
        this.age = in.readInt();
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 在想要進行序列號傳遞的實體類內部一定要聲明該常量。常量名只能是CREATOR,類型也必須是
     * Parcelable.Creator  T:就是當前對象類型
     */
    public static final Creator CREATOR = new Creator() {

        /***
         * 根據序列化的Parcel對象,反序列化為原本的實體對象
         * 讀出順序要和writeToParcel的寫入順序相同
         */
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in.readString(),in.readInt());
        }

        /**
         * 創建一個要序列化的實體類的數組,數組中存儲的都設置為null
         */
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 將對象寫入到Parcel(序列化)
     * @param dest:就是對象即將寫入的目的對象
     * @param flags: 有關對象序列號的方式的標識
     * 這裡要注意,寫入的順序要和在createFromParcel方法中讀出的順序完全相同。例如這裡先寫入的為name,
     * 那麼在createFromParcel就要先讀name
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}

4. 建立該類對應的aidl文件

在Person類所在的包建立一個對應的aidl文件Person.aidl,文件內容:

package com.lt.aidl;

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

這裡現在的項目結構為:

這裡寫圖片描述

OK,將新建的Person.javaPerson.aidl往客戶端相同的地方復制一份,然後重新構建整個項目,如果直接這樣Android Studio在構建的時候會報導入包出錯(AIDL自定義類型導入失敗),這個問題折騰了我幾個小時(說多了都是淚…),解決辦法:

在項目grade文件中添加如下代碼將AIDL也作為源文件夾:

sourceSets {
    main {
        manifest.srcFile 'src/main/AndroidManifest.xml'
        java.srcDirs = ['src/main/java', 'src/main/aidl']
        resources.srcDirs = ['src/main/java', 'src/main/aidl']
        aidl.srcDirs = ['src/main/aidl']
        res.srcDirs = ['src/main/res']
        assets.srcDirs = ['src/main/assets']
    }
}

加入這段代碼後整個grade文件內容為:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.lt.remoteclient"
        minSdkVersion 11
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
}

然後重新構建一下項目,OK,完美解決(激動…),分別運行兩個應用後的測試結果:

這裡寫圖片描述源碼。

到這裡AIDL的使用也就介紹完了

總結:

?AIDL作為一個接口定義語言,它有它自己的語法,其語法和定義java接口類似,所以有人也通過建立一個接口,然後更改一下後綴名為aidl來創建AIDL接口文件,需要注意的是AIDL只有當不同的應用需要即使通訊,並且需要處理多線程的情況下才會使用,不然可以使用其他進程通信的方式來實現,還有,AIDL默認支持大部分數據類型,但如果要傳遞對象數據,那麼需要采取一些措施才行。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved