Android中的IPC機制(一)

好久沒有寫文章了,內心可能有千種理由,可是說到底仍是惰性形成的。學習自己就是一場修行,在沒有人督促的狀況下,更容易產生惰性。因此,要時刻提醒本身要更努力一些。html

前言

因爲IPC機制在Android中屬於比較重要的機制,加之也比較複雜。因此,我會分兩篇文章進行記錄。這篇文章將會從IPC機制概念、進程和線程之間的區別、IPC機制來、常見IPC機制和AIDL實踐來說解IPC機制,剩下的知識點將會在下一篇文章進行記錄。java

IPC機制概念

IPC的全稱:Inter-Process Communication,翻譯過來就是「進程間通訊」,是指兩個進城之間的相互通訊。講到這裏確定會有人問什麼是進程?進程與線程之間的區別和聯繫是什麼?讓咱們來看一下。android

線程:
根據操做系統的描述,線程是是CPU調度的最小單元,同時線程是一種有限的系統資源。它是進程的一個執行流,每一個線程都有本身的堆棧和局部變量。
面試

進程:
一般狀況下是指一個執行單元,在PC或者移動設備上指一個程序或者一個應用。
shell

二者之間的區別:
一、進程是資源分配的最小單位,線程是程序執行的最小單位。
二、線程之間通訊能夠經過共享局部變量、靜態變量等數據。進程之間通訊須要經過IPC機制。
app

固然二者之間的區別確定不止上面兩條,你們能夠自行查閱。
ide

Android如何開啓多進程

講到如何在Android中開啓多進程,你們可能有點懵:「兩個進程不就是兩個應用嗎?」。沒錯,這裏只是開啓多進程的一種形式,可是咱們咱們若是想在一個應用中開啓多進程該如何操做呢?最簡單的咱們能夠經過給四大組件設置不一樣的android:process值來實如今一個應用中的多進程。下面咱們將舉一個例子。源碼分析

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.a00123.ipcdemo">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".FirstActivity"
            android:process=":process"> //1
        </activity>
        <activity
            android:name=".SecondActivity"
            android:process="com.a00123.ipcdemo.process"> //2
        </activity>
    </application>
</manifest>
複製代碼

咱們在Manifest文件中建立了三個Activity,分別是:MainActivityFirstActivitySecondActivity,其中咱們爲FirstActivitySecondActivity分別設置了android:process屬性(代碼註釋1和註釋2處),在MainActivity中分別設置了兩個按鈕,用於打開另外兩個Activity學習

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn1:
                Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                startActivity(intent);
                break;
            case R.id.btn2:
                Intent intent2 = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent2);
                break;
        }
    }
複製代碼

讓咱們來運行一下,看看會有什麼結果。在這裏再多說一句題外話,這裏我用的是adb命令進行查看的進程名稱,至於各位小夥伴的電腦請參照具體方法查看。 ui

由此咱們能夠看出若是沒有設置 android:process屬性的話,默認的進程名就是當前應用的包名。使用「:」來設置的話,會自動在前面加上對應的包名。最後一個就是咱們設置的 android:process="com.a00123.ipcdemo.process"屬性值。這也其實也很好理解,設置進程名就至關於咱們電腦中文件中的絕對路徑和相對路徑。其中以 「:」開頭的進程屬於當前應用的私有進程,其餘應用的組件不能夠和它運行在同一個進程中,而不以 「:」開頭的進程則屬於全局進程,其餘應用經過 ShareUID方式能夠和它運行在同一個進程中。
Android在運行一個程序時,會爲其分配一個惟一的UID,具備相同的UID的兩個應用才能共享數據。若是兩個應用想經過 ShareUID的方式運行在同一進程須要這兩個程序擁有相同的 ShareUID和相同的簽名才能夠。只有這種狀況下,兩個程序之間才能相互獲取對方的私有數據。
既然這麼容易的就能開啓多進程,那咱們是否是就能說進程間的通訊難度也不過如此嘛。若是這樣想,只能說咱們太年輕。

咱們仍是用上面的代碼,再舉一個例子。咱們建立一個類,類中有一個靜態變量,按照咱們正常的思惟理解,靜態資源應該是被共享的,只要有一處改變,其餘地方也會隨之改變,可是在多進程中是這樣嗎?咱們運行一下用數聽說話。

