2.3 IPC基礎(chǔ)概念介紹(二)

1. Binder簡介

Binder是Android中的一個類,它實(shí)現(xiàn)了IBinder接口姨拥。從IPC角度來說绅喉,Binder是Android中的一種跨進(jìn)程通信方式渠鸽。Binder還可以理解為一種虛擬的物理設(shè)備,其設(shè)備驅(qū)動是/dev/binder柴罐,該通信方式在Linux中沒有徽缚。從Android Framework(框架層) 角度來說,Binder是ServiceManager連接各種Manager(ActivityManager革屠,WindowManager凿试,等等)和相應(yīng)的ManagerService的橋梁;從android應(yīng)用層來講屠阻,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介红省,當(dāng)bindService的時候额各,服務(wù)端會返回一個包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象国觉,通過這個Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)虾啦,包括普通服務(wù)和AIDL的服務(wù)麻诀。
Android開發(fā)中,Binder主要用于在Service中傲醉,包括AIDL和Messenger蝇闭,其中普通Service中的Binder不涉及進(jìn)程間通信,所以比較簡單硬毕,無法觸及Binder核心呻引,而Messenger(信息員)的底層其實(shí)是AIDL,所以這里選擇用AIDL來分析Binder的工作機(jī)制知允。

2. 普通Service實(shí)現(xiàn)

public class BookService extends Service {
    BookBinder bookBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        bookBinder = new BookBinder();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return bookBinder;
    }
    // service里的方法比庄,這里是private志笼,對外提供接口BookListenner
    private class BookBinder extends Binder implements BookListnner {
        @Override
        public void bookPrint() {
            Log.e("aaa", "book print");
        }

    }
}
public interface BookListnner {
    void bookPrint();
}

activity中綁定service并調(diào)用service中的方法

public class BookActivity extends AppCompatActivity {
    BookListnner bookListnner;
    ServiceConnection serviceConnection;
    Intent intent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book);
        intent = new Intent(this, BookService.class);
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 綁定成功后
                if (service != null) {
                    bookListnner = (BookListnner) service;
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                // 解綁成功后
            }
        };
        // 綁定服務(wù)
        bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);

        findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bookListnner.bookPrint();
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
         // 解綁兩次也會出錯,這里trycatch包一下
        try {
            unbindService(serviceConnection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 啟動服務(wù)-startService
    服務(wù)生命周期:onCreate
  • 停止服務(wù)-stopService
    服務(wù)生命周期:onDestroy
  • 綁定服務(wù)-bindService
    服務(wù)生命周期:onCreate-onBind
  • 解綁服務(wù)-unbindService
    服務(wù)生命周期:onUnBind-onDestroy
  • 如果只是綁定服務(wù)童谒,Activity銷毀時要解綁服務(wù)。
  • 如果啟動服務(wù)和綁定服務(wù)都執(zhí)行了沪羔,Activity銷毀時饥伊,也要解綁服務(wù),這時因?yàn)榉?wù)被startService啟動過蔫饰,不會隨著解綁而銷毀琅豆。除非顯性執(zhí)行stopService,否則一直在后臺運(yùn)行篓吁。
  • 服務(wù)調(diào)用Activity方法使用廣播方式茫因。

3. 編寫B(tài)ook.java,Book.aidl越除,IBookManager.aidl文件

public class Book implements Parcelable {
    private int bookId;
    private String bookName;
    // getter setter parcelable...
}
// Book.aidl
package qingfengmy.developmentofart._2activity.aidl;

parcelable Book;

  • Book.aidl文件和Book.java要在同一目錄下节腐。
  • 用Android studio新建的aidl要求interface的名字唯一外盯,因?yàn)橹耙呀?jīng)建過Book.java,所以Book.aidl無法創(chuàng)建翼雀,可以先寫成IBook.aidl饱苟,然后再重命名。
  • 新建的aidl文件是帶interface的狼渊,注意這里的Book.aidl沒有interface聲明箱熬。
  • Book.aidl是Book類在aidl中的聲明,這樣別的aidl類就可以引用它狈邑。
