Android 進程間通信AIDL(一)

AIDL是什么

AIDL,即Android Interface Definition Language(Android接口定義語音),提到AIDL,不得不先引入Android系統中的Binder,Binder是Android跨進程通信的一種方式,眾所周知,Android系統是基于linux內核,系統會為每一個應用分配一個單獨的虛擬機,每個應用擁有自己獨立的進程,當然,一個應用也可以有多個進程,如果我們的應用需要實現進程間通信,Android并沒有采用linux已有的通信方式,如Socket、管道等,而是采用一種了新的通信方式Binder,Binder在平時應用層開發可能用到的不多,但是如果查看Android系統源碼就會發現,Android系統中很多地方用到了Binder,它是ServiceManager連接各種Manager(ActivityManager,WindowManager等)與其相對應的ManagerService的橋梁,對于應用層來說,通過Binder,客戶端與服務端可以進行跨進程通信,客戶端可以訪問服務端提供的方法或數據,AIDL正是為了定義客戶端與服務端進程間通信時相互都認可的接口,當我們創建AIDL接口時,Android SDK會為我們自動生成對應的binder類,通過這個binder可以實現進程間通信。

如何使用AIDL

AIDL基本使用有三個步驟:

  1. 創建一個Service和AIDL文件
  2. 實現接口
  3. 向客戶端公開接口

需要注意的是,AIDL支持下列數據類型:

  • 基本數據類型(int long double char 等等)
  • CharSequence
  • String
  • List
  • Map
  • Parcelable

這里選擇用自定義對象來作為數據傳遞,首先我們新建一個Person類并且實現Parcelable接口。需要注意的是,如果需要通過ADIL進行復雜對象傳遞的話,該對象必須實現Parcelable接口

package com.zx.aidl.demo;
import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {


    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.id);
        dest.writeString(this.name);
    }

    public Person() {
    }

    protected Person(Parcel in) {
        this.id = in.readInt();
        this.name = in.readString();
    }

    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
        @Override public Person createFromParcel(Parcel source) {
            return new Person(source);
        }

        @Override public Person[] newArray(int size) {
            return new Person[size];
        }
    };
}

接下來分別創建Person.aidl、IRemoteService.aidl和Service
Person.aidl

package com.zx.aidl.demo;
parcelable Person;

IRemoteService.aidl

package com.zx.aidl.demo;
import  com.zx.aidl.demo.Person;

interface IRemoteService {
void addPerson(in Person person);
List<Person> getPersons();
}

IRemoteService 這個接口中定義了倆個方法,需要注意的是AIDL接口中,只能定義方法,并不支持定義靜態常量,當我們在AIDL文件中引用到其他自定義類型時,需要創建一個與該類同名的AIDL文件,如上面的Person.aidl ,并在文件中聲明該類為parcelable,這里的parcelable首字母是小寫,同時需要在引用該類的AIDL接口中import它的包名,接下來創建服務端的Service

package com.zx.aidl.demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoteService extends Service {

    private static final  String TAG = RemoteService.class.getSimpleName();

    private CopyOnWriteArrayList<Person> persons = new CopyOnWriteArrayList<>();

    @Override public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate() executed");
    }

    @Override public int onStartCommand(Intent intent,  int flags, int startId) {
        Log.i(TAG, "onStartCommand() executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy() executed");
    }

    @Override public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind() executed");
        return super.onUnbind(intent);
    }

    private IRemoteService.Stub binder = new IRemoteService.Stub() {
        @Override public void addPerson(Person person) throws RemoteException {
            persons.add(person);
        }

        @Override public List<Person> getPersons() throws RemoteException {
            return persons;
        }
    };


    @Nullable @Override public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind() executed");
        return binder;
    }
}

到這里,已經完成了上面所述的三個步驟;
1.創建AIDL和Service
2.實現接口

  private IRemoteService.Stub binder = new IRemoteService.Stub() {
        @Override public void addPerson(Person person) throws RemoteException {
            persons.add(person);
        }

        @Override public List<Person> getPersons() throws RemoteException {
            return persons;
        }
    };

3.向客戶端公開接口

 @Nullable @Override public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind() executed");
        return binder;
    }

第二步實現接口的時候發現這么一個類IRemoteService.Stub,我們之前并沒有創建過這個類,那這個類到底從哪來的?



IRemoteService其實是編譯器根據IRemoteService.aidl這個文件為我們自動生成的一個Java類,Stub是IRemoteService的一個內部抽象類