public class IPCManager {
    public static int managerId = 1;
}
複製代碼

正如上面這幾張圖所示,咱們運行起來以後有三個進程。可是,只有在默認進程中靜態變量的值是改變的,其餘兩進程中的靜態變量居然仍是原來的值,這事怎麼回事?

多進程帶來的問題

從上面咱們也能夠看出,當程序中存在多進程時,容易出現問題。出現上面問題的具體緣由是這三個Activity分別運行在三個進程中,上面咱們也說過,Android系統會給每個應用程序分配一個獨立的虛擬機,不一樣的虛擬機在內存地址的分配上是不一樣的。當訪問一個靜態變量時,不一樣的進程之間相互沒有影響,因此這就是爲何在MainActivity中修改靜態變量的值時其餘Activity沒發生變化的緣由。
當運行在不一樣進程中的四大組件,它們想要經過共享內存的方式來共享數據都會產生失敗。因此說,進程間共享數據時,會出現如下問題:

一、靜態成員和單例模式失效
二、線程同步機制失效
三、SharePreference的可靠性降低
四、Application會被重複建立

分別解釋一下 第一個問題在上面中的例子中已經有所說明;第二個問題和第一個問題相同,進程間運行在不一樣的內存上,無論有沒有鎖對象都無法保證線程同步,由於鎖做用的對象不是同一個;第三個問題,SharePreference文件是不支持兩個進程同時寫數據,由於有可能形成數據的丟失,因此可靠性會下降;第四個問題,咱們說過Android建立新的進程同時會分配獨立的虛擬機,這就至關因而從新啓動一個應用的過程,啓動應用是會自動建立新的Application對象,因此會形成Application對象重複建立。

IPC的基礎知識準備

這裏的基礎知識準備主要講講序列化的知識點,固然還有另一個知識點Binder,這個知識點準備放在AIDL實例講解中介紹。
當咱們在四大組件之間使用Intent傳遞對象時,這自己就是一種進程間通訊的方式,須要將對象進行序列化和反序列化。再或者,當咱們須要把對象存儲在手機設備上,這個過程也是須要將對象序列化。咱們來看一下兩種序列化方式SerializableParcelable

Serializable

SerializableJava提供的一種序列化接口,能夠爲對象提供序列化和反序列化操做。使用的時候須要類實現Serializable接口,同時聲明一個serialVersionUID來實現序列化。
從聲明上咱們能夠看出,這個serialVersionUID至關因而類的惟一標識,也就是說,一個類在序列化時保存了惟一標識,在反序列化時會將這個惟一標識要反序列化對象的惟一表示進行對比,若是不一致會報:InvalidClassException

Parcelable

ParcelableAndroid提供的一種序列化方法,相比於Serializable其效率更高,佔用的內存更少,可是使用起來也更麻煩。Android官方推薦使用Parcelable方法進行序列化操做。在AndroidBundleIntent等都默認實現了Parcelable接口。
既然是谷歌爸爸推薦的,咱們確定要多使用一下。可是在使用時比較麻煩,怎麼辦?其實不用擔憂,Android Studio已經中可使用插件進行一鍵生成,美滋滋了。
舉個例子吧,看看Parcelable序列化方式都作了什麼。

public class Dog implements Parcelable {
    private String name;
    private String color;

    public Dog(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }

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

    public void setColor(String color) {
        this.color = color;
    }
    /** * 內容描述 */
    @Override
    public int describeContents() {
        return 0;
    }

    /** * 序列化操做 */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeString(this.color);
    }
    
    //反序列化數據恢復
    protected Dog(Parcel in) {
        this.name = in.readString();
        this.color = in.readString();
    }
    
    //建立Parcelable對象
    public static final Parcelable.Creator<Dog> CREATOR = new Parcelable.Creator<Dog>() {
        @Override
        public Dog createFromParcel(Parcel source) {
            return new Dog(source);
        }

        @Override
        public Dog[] newArray(int size) {
            return new Dog[size];
        }
    };
}
複製代碼

Serializable和Parcelable的區別

一、Serializable屬於Java;Parcelable屬於Android
二、Serializable序列化和反序列化過程須要大量的I/O操做;Parcelable不須要
三、Serializable開銷較大;Parcelable開銷較小
四、Serializable效率低;Parcelable效率低

