Android進程通信(IPC)之AIDL對象傳遞

之前寫過一篇關于AIDL的入門篇,相信看過的朋友對AIDL多少會有點了解,如果對AIDL還不是很了解的可以看一下這篇文章Android進程通信(IPC)之AIDL快速入門.今天這篇文章主要講講在AIDL中如何傳遞對象。

在AIDL中可以傳輸的數據有:

  • 基本數據類型(除short)
  • String
  • CharSequence
  • List
  • Map

我們知道Android的每個進程都運行在獨立的虛擬機中,所以進程之間通信不是直接的,如果我們要傳遞一個對象我們得把對象拆分成Android系統可識別的數據單元,然后系統再重新將數據單元合成傳遞給另一個進程。這個過程其實也就是對象的序列化。在AIDL中傳遞對象我們要做以下幾個步驟:

  1. 讓你的對象類實現Parcelable接口
  2. 在該類中實現writeToParcel方法
  3. 添加一個靜態構造器CREATOR
  4. 創建你要傳遞的對象的aidl文件

接下我們來看一個具體的例子,這里我沒有新建工程還是用的上篇文章的工程,如果對該工程目錄結構不清楚的童鞋可以猛戳這里Android進程通信(IPC)之AIDL快速入門

首先在我們服務端建立要傳遞的對象類:

package com.example.administrator.aidldemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable{
    private String name;
    private int age;
    public Person(String name, int age){ 
        this.name = name;
        this.age = age;    
    } 
    //從Parcel中讀出之前寫進的數據
    protected Person(Parcel in) { 
       this.name = in.readString(); 
       this.age = in.readInt();    
    } 
    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
       //反序列化,把我們通過writeToParcel方法寫進的數據再讀出來
       @Override 
       public Person createFromParcel(Parcel in) { 
           return new Person(in);        
       } 
       @Override
       public Person[] newArray(int size) { 
           return new Person[size];       
       }    
    };   
    public String getName() {
        return name;    
    }    
    public void setName(String name) { 
       this.name = name;    
    }    
    public int getAge() { 
       return age;    
    }    
    public void setAge(int age) { 
       this.age = age;    
    } 
    //實現 Parcelable 接口所需重寫的方法1,一般不用管
    @Override    
    public int describeContents() {
        return 0;    
    }
    //實現 Parcelable 接口所需重寫的方法2,將對象的每個字段寫入到Parcel中
    @Override
    public void writeToParcel(Parcel dest, int flags) {
       dest.writeString(name); 
       dest.writeInt(age);
    }    
    @Override
    public String toString() { 
       return "Person{" +
                "name='" + name + '\'' + 
               ", age=" + age +
                '}'; 
    }
}

我們看到,這個對象實現Parcelable接口,覆寫了接口中的兩個方法:describeContents()writeToParcel(Parcel dest, int flags),前一個方法我們暫且不用管它,主要看writeToParcel(Parcel dest, int flags)這個方法,這個方法就是把該對象中的字段寫進Parcel,相當于一艘輪船,我們把要運輸的貨物都裝進去。通過查看文檔我們發現Parcel是沒有writeShort()這個方法的,這也正是AIDL無法傳遞short類型的數據的原因。然后我們再靜態創建構造器CREATOR,注意要加final修飾且名字必須為CREATOR不能更改。其中我們要實現兩個方法,主要看createFromParcel(Parcel in)這個方法,這個方法其實就是把我們之前寫進Parcel中的數據在讀出來。這里要注意,我們之前怎么寫的就怎么讀出來,即哪個字段先寫進去,就先讀取哪個字段。最后覆寫了toString方法,以便之后打印數據。好了這樣我們實體類就建好了。

然后我們在服務端創建該類的aidl文件,文件名為Person

package com.example.administrator.aidldemo;
parcelable Person;

結構很簡單,首先聲明包名,然后在你建的對象類名前加上parcelable,這樣我們的類aidl文件也建好了。