// IBookManager.aidl
package qingfengmy.developmentofart._2activity.aidl;

// Declare any non-default types here with import statements
import qingfengmy.developmentofart._2activity.aidl.Book;
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
}
  • IBookManager.aidl和Book.aidl在同一目錄下城须。
  • IBookManager.aidl引用了Book,盡管同一目錄米苹,也要寫import語句糕伐,否則報錯如下:
> java.lang.RuntimeException:
com.android.ide.common.process.ProcessException:
org.gradle.process.internal.ExecException: 
Process 'command 'F:\android-sdk\build-tools\23.0.3\aidl.exe'' finished with non-zero exit value 1
  • Book.aidl沒有對應(yīng)的java文件生成。
  • IBookManager.aidl有對應(yīng)的java文件生成其路徑是
F:\workspace4sdutio\DevelopmentOfArt
\app\build\generated\source\aidl\debug
\qingfengmy\developmentofart\_2activity\aidl\IBookManager.java

4. aidl生成的java文件分析

public interface IBookManager extends android.os.IInterface {
    //...
}

IBookManager是個interface蘸嘶,并且實(shí)現(xiàn)了IInterface接口良瞧。所有可以在Binder中傳輸?shù)慕涌诙夹枰^承interface這個接口。

public java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> getBookList() throws android.os.RemoteException;

public void addBook(qingfengmy.developmentofart._2activity.aidl.Book book) throws android.os.RemoteException;

定義了兩個方法训唱,就是我們在aidl中定義的方法褥蚯。

static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

聲明了兩個int型的方法ID,這兩個id用于標(biāo)識在transact過程中客戶端所請求的到底是哪個方法况增。

public static abstract class Stub extends android.os.Binder implements qingfengmy.developmentofart._2activity.aidl.IBookManager {
...
}

一個叫做Stub的內(nèi)部類赞庶,這個Stub(存根)就是一個Binder類,當(dāng)客戶端和服務(wù)端位于同一進(jìn)程時澳骤,方法調(diào)用不會走跨進(jìn)程的transact過程歧强,而當(dāng)兩者位于不同進(jìn)程時,方法調(diào)用需要走transact過程宴凉,這個邏輯由Stub內(nèi)部代理Proxy來完成誊锭。

private static final java.lang.String DESCRIPTOR = "qingfengmy.developmentofart._2activity.aidl.IBookManager";

DESCRIPTOR:Binder的唯一標(biāo)識,一般用當(dāng)前Binder的類名表示弥锄,比如本例中的"qingfengmy.developmentofart._2activity.aidl.IBookManager";

public static qingfengmy.developmentofart._2activity.aidl.IBookManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof qingfengmy.developmentofart._2activity.aidl.IBookManager))) {
        return ((qingfengmy.developmentofart._2activity.aidl.IBookManager) iin);
    }
    return new qingfengmy.developmentofart._2activity.aidl.IBookManager.Stub.Proxy(obj);
}

用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的aidl接口類型的對象丧靡,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端位于同一進(jìn)程籽暇,那么此方法返回的就是服務(wù)端的Stub對象本身温治,否則返回的就是系統(tǒng)封裝后的Stub.proxy對象。

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