AIDL實例

在咱們平常開發中不多使用到AIDL(也多是本人開發過程當中不多使用),但不論在平時學習仍是面試的過程當中,這都是一個很是重要的知識點。咱們經過一個實例來練習一下,並經過這個例子探究一下Binder機制。有關AIDL的介紹網上有不少,我在這裏就再也不贅述,有興趣的小夥伴能夠參照AIDL谷歌官方文檔
在寫AIDL例子的時候,我在網上看到不少都是參照任玉剛老師《Android開發工藝探索》的中例子。這個例子很經典,我也打算參照這個例子,可是此次是對電影票進行操做。

建立服務端

一、建立Tickets類

public class Tickets implements Parcelable {
    private String name;
    private float prices;

    public Tickets(String name, float prices) {
        this.name = name;
        this.prices = prices;
    }

    public String getName() {
        return name;
    }

    public float getPrices() {
        return prices;
    }

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

    public void setPrices(float prices) {
        this.prices = prices;
    }

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

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

    protected Tickets(Parcel in) {
        this.name = in.readString();
        this.prices = in.readFloat();
    }

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

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

    @Override
    public String toString() {
        return "票名:" + name + "---" + "票價:" + prices + "元";
    }
}
複製代碼

在這個類中建立了兩個變量,分別表示名稱和價格。

二、建立Tickets.aidl文件

建立文件很簡單,在項目包目錄下直接右鍵,選擇新建->AIDL->AIDL File,取名爲Tickets.aidl

文件建立成功以後, AndroiStudio會自動在工程目錄下建立一個aidl包,而且其子包名一工程的包名一致,須要有兩點須要注意的地方。第一,這個 ADIL文件應該和第一步中建立的實體類的包名保持一致;第二,就是aidl文件中的內容,建立出來以後若是不對內容修改,會報出名稱不惟一的錯誤。

//Tickets.aidl文件內容
package com.a00123.aidlservice;
// Declare any non-default types here with import statements
parcelable Tickets;
複製代碼

在這裏須要再次提醒一下,每新建一個類或者aidl文件,最好從新build一下工程。

三、建立TicketsManager.aidl文件

建立步驟和第二步相同,這裏要注意的是最好路徑一上一個aidl文件路徑相同,這樣方便後期操做。

// TicketsManager.aidl
package com.a00123.aidlservice;

// Declare any non-default types here with import statements
import com.a00123.aidlservice.Tickets;

interface TicketsManager {
    List<Tickets> getTicketsList();

    void addTickets(in Tickets tickets);
}

複製代碼

在這裏定義了兩個方法,一個是獲取票列表,另一個是增長票的方法。aidl文件中是不支持自動導包,因此咱們須要手動把Tickets的包名導入。咱們看到在addTickets方法中,在參數以前有一個in,這是什麼意思呢?
實際上in, out, inout是三個定向tag,它們實際的含義是:全部的非基本參數都須要一個定向tag來指出數據的流向,無論是 in , out , 仍是 inout 。基本參數的定向tag默認是而且只能是 in
在咱們從新build完項目以後,會在項目的build目錄下生成兩個文件。其中,有一個Tickets.java文件,這個文件內容爲空。另外還會生成一個Interface文件,這個文件是比較中要的,它牽扯到AIDL是怎樣在進程間進行通訊的。因爲這個文件比較複雜,放到文章的後部進行講解,咱們先把流程走通。

四、建立服務

public class TicketsManagerService extends Service {
   private CopyOnWriteArrayList<Tickets> mTickets = new CopyOnWriteArrayList<>();
   private Binder mBinder = new TicketsManager.Stub() {
       @Override
       public List<Tickets> getTicketsList() throws RemoteException {
           return mTickets;
       }

       @Override
       public void addTickets(Tickets tickets) throws RemoteException {
           mTickets.add(tickets);
       }
   };

   @Override
   public void onCreate() {
       super.onCreate();

       mTickets.add(new Tickets("攀登者",50));
       mTickets.add(new Tickets("我和個人祖國",55));
   }

   @Override
   public IBinder onBind(Intent intent) {
       return mBinder;
   }
}
複製代碼