然后我們新建一個aidl接口,叫做DataTestAidlInterface

package com.example.administrator.aidldemo;
import com.example.administrator.aidldemo.Person;
interface DataTestAidlInterface {
    List<Person> getPersonListIn(Person person);
}

這個接口定義了一個方法,方法很簡單,傳入一個Person對象返回一個List,注意要導入之前我們創建的 Person對象。好了我們現在構建下。

Fuck!報錯了。。。好吧我們來看一下報的什么,這里有句話很關鍵

aidl.exe E  4888  4892 type_namespace.cpp:130]     'Person' can be an out type, so you must declare it as in, out or inout.

在AIDL進程傳遞數據時,都有一個數據拆包和打包的過程,這是一個很耗系統內存的,我們要在傳遞的數據前面加上inoutinout來表示數據傳遞的方向。

  • in 表示客戶端向服務端傳遞數據,當服務端的數據發生改變,不會影響到客戶端
  • out表示服務端對數據修改,客戶端會同步變動
  • inout表示客戶端和服務端的數據總是同步的

然后我們修改一下代碼:

package com.example.administrator.aidldemo;
import com.example.administrator.aidldemo.Person;
interface DataTestAidlInterface { 
    List<Person> getPersonListIn(in Person person);
}

這樣我們的aidl接口也定義好了

然后我們新建一個服務

package com.example.administrator.aidldemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class IRemoteService extends Service {
    List<Person> persons;
    @Nullable    
    @Override
    public IBinder onBind(Intent intent) {
        persons = new ArrayList<>();
        return iBinder; 
    }
    private IBinder iBinder = new DataTestAidlInterface.Stub(){
       @Override
        public List<Person> getPersonList(Person person) throws RemoteException {
            persons.add(person);
            return persons;
        }
   }
}

這個服務很簡單,當綁定該服務是新建一個List,用于存放客戶端添加的Person對象。這樣我們的服務端代碼就都寫完了,然后我們把服務端的Person類文件和aidl文件,以及我們定義的DataTestAidlInterface.aidl文件都拷貝到客戶端對應的文件夾下

在客戶端,我們新建一個布局文件,里面就一個按鈕,用于傳遞對象

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent" 
   android:layout_height="match_parent" 
   android:orientation="vertical">
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="傳遞對象"/>
</LinearLayout>

接下來看一下主界面:

public class DataTestActivity extends Activity{
    private Button btn;
    private List<Person> persons;
    private DataTestAidlInterface mDataTestAidlInterface;
    private ServiceConnection conn = new ServiceConnection() {
        //服務連接成功時回調
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDataTestAidlInterface = DataTestAidlInterface.Stub.asInterface(service);
        }
        //斷開服務時回調
        @Override
        public void onServiceDisconnected(ComponentName name) {
            if(mDataTestAidlInterface != null){
                mDataTestAidlInterface = null;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceSate){
        super.onCreate(savedInstanceSate);
        setContentView(R.layout.data_test_layout); 
        btn = (Button)findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Person person = new Person("張三", 10);
                try {
                    persons = mDataTestAidlInterface.getPersonListIn(person);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                Toast.makeText(DataTestActivity.this,persons.toString(),
                        Toast.LENGTH_SHORT).show();
            }
        });
        bindService();
    }
    //綁定服務
    private void bindService(){
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.example.administrator.aidldemo",
                "com.example.administrator.aidldemo.IRemoteService"));
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    } 
    @Override
    protected void onDestroy(){
        super.onDestroy();
        unbindService(conn);
    }
}

當界面啟動時,就綁定到我們之前在服務端中的IRemoteService,綁定成功后,將調用conn中的onServiceConnected方法來獲取在服務端實現好的接口。這樣獲取到接口后,當我們點擊按鈕時,新建一個Person對象,然后調用獲取到的DataTestAidlInterface接口中getPersonListIn方法,之后把返回的List顯示出來。

運行后效果如下:

演示
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容