返回當(dāng)前的Binder對象戒悠,IInterface接口中的唯一方法熬荆。

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_getBookList: {
            data.enforceInterface(DESCRIPTOR);
            java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> _result = this.getBookList();
            reply.writeNoException();
            reply.writeTypedList(_result);
            return true;
        }
        case TRANSACTION_addBook: {
            data.enforceInterface(DESCRIPTOR);
            qingfengmy.developmentofart._2activity.aidl.Book _arg0;
            if ((0 != data.readInt())) {
                _arg0 = qingfengmy.developmentofart._2activity.aidl.Book.CREATOR.createFromParcel(data);
            } else {
                _arg0 = null;
            }
            this.addBook(_arg0);
            reply.writeNoException();
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

這個方法運(yùn)行在服務(wù)端中的線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請求時绸狐,遠(yuǎn)程請求會通過系統(tǒng)底層封裝后交由此方法來處理卤恳。該方法原型為

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么累盗,接著從data中取出目標(biāo)方法所需的參數(shù),然后執(zhí)行目標(biāo)方法突琳。當(dāng)目標(biāo)方法執(zhí)行完畢后若债,就向reply中寫入返回值。
如果此方法返回false拆融,那么客戶端會請求失敗蠢琳,因此我們可以利用這個特性來做權(quán)限驗(yàn)證,畢竟我們不希望隨便一個進(jìn)程都能遠(yuǎn)程調(diào)用我們的服務(wù)镜豹。

private static class Proxy implements qingfengmy.developmentofart._2activity.aidl.IBookManager {
    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;
    }
}

Proxy是Stub的內(nèi)部類傲须,如果是跨進(jìn)程的話,客戶端拿到的Binder會是proxy趟脂。這里的方法在客戶端調(diào)用泰讽。

private static class Proxy implements qingfengmy.developmentofart._2activity.aidl.IBookManager {

    @Override
    public java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> getBookList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<qingfengmy.developmentofart._2activity.aidl.Book> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(qingfengmy.developmentofart._2activity.aidl.Book.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

getBookList方法所需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和返回值對象List散怖;然后把該方法的參數(shù)信息寫入_data中菇绵;接著調(diào)用transact方法來發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請求肄渗,同時當(dāng)前線程掛起镇眷;然后服務(wù)端的onTransact方法會被調(diào)用,知道RPC過程返回后翎嫡,當(dāng)前線程繼續(xù)執(zhí)行欠动,并從_reply中取出RPC過程的返回結(jié)果;最后返回_reply中的數(shù)據(jù)惑申。

@Override
public void addBook(qingfengmy.developmentofart._2activity.aidl.Book book) 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 ((book != null)) {
            _data.writeInt(1);
            book.writeToParcel(_data, 0);
        } else {
            _data.writeInt(0);
        }
        mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

這個方法也在客戶端運(yùn)行具伍,同上面的方法。只是addBook沒有返回值所以不需要從_reply中取結(jié)果圈驼。
注意:首先人芽,當(dāng)客戶端發(fā)起遠(yuǎn)程請求時,由于當(dāng)前線程會被掛起直至服務(wù)器進(jìn)程返回數(shù)據(jù)绩脆,所以如果一個遠(yuǎn)程方法是很耗時的萤厅,那么不能在UI線程中發(fā)起此遠(yuǎn)程請求;其次靴迫,由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中惕味,所以Binder方法不管是否耗時都應(yīng)該采用同步的方式去實(shí)現(xiàn),因?yàn)樗呀?jīng)運(yùn)行在一個線程中了玉锌。

5. 同一個進(jìn)程客戶端調(diào)用服務(wù)端

public class BookService extends Service {
    BookBinder bookBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        bookBinder = new BookBinder();
        Log.e("aaa", "onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e("aaa", "onBind");
        return bookBinder;
    }

    private class BookBinder extends IBookManager.Stub {

        @Override
        public List<Book> getBookList() throws RemoteException {
            Log.e("aaa", "getBookList");
            return null;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.e("aaa", "addBook");
        }
    }
}