在建立服務的時候咱們新建一個TicketsManager.Stub對象,同時也實現了其內部的兩個方法,這兩個方法就是咱們在第3步中定義的方法。在服務建立的時候新增了兩張票。到此服務端代碼已經建立完畢,不要忘記在清單文件中註冊服務。

<service
    android:name=".TicketsManagerService"
    android:enabled="true"
    android:exported="true"/>
複製代碼

其中的enable屬性表示是否能夠被系統實例化,exported屬性表示可否被其餘應用隱式調用。

建立客服端

一、複製文件

咱們須要將服務端的兩個aidl文件、Tickets文件複製到客戶端,這裏須要注意,這些文件的路徑要和服務端中對應文件路徑保持一致。

二、調用服務進行進程間通訊

public class MainActivity extends AppCompatActivity {
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            TicketsManager ticketsManager = TicketsManager.Stub.asInterface(service);
            try {
                List<Tickets> list = ticketsManager.getTicketsList();
                Log.i("MainActivity", "query tickets list:" + list.toString());
                Tickets tickets = new Tickets("中國機長", 45);
                ticketsManager.addTickets(tickets);
                Log.i("MainActivity", "add tickets:" + tickets);
                List<Tickets> newList = ticketsManager.getTicketsList();
                Log.i("MainActivity", "query tickets list:" + newList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setClassName("com.a00123.aidlservice", "com.a00123.aidlservice.TicketsManagerService");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}
複製代碼

在服務鏈接以後,咱們首調用了getTicketsList()方法並將結果打印了出來,而後有新增了一個Tickets對象,最後在一次打印。因爲服務是在另一個進程中,因此在綁定的時候須要知道服務所在的包名和全路徑名稱,最後直接綁定。讓咱們看一下打印結果。

AIDL原理

雖然咱們已經學會了如何使用AIDL,可是有不少地方感受仍是有些雲裏霧裏,就讓咱們探究一下AIDL的原理。
不知道你們是否還記得,當咱們建立TicketsManager以後系統會在build目錄下爲咱們自動建立的那個TicketsManager文件,這個文件很重要,能夠說這裏面就是AIDL的運行本質,一樣也能夠說是binder機制的原理,讓咱們來看一下。

public interface TicketsManager extends android.os.IInterface {
    /** * Default implementation for TicketsManager. */
    public static class Default implements com.a00123.aidlservice.TicketsManager {
        @Override
        public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
            return null;
        }

        @Override
        public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
        }

        @Override
        public android.os.IBinder asBinder() {
            return null;
        }
    }