,我們先不管這個類,接下來分析AIDL原理的時候,再去分析這個類,在RemoteService這個里,創建了一個CopyOnWriteArrayList來保存數據,為什么采用它呢,這里主要是提醒大家考慮線程并發的問題,也就是多個客戶端跟服務端通信的問題,CopyOnWriteArrayList可以避免多線程并發引發的問題,這里我們不對這它進行分析,如果感興趣,可自行去了解,我們在RemoteService里對它的生命周期方法進行了log打印,順便查看下客戶端服務端通信時,Service的生命周期。,不要忘了在AndroidManifest中注冊Service
 <service android:name=".RemoteService"
        android:process=":remote"
        >
    </service>
  </application>

服務端工作已完成,接下來直接上客戶端布局以及Activity代碼


package com.zx.aidl.demo;

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.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btnBind;
    private Button btnUnbind;
    private EditText etId;
    private EditText etName;
    private Button btnAdd;
    private Button btnGetPersons;
    private TextView tvContent;
    private IRemoteService iRemoteService;

    private ServiceConnection serviceConnection = new ServiceConnection() {

        @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             iRemoteService = IRemoteService.Stub.asInterface(iBinder);
        }

        @Override public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnBind = (Button) findViewById(R.id.btn_bind);
        btnUnbind = (Button) findViewById(R.id.btn_unbind);
        btnAdd = (Button) findViewById(R.id.btn_add);
        etId = (EditText) findViewById(R.id.et_id);
        etName = (EditText) findViewById(R.id.et_name);
        btnGetPersons = (Button) findViewById(R.id.btn_get);
        tvContent = (TextView) findViewById(R.id.tv_content);
        btnBind.setOnClickListener(this);
        btnUnbind.setOnClickListener(this);
        btnGetPersons.setOnClickListener(this);
        btnAdd.setOnClickListener(this);
    }

    @Override public void onClick(View view) {
        switch (view.getId()) {
            case  R.id.btn_bind :
                bindService(new Intent(this,RemoteService.class),serviceConnection, Context.BIND_AUTO_CREATE);
                break;
            case  R.id.btn_unbind:
                unbindService(serviceConnection);
                break;
            case  R.id.btn_add:
                addPerson();
                break;
            case  R.id.btn_get :
                getPersons();
                break;
        }
    }

    private void getPersons() {
        try {
            List<Person> personList = iRemoteService.getPersons();
            if (personList != null) {
                StringBuilder sb = new StringBuilder();
                for (Person person : personList) {
                    sb.append("id :").append(person.getId()).append(" ").append("姓名:").append(person.getName()).append("\n");
                }
                tvContent.setText(sb.toString());
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void addPerson() {
        String id = etId.getText().toString();
        String name = etName.getText().toString();
        if (! TextUtils.isEmpty(id) && ! TextUtils.isEmpty(name)) {
            try {
                Person person = new Person();
                person.setId(Integer.parseInt(id));
                person.setName(name);
                iRemoteService.addPerson(person);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

客戶端實現其實很簡單,首先通過bindService方法綁定服務端Service并建立連接,在ServiceConnection 的onServiceConnected方法回調內接受服務端Service onBind方法返回的binder對象并把它轉換為我們需要的
IRemoteService 實例,然后通過IRemoteService實例去調用它本身的addPerson()與getPersons()方法,這樣就達到了客戶端調用服務端的方法功能,其實也就是客戶端與服務端通信,接下來首先看下客戶端bindService()與unBindService()方法時,Service會執行那些生命周期方法。


與startService()方法不同的是,bindService時,Service只執行了onCreate()與onBind()方法,同理,執行unBindService()時,Service執行了與之對應的onUnbind()與onDestroy()方法,所以當我們綁定解綁一個Service時,它的生命周期為:onCreate()--->onBind()--->onUnbind()--->onDestory()
回到正題,看下客戶端與服務端數據傳遞,首先我們在之前已經在服務端RemoteService實現的接口的回調方法里加幾行log日志打印

   private IRemoteService.Stub binder = new IRemoteService.Stub() {
        @Override public void addPerson(Person person) throws RemoteException {
            Log.e(TAG, " addPerson() executed ");
            Log.e(TAG,person.getId()+"");
            Log.e(TAG,person.getName());
            persons.add(person);
        }

        @Override public List<Person> getPersons() throws RemoteException {
            Log.e(TAG, " getPersons() executed ");
            if (persons.size() > 0) {
                for (Person person : persons) {
                    Log.e(TAG,person.getId()+"");
                    Log.e(TAG,person.getName());
                }
            }
            return persons;
        }
    };

通過日志發現,每次在MainActivity中通過調用IRemoteService的addPerson和getPersons方法時,都會執行服務端binder的對應addPerson和getPersons回調方法,至于其中其中的原理,下篇分析AIDL原理時,再詳細分析。。以上就是AIDL基本的使用,當然如果在項目中使用AIDL,業務肯定要復雜的多,本文只是拋磚引玉,真正使用的時候,根據項目相應業務去做處理。。。
AIDL原理分析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容