public class BookActivity extends AppCompatActivity {
    IBookManager iBookManager;
    ServiceConnection serviceConnection;
    Intent intent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book);
        intent = new Intent(this, BookService.class);
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                if (service != null) {
                    iBookManager = IBookManager.Stub.asInterface(service);
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        
        bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);

        findViewById(R.id.click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    iBookManager.getBookList();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁兩次也會出錯名挥,這里trycatch包一下
        try {
            unbindService(serviceConnection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6. 客戶端和服務(wù)端不在一個進(jìn)程

首先把BookActivity的進(jìn)程改成子進(jìn)程

<activity android:name="._2activity.BookActivity" android:process=":remote"></activity>

結(jié)果正常執(zhí)行。
接著改成全局進(jìn)程主守。

<activity android:name="._2activity.BookActivity" android:process="qingfengmy.developmentofart.remote"></activity>

也可以禀倔¢冢可能是因?yàn)轱@性綁定服務(wù)的原因,這里改成隱式綁定服務(wù)救湖。

<service android:name="._2activity.BookService">
    <intent-filter>
        <action android:name="qingfengmy.developmentofart.bookService"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
    </intent-filter>
</service>
intent = new Intent();
intent.setAction("qingfengmy.developmentofart.bookService");

綁服務(wù)的時候直接掛掉剃袍。報錯如下:

Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit

這里說的是不能隱式啟動Service,這是5.0以后加上的捎谨,那么我們修改如下:

intent = new Intent();
intent.setAction("qingfengmy.developmentofart.bookService");
intent.setPackage("qingfengmy.developmentofart");

結(jié)果無論BookActivity的進(jìn)程改成什么民效,都可以成功鏈接service并調(diào)用service的方法。

7. 跨應(yīng)用調(diào)用該服務(wù)

首先Book和aidl的包路徑要和服務(wù)里的一樣

qingfengmy.developmentofart._2activity.aidl.Book
qingfengmy/developmentofart/_2activity/aidl/Book.aidl
qingfengmy/developmentofart/_2activity/aidl/IBookManager.aidl

其他和同應(yīng)用的寫法一樣涛救。原service沒做任何其他配置畏邢。至于為什么Service沒有配置android:exported="true"也可以被其他應(yīng)用啟動。那是因?yàn)閑xported屬性检吆,在四大組件沒有配置intent-filter時舒萎,其值默認(rèn)為false。在四大組件配置intent-filter時蹭沛,其值默認(rèn)為true臂寝。也就是上面的實(shí)現(xiàn),隱式啟動Service時摊灭,exported的值都是true咆贬。如果改成false,跨應(yīng)用將不可調(diào)用帚呼。而同應(yīng)用里的不同進(jìn)程掏缎,還是可以啟動和綁定Service。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末煤杀,一起剝皮案震驚了整個濱河市眷蜈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沈自,老刑警劉巖酌儒,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枯途,居然都是意外死亡忌怎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門柔袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呆躲,“玉大人,你說我怎么就攤上這事捶索〔宓啵” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辅甥。 經(jīng)常有香客問我酝润,道長,這世上最難降的妖魔是什么璃弄? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任要销,我火速辦了婚禮,結(jié)果婚禮上夏块,老公的妹妹穿的比我還像新娘疏咐。我一直安慰自己,他們只是感情好脐供,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布浑塞。 她就那樣靜靜地躺著,像睡著了一般政己。 火紅的嫁衣襯著肌膚如雪酌壕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天歇由,我揣著相機(jī)與錄音卵牍,去河邊找鬼。 笑死沦泌,一個胖子當(dāng)著我的面吹牛糊昙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赦肃,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼溅蛉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了他宛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤欠气,失蹤者是張志新(化名)和其女友劉穎厅各,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體预柒,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队塘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宜鸯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔古。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖淋袖,靈堂內(nèi)的尸體忽然破棺而出鸿市,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布焰情,位于F島的核電站陌凳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏内舟。R本人自食惡果不足惜合敦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望验游。 院中可真熱鬧充岛,春花似錦、人聲如沸耕蝉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赔硫。三九已至炒俱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爪膊,已是汗流浹背权悟。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留推盛,地道東北人峦阁。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像耘成,于是被迫代替她去往敵國和親榔昔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容