    /** * Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.a00123.aidlservice.TicketsManager {
        private static final java.lang.String DESCRIPTOR = "com.a00123.aidlservice.TicketsManager";

        /** * Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /** * Cast an IBinder object into an com.a00123.aidlservice.TicketsManager interface, * generating a proxy if needed. */
        public static com.a00123.aidlservice.TicketsManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.a00123.aidlservice.TicketsManager))) {
                return ((com.a00123.aidlservice.TicketsManager) iin);
            }
            return new com.a00123.aidlservice.TicketsManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getTicketsList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.a00123.aidlservice.Tickets> _result = this.getTicketsList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addTickets: {
                    data.enforceInterface(descriptor);
                    com.a00123.aidlservice.Tickets _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.a00123.aidlservice.Tickets.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addTickets(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.a00123.aidlservice.TicketsManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.a00123.aidlservice.Tickets> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getTicketsList, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getTicketsList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((tickets != null)) {
                        _data.writeInt(1);
                        tickets.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addTickets, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addTickets(tickets);
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            public static com.a00123.aidlservice.TicketsManager sDefaultImpl;
        }

        static final int TRANSACTION_getTicketsList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addTickets = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

        public static boolean setDefaultImpl(com.a00123.aidlservice.TicketsManager impl) {
            if (Stub.Proxy.sDefaultImpl == null && impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.a00123.aidlservice.TicketsManager getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException;

    public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException;
}
複製代碼

若是你們將方法進行摺疊,你會發現這裏面其實一共作了如下幾件事:

一、定義了getTicketsList()方法
二、定義了addTickets(com.a00123.aidlservice.Tickets tickets)方法
三、建立了一個默認實現TickManager接口的靜態內部類
四、建立了一個名爲Stub的靜態內部類,這個類繼承自Binder,同時也實現了TicketsManager接口
可是當咱們仔細觀察時,有感受無從下手,彆着急,咱們一步一步的看。

android.os.IInterface

咱們看到這個TicketManager接口繼承了一android.os.IInterface接口,這個接口內部作了什麼?

/** * Base class for Binder interfaces. When defining a new interface, * you must derive it from IInterface. */
public interface IInterface {
    /** * Retrieve the Binder object associated with this interface. * You must use this instead of a plain cast, so that proxy objects * can return the correct result. */
    public IBinder asBinder();
}
複製代碼

從翻譯中咱們不難看出,這個接口是Binder接口的基類,若是想定義一個新的接口,必需要繼承它。這個接口中有一個asBinder()方法,默認返回的是IBinder。也就是說IInterface能夠把全部繼承它的對象轉換成IBinder

IBinder

既然說到了IBinder,咱們來看一IBinder是什麼。

public interface IBinder {
...
    /** * Attempt to retrieve a local implementation of an interface * for this Binder object. If null is returned, you will need * to instantiate a proxy class to marshall calls through * the transact() method. */
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
   
   /** * Perform a generic operation with the object. * * @param code The action to perform. This should * be a number between {@link #FIRST_CALL_TRANSACTION} and * {@link #LAST_CALL_TRANSACTION}. * @param data Marshalled data to send to the target. Must not be null. * If you are not sending any data, you must create an empty Parcel * that is given here. * @param reply Marshalled data to be received from the target. May be * null if you are not interested in the return value. * @param flags Additional operation flags. Either 0 for a normal * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC. * * @return Returns the result from {@link Binder#onTransact}. A successful call * generally returns true; false generally means the transaction code was not * understood. */
    public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException; 
...
}
複製代碼

咱們能夠看到IBinder也是一個接口,這個接口中定義了不少變量和方法,其中咱們着重看一下上面兩個方法。

IInterface queryLocalInterface(@NonNull String descriptor)

這個方法但願返回一個本地實現了該接口的Binder對象,若是返回值爲null,就須要實例化一個代理類去調用transact()方法。其實這個方法使用來判斷AIDL中服務調用這的進程身份,若是是當前進程,返回值不爲null;若是是其餘進程,就須要建立一個代理類去調用transact()方法。

public boolean transact(...)

這個方法中傳入了4個參數,咱們分別看一下這4個參數都是什麼意思:

一、code表示要執行的操做,它的取值範圍在FIRST_CALL_TRANSACTIONLAST_CALL_TRANSACTION之間
二、data表示傳輸給目標的數據,必定不能爲空,若是爲空,須要建立一個未被初始化的Parcel數據。
三、replay表示從目標接收的數據,若是你不感興趣能夠返回空
四、flags附加操做的標識,0是指普通的RPC。或者FLAG_ONEWAY,指單向RPC。
這個方法返回值會經過調用Binder類中的onTransact方法。若是成功的執行了,則返回true;反之表示不清楚要處理的code值是什麼含義。

RPC

從上面的方法中咱們又知道了另一個名詞:RPC關於其定義這裏就很少解釋了,詳情請參照柳樹之大佬的如何給老婆解釋什麼是RPC

Binder

上面的transact(...)方法會調用Binder中的onTransact方法,因此咱們來看一下Binder類。

/** * Base class for a remotable object, the core part of a lightweight * remote procedure call mechanism defined by {@link IBinder}. * This class is an implementation of IBinder that provides * standard local implementation of such an object. * * <p>Most developers will not implement this class directly, instead using the * <a href="{@docRoot}guide/components/aidl.html">aidl</a> tool to describe the desired * interface, having it generate the appropriate Binder subclass. You can, * however, derive directly from Binder to implement your own custom RPC * protocol or simply instantiate a raw Binder object directly to use as a * token that can be shared across processes. * * <p>This class is just a basic IPC primitive; it has no impact on an application's * lifecycle, and is valid only as long as the process that created it continues to run. * To use this correctly, you must be doing so within the context of a top-level * application component (a {@link android.app.Service}, {@link android.app.Activity}, * or {@link android.content.ContentProvider}) that lets the system know your process * should remain running.</p> * * <p>You must keep in mind the situations in which your process * could go away, and thus require that you later re-create a new Binder and re-attach * it when the process starts again. For example, if you are using this within an * {@link android.app.Activity}, your activity's process may be killed any time the * activity is not started; if the activity is later re-created you will need to * create a new Binder and hand it back to the correct place again; you need to be * aware that your process may be started for another reason (for example to receive * a broadcast) that will not involve re-creating the activity and thus run its code * to create a new Binder.</p> * * @see IBinder */
public class Binder implements IBinder {
...
    /** * Use information supplied to attachInterface() to return the * associated IInterface if it matches the requested * descriptor. */
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

    /** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */
    public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            reply.writeString(getInterfaceDescriptor());
            return true;
        } else if (code == DUMP_TRANSACTION) {
            ParcelFileDescriptor fd = data.readFileDescriptor();
            String[] args = data.readStringArray();
            if (fd != null) {
                try {
                    dump(fd.getFileDescriptor(), args);
                } finally {
                    IoUtils.closeQuietly(fd);
                }
            }
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
            return true;
        } else if (code == SHELL_COMMAND_TRANSACTION) {
            ParcelFileDescriptor in = data.readFileDescriptor();
            ParcelFileDescriptor out = data.readFileDescriptor();
            ParcelFileDescriptor err = data.readFileDescriptor();
            String[] args = data.readStringArray();
            ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
            ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
            try {
                if (out != null) {
                    shellCommand(in != null ? in.getFileDescriptor() : null,
                            out.getFileDescriptor(),
                            err != null ? err.getFileDescriptor() : out.getFileDescriptor(),
                            args, shellCallback, resultReceiver);
                }
            } finally {
                IoUtils.closeQuietly(in);
                IoUtils.closeQuietly(out);
                IoUtils.closeQuietly(err);
                // Write the StrictMode header.
                if (reply != null) {
                    reply.writeNoException();
                } else {
                    StrictMode.clearGatheredViolations();
                }
            }
            return true;
        }
        return false;
    }
...
}
複製代碼

咱們瞅一眼註釋就能看出Binder的重要性,它不只用在AIDL,同時運用在ServiceActivityContentProvider中。註釋中說了不少,總結成一句話:這個類很重要,你會不止一次地回頭看它。看到這裏,咱們來分析如下咱們本身寫的代碼。
首先,咱們在建立服務端遠程服務時調用TicketsManager.Stub()方法建立了一個Binder對象,這個Binder對象中重寫了getTicketsList()addTickets(Tickets tickets)方法。最後在服務被綁定時將這個binder對象轉成IBinder返回出去。
第二步,咱們須要建立遠程服務,並綁定。
第三步,建立遠程對象時須要傳入一個ServiceConnection對象,因此咱們在客戶端又建立ServiceConnection對象,並在其onServiceConnected方法回調中獲取了一個IBinder對象,這個IBinder對象就是第一步一步中傳遞過來的。拿到這個對象以後,咱們又調用了TicketsManager.Stub.asInterface(service)方法,將IBinder對象傳入,得到一個TicketsManager對象。自此,客戶端和服務端算是鏈接到一塊兒。咱們看一下TicketsManager.Stub.asInterface(service)內部的調用狀況。

public static abstract class Stub extends android.os.Binder implements com.a00123.aidlservice.TicketsManager {
        private static final java.lang.String DESCRIPTOR = "com.a00123.aidlservice.TicketsManager";

        /** * Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /** * Cast an IBinder object into an com.a00123.aidlservice.TicketsManager interface, * generating a proxy if needed. */
        public static com.a00123.aidlservice.TicketsManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 
            if (((iin != null) && (iin instanceof com.a00123.aidlservice.TicketsManager))) { //1
                return ((com.a00123.aidlservice.TicketsManager) iin);
            }
            return new com.a00123.aidlservice.TicketsManager.Stub.Proxy(obj);
        }
        
        private static class Proxy implements com.a00123.aidlservice.TicketsManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                java.lang.String descriptor = DESCRIPTOR;
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(descriptor);
                        return true;
                    }
                    case TRANSACTION_getTicketsList: { //3
                        data.enforceInterface(descriptor);
                        java.util.List<com.a00123.aidlservice.Tickets> _result = this.getTicketsList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addTickets: { //3
                        data.enforceInterface(descriptor);
                        com.a00123.aidlservice.Tickets _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = com.a00123.aidlservice.Tickets.CREATOR.createFromParcel(data);
                        } else {
                            _arg0 = null;
                        }
                        this.addTickets(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                    default: {
                        return super.onTransact(code, data, reply, flags);
                    }
            }
        }

            @Override
            public java.util.List<com.a00123.aidlservice.Tickets> getTicketsList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.a00123.aidlservice.Tickets> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getTicketsList, _data, _reply, 0); //2
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getTicketsList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            ...
            @Override
            public void addTickets(com.a00123.aidlservice.Tickets tickets) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((tickets != null)) {
                        _data.writeInt(1);
                        tickets.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addTickets, _data, _reply, 0); //2
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addTickets(tickets);
                        return;
                    }
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            public static com.a00123.aidlservice.TicketsManager sDefaultImpl;
        }
}
複製代碼

上面的代碼咱們已經看過一次了,咱們在來看如下。在調用TicketsManager.Stub.asInterface方法以後,代碼會來到註釋1處。在以前的源碼分析中咱們知道這裏是對進程進行判斷,若是屬於同一進程,則會返回本地的TicketManager;若是不屬於同一進程,則會返回Proxy對象。

第四步,在客戶端的MainActivity中咱們調用了ticketsManager.getTicketsList()方法,其實這個方法就是調用了Proxy對象的getTicketsList()方法。在這個方法中會調用mRemote.transact方法,並把參數傳入了進去。咱們看到傳入的第一個參數是Stub.TRANSACTION_addTickets,這個參數是標識操做類型。同時,這個mRomote對象,其實就是一個TicketsManager對象,你們不要被繞暈。在調用transact(...)方法時,內部會去調用onTransact(...)方法,也就是在註釋3處。在註釋3處,首先判斷了要操做的類型,這裏的this就是咱們在服務端的TicketManagerService中定義的那個mBinder對象,它內部會獲取到一個List集合,並賦值給_result變量。因爲有返回值。因此進行了reply.writeTypedList(_result)方法,該方法。。。。。

第五步,因爲onTransact(...)方法中已經返回true,回到註釋2處,將結果進行反序列化_reply.createTypedArrayList(com.a00123.aidlservice.Tickets.CREATOR),最後將結果返回到客服端。還有另一個addTickets(Tickets tickets)方法,這個方法執行流程與getTickets()是一致的,這裏就不作贅述。
至此,咱們已經將AIDL的原理進行了說明,如今就讓咱們進行小結一下。

小結

一、當咱們建立帶有抽象方法的XXX.aidl文件時,系統會自動幫咱們建立一個對應的XXXManager文件,若是有時間的話,咱們能夠本身實現。
二、在服務服務端建立一個Service對象,並將第一步中的XXXManager中子類Stub的實現對象在onBind()方法返回出去。
三、在客戶端建立一個XXXManager.Stub對象,並調用asInterface()方法將服務完成鏈接後的IBinder對象傳進去,這個IBinder對象就是咱們在第二步中的onBind()方法中返回的XXXManager.Stub對象。在調用asInterface時,其內部對進程進行了判斷,若是是不一樣的進程,則會返回一個XXXManagerProxy代理對象。
四、咱們在客戶端調用XXXManager中獲取數據、修改數據方法時,會走到XXXManager.Stub對象的transact方法,並傳入參數。在transact方法內部還將會調用XXXManager.Stub中的onTransact方法,在這個方法中回首先根據傳入的參數進行操做類型的判斷,而後對數據進行序列化或者賦值等操做,最後將數據作返回或者存入修改

因爲篇幅有限,這篇文章暫時先分析到這裏。本人資歷尚淺,能力有限,若是有哪裏寫的不對的地方,歡迎各位大佬批評指正。關於Binder機制的總結和其餘IPC方式,我會在後續的Android中的IPC機制(二)中繼續分析,敬請期待。

參考資料

AIDL谷歌官方文檔
任玉剛《Android開發藝術探索》
柳樹之如何給老婆解釋什麼是RPC

相關文章
相關標籤/搜索