4.2.3 Service 結(jié)丹

標(biāo)注:本文為個(gè)人學(xué)習(xí)使用,僅做自己學(xué)習(xí)參考使用公给,請(qǐng)勿轉(zhuǎn)載和轉(zhuǎn)發(fā)
2018-07-11: 初稿基协,感覺自己的Android的水平還是差了很多很多啊,參考博主coder-pig
2018-07-13: 腦殼痛剑按,就讓自己有理由休息了

0. 引言

  • Android中跨進(jìn)程通信AIDL的一些概念
  • 本節(jié)對(duì)應(yīng)的官方文檔:Binder

1. Bander機(jī)制

1.1 IBander和Bander
  • IBinder是遠(yuǎn)程對(duì)象的基本接口疾就,是為了高性能而設(shè)計(jì)的輕量級(jí)遠(yuǎn)程調(diào)用機(jī)制的核心部分。但他不僅用于遠(yuǎn)程調(diào)用艺蝴,也用于進(jìn)程內(nèi)調(diào)用猬腰。該接口定義了與遠(yuǎn)程對(duì)象間交互的協(xié)議。但不要直接實(shí)現(xiàn)這個(gè)接口猜敢,而是繼承(extends)Binder姑荷。
  • IBinder主要的API是transact(),與之對(duì)應(yīng)的API是Binder.onTransact()缩擂。通過前者鼠冕,你能 想遠(yuǎn)程IBinder對(duì)象發(fā)送發(fā)出調(diào)用,后者使你的遠(yuǎn)程對(duì)象能夠響應(yīng)接收到的調(diào)用胯盯。IBinder的API都是 Syncronous(同步)執(zhí)行的供鸠,比如transact()直到對(duì)方的Binder.onTransact()方法調(diào)用玩 后才返回。 調(diào)用發(fā)生在進(jìn)程內(nèi)時(shí)無疑是這樣的陨闹,而在進(jìn)程間時(shí)楞捂,在IPC的幫助下薄坏,也是同樣的效果。
  • 通過transact()發(fā)送的數(shù)據(jù)是Parcel寨闹,Parcel是一種一般的緩沖區(qū)胶坠,除了有數(shù)據(jù)外還帶有 一些描述它內(nèi)容的元數(shù)據(jù)。元數(shù)據(jù)用于管理IBinder對(duì)象的引用繁堡,這樣就能在緩沖區(qū)從一個(gè)進(jìn)程移動(dòng) 到另一個(gè)進(jìn)程時(shí)保存這些引用沈善。這樣就保證了當(dāng)一個(gè)IBinder被寫入到Parcel并發(fā)送到另一個(gè)進(jìn)程中, 如果另一個(gè)進(jìn)程把同一個(gè)IBinder的引用回發(fā)到原來的進(jìn)程椭蹄,那么這個(gè)原來的進(jìn)程就能接收到發(fā)出的 那個(gè)IBinder的引用闻牡。這種機(jī)制使IBinder和Binder像唯一標(biāo)志符那樣在進(jìn)程間管理。
  • 系統(tǒng)為每個(gè)進(jìn)程維護(hù)一個(gè)存放交互線程的線程池绳矩。這些交互線程用于派送所有從另外進(jìn)程發(fā)來的IPC 調(diào)用罩润。例如:當(dāng)一個(gè)IPC從進(jìn)程A發(fā)到進(jìn)程B,A中那個(gè)發(fā)出調(diào)用的線程(這個(gè)應(yīng)該不在線程池中)就阻塞 在transact()中了翼馆。進(jìn)程B中的交互線程池中的一個(gè)線程接收了這個(gè)調(diào)用割以,它調(diào)用 Binder.onTransact(),完成后用一個(gè)Parcel來做為結(jié)果返回应媚。然后進(jìn)程A中的那個(gè)等待的線程在 收到返回的Parcel后得以繼續(xù)執(zhí)行严沥。實(shí)際上,另一個(gè)進(jìn)程看起來就像是當(dāng)前進(jìn)程的一個(gè)線程中姜, 但不是當(dāng)前進(jìn)程創(chuàng)建的消玄。
  • Binder機(jī)制還支持進(jìn)程間的遞歸調(diào)用。例如丢胚,進(jìn)程A執(zhí)行自己的IBinder的transact()調(diào)用進(jìn)程B 的Binder莱找,而進(jìn)程B在其Binder.onTransact()中又用transact()向進(jìn)程A發(fā)起調(diào)用,那么進(jìn)程A 在等待它發(fā)出的調(diào)用返回的同時(shí)嗜桌,還會(huì)用Binder.onTransact()響應(yīng)進(jìn)程B的transact()奥溺。 總之Binder造成的結(jié)果就是讓我們感覺到跨進(jìn)程的調(diào)用與進(jìn)程內(nèi)的調(diào)用沒什么區(qū)別。
  • 當(dāng)操作遠(yuǎn)程對(duì)象時(shí)骨宠,你經(jīng)常需要查看它們是否有效浮定,有三種方法可以使用:
    1. transact()方法將在IBinder所在的進(jìn)程不存在時(shí)拋出RemoteException異常。
    2. 如果目標(biāo)進(jìn)程不存在层亿,那么調(diào)用pingBinder()時(shí)返回false桦卒。
    3. 可以用linkToDeath()方法向IBinder注冊(cè)一個(gè)IBinder.DeathRecipient, 在IBinder代表的進(jìn)程退出時(shí)被調(diào)用匿又。

嗯方灾,總體上來說

  • IBinder是Android給我們提供的一個(gè)進(jìn)程間通信的一個(gè)接口,而我們一般是不直接實(shí)現(xiàn)這個(gè)接口的, 而是通過繼承Binder類來實(shí)現(xiàn)進(jìn)程間通信裕偿!是Android中實(shí)現(xiàn)IPC(進(jìn)程間通信)的一種方式洞慎!
1.2 Binder機(jī)制
  • Android中的Binder機(jī)制由一系列組件構(gòu)成:Client、Server嘿棘、ServiceManager劲腿、Binder驅(qū)動(dòng)程序
  • 調(diào)用流程大致如下


流程解析

  1. Client調(diào)用某個(gè)代理接口中的方法時(shí),代理接口的方法會(huì)將Client傳遞的參數(shù)打包成Parcel對(duì)象鸟妙;
  2. 然后代理接口把該P(yáng)arcel對(duì)象發(fā)送給內(nèi)核中的Binder driver焦人;;
  3. 然后Server會(huì)讀取Binder Driver中的請(qǐng)求數(shù)據(jù)重父,假如是發(fā)送給自己的花椭,解包Parcel對(duì)象, 處理并將結(jié)果返回房午;
  4. PS:代理接口中的定義的方法和Server中定義的方法是一一對(duì)應(yīng)的矿辽, 另外,整個(gè)調(diào)用過程是一個(gè)同步的歪沃,即Server在處理時(shí)嗦锐,Client會(huì)被Block(鎖)住! 而這里說的代理接口的定義就是等下要說的AIDL(Android接口描述語言)嫌松!
1.3 為何Android會(huì)使用Binder機(jī)制來實(shí)現(xiàn)進(jìn)程間的通信
  1. 可靠性:在移動(dòng)設(shè)備上沪曙,通常采用基于Client-Server的通信方式來實(shí)現(xiàn)互聯(lián)網(wǎng)與設(shè)備間的內(nèi)部通信。目前l(fā)inux支持IPC包括傳統(tǒng)的管道萎羔,System V IPC液走,即消息隊(duì)列/共享內(nèi)存/信號(hào)量,以及socket中只有socket支持Client-Server的通信方式贾陷。Android系統(tǒng)為開發(fā)者提供了豐富進(jìn)程間通信的功能接口缘眶,媒體播放,傳感器髓废,無線傳輸巷懈。這些功能都由不同的server來管理。開發(fā)都只關(guān)心將自己應(yīng)用程序的client與server的通信建立起來便可以使用這個(gè)服務(wù)慌洪。毫無疑問顶燕,如若在底層架設(shè)一套協(xié)議來實(shí)現(xiàn)Client-Server通信,增加了系統(tǒng)的復(fù)雜性冈爹。在資源有限的手機(jī) 上來實(shí)現(xiàn)這種復(fù)雜的環(huán)境涌攻,可靠性難以保證。
  2. 傳輸性能:socket主要用于跨網(wǎng)絡(luò)的進(jìn)程間通信和本機(jī)上進(jìn)程間的通信频伤,但傳輸效率低恳谎,開銷大。消息隊(duì)列和管道采用存儲(chǔ)-轉(zhuǎn)發(fā)方式憋肖,即數(shù)據(jù)先從發(fā)送方緩存區(qū)拷貝到內(nèi)核開辟的一塊緩存區(qū)中因痛,然后從內(nèi)核緩存區(qū)拷貝到接收方緩存區(qū)婚苹,其過程至少有兩次拷貝。雖然共享內(nèi)存無需拷貝婚肆,但控制復(fù)雜租副。比較各種IPC方式的數(shù)據(jù)拷貝次數(shù)。共享內(nèi)存:0次较性。Binder:1次用僧。Socket/管道/消息隊(duì)列:2次。
  3. 安全性:Android是一個(gè)開放式的平臺(tái)赞咙,所以確保應(yīng)用程序安全是很重要的责循。Android對(duì)每一個(gè)安裝應(yīng)用都分配了UID/PID,其中進(jìn)程的UID是可用來鑒別進(jìn)程身份。傳統(tǒng)的只能由用戶在數(shù)據(jù)包里填寫UID/PID攀操,這樣不可靠院仿,容易被惡意程序利用。而我們要求由內(nèi)核來添加可靠的UID速和。 所以歹垫,出于可靠性、傳輸性颠放、安全性排惨。android建立了一套新的進(jìn)程間通信方式。 ——摘自:Android中的Binder機(jī)制的簡(jiǎn)要理解

Binder的好處

  • 我們無需關(guān)心底層如何實(shí)現(xiàn)碰凶,只需按照AIDL的規(guī)則暮芭,自定義一個(gè)接口文件, 然后調(diào)用調(diào)用接口中的方法欲低,就可以完成兩個(gè)進(jìn)程間的通信了辕宏!

2. AIDL使用解析

2.1 什么是AIDL
  • IPC這個(gè)名詞,他的全名叫做:跨進(jìn)程通信(interprocess communication)
  • Android系統(tǒng)中,個(gè)個(gè)應(yīng)用程序都運(yùn)行在自己的進(jìn)程中,進(jìn)程之間一般是無法直接進(jìn)行數(shù)據(jù)交換的, 而為了實(shí)現(xiàn)跨進(jìn)程砾莱,Android給我們提供了上面說的Binder機(jī)制
  • 這個(gè)機(jī)制使用的接口語言就是: AIDL(Android Interface Definition Language)瑞筐,他的語法很簡(jiǎn)單,而這種接口語言并非真正的編程語言腊瑟,只是定義兩個(gè)進(jìn)程間的通信接口而已聚假。
  • 而生成符合通信協(xié)議的Java代碼則是由Android SDK的 platform-tools目錄下的aidl.exe工具生成,生成對(duì)應(yīng)的接口文件在:gen目錄下扫步,一般是:Xxx.java的接口魔策!
  • 而在該接口中包含一個(gè)Stub的內(nèi)部類,該類中實(shí)現(xiàn)了在該類中實(shí)現(xiàn)了IBinder接口與自定義的通信接口, 這個(gè)類將會(huì)作為遠(yuǎn)程Service的回調(diào)類——實(shí)現(xiàn)了IBinder接口,所以可作為Service的onBind( )方法的返回值河胎。
2.2 AIDL實(shí)現(xiàn)兩個(gè)進(jìn)程間通信

編寫AIDL的一些注意事項(xiàng):

  1. 接口名詞需要與aidl文件名相同
  2. 接口和方法前面不要加訪問權(quán)限修飾符:public ,private,protected等闯袒,也不能用static final!
  3. AIDL默認(rèn)支持的類型包括Java基本類型,String,List政敢,Map其徙,CharSequence,除此之外的其他類型都 需要import聲明喷户,對(duì)于使用自定義類型作為參數(shù)或者返回值唾那,自定義類型需要實(shí)現(xiàn)Parcelable接口, 詳情請(qǐng)看后面的傳遞復(fù)雜數(shù)據(jù)類型
  4. 自定義類型和AIDL生成的其它接口類型在aidl描述文件中褪尝,應(yīng)該顯式import闹获,即便在該類和定義 的包在同一個(gè)包中。自定義類型應(yīng)該可以轉(zhuǎn)化為Json串傳輸河哑。

AS中可以直接創(chuàng)建AIDL文件

  • 然后就直接創(chuàng)建了AIDL的文件夾和AIDL文件了避诽,建立之前給文件取一個(gè)名,然后重新編譯下就好了璃谨。

一沙庐、服務(wù)端

  1. 創(chuàng)建AIDL文件
    IPerson.aidl
package com.jay.aidl;

interface IPerson {
    String queryPerson(int num);
}

IPerson.java

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: C:\\Code\\ASCode\\AIDLServer\\app\\src\\main\\aidl\\com\\jay\\aidl\\IPerson.aidl
 */
package com.jay.aidl;
public interface IPerson extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.jay.aidl.IPerson
{
private static final java.lang.String DESCRIPTOR = "com.jay.aidl.IPerson";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.jay.aidl.IPerson interface,
 * generating a proxy if needed.
 */
public static com.jay.aidl.IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jay.aidl.IPerson))) {
return ((com.jay.aidl.IPerson)iin);
}
return new com.jay.aidl.IPerson.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
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_queryPerson:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.queryPerson(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jay.aidl.IPerson
{
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.lang.String queryPerson(int num) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num);
mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String queryPerson(int num) throws android.os.RemoteException;
}
  • 上面的文件很復(fù)雜,我們只關(guān)注的是asInterface(IBinder)和我們定義的接口中的queryPerson()方法!
  • 這個(gè)方法會(huì)把IBander類型的對(duì)象轉(zhuǎn)換成IPerson類型的佳吞,必要時(shí)生成一個(gè)代理對(duì)象返回結(jié)果
  1. 自定義我們的Service類拱雏,完成以下操作
    1)繼承Service類,同時(shí)也自定義了一個(gè)PersonQueryBinder類來繼承IPerson.Stub類底扳,就是實(shí)現(xiàn)了IPerson接口和IBander接口
    2)實(shí)例化自定義的Stub類铸抑,并重寫Service的onBind方法,返回一個(gè)binder對(duì)象花盐!

AIDLService.java

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.jay.aidl.IPerson.Stub;

/**
 * Created by Jay on 2015/8/18 0018.
 */
public class AIDLService extends Service {

    private IBinder binder = new PersonQueryBinder();
    private String[] names = {"B神","艸神","基神","J神","翔神"};

    private String query(int num)
    {
        if(num > 0 && num < 6){
            return names[num - 1];
        }
        return null;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    private final class PersonQueryBinder extends Stub{
        @Override
        public String queryPerson(int num) throws RemoteException {
            return query(num);
        }
    }
}
  1. 在AndroidMainfest.xml中注冊(cè)Service
<service android:name=".AIDLService">
       <intent-filter>
              <action android:name="android.intent.action.AIDLService" />
              <category android:name="android.intent.category.DEFAULT" />
       </intent-filter>
</service>

二羡滑、客戶端

  • 直接把服務(wù)端的那個(gè)aidl文件復(fù)制過來菇爪,然后我們直接在MainActivity中完成算芯,和綁定本地Service的操作
  • 有點(diǎn)類似,流程如下:
    1)自定義PersonConnection類實(shí)現(xiàn)ServiceConnection接口
    2)以PersonConnection對(duì)象作為參數(shù),調(diào)用bindService綁定遠(yuǎn)程Service
    bindService(service,conn,BIND_AUTO_CREATE);
    ps:第三個(gè)參數(shù)是設(shè)置如果服務(wù)沒有啟動(dòng)的話,自動(dòng)創(chuàng)建
    3)和本地Service不同凳宙,綁定遠(yuǎn)程Service的ServiceConnection并不能直接獲取Service的onBind( )方法
  • 返回的IBinder對(duì)象熙揍,只能返回onBind( )方法所返回的代理對(duì)象,需要做如下處理:
    iPerson = IPerson.Stub.asInterface(service);
    再接著完成初始化,以及按鈕事件等就可以了
    具體代碼如下:
    MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText edit_num;
    private Button btn_query;
    private TextView txt_name;
    private IPerson iPerson;
    private PersonConnection conn = new PersonConnection();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //綁定遠(yuǎn)程Service氏涩, android6.0之后采用顯示通信
        Intent serviceIntent = new Intent();
        serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));

        bindService(service, conn, BIND_AUTO_CREATE);
        btn_query.setOnClickListener(this);
    }

    private void bindViews() {
        edit_num = (EditText) findViewById(R.id.edit_num);
        btn_query = (Button) findViewById(R.id.btn_query);
        txt_name = (TextView) findViewById(R.id.txt_name);
    }

    @Override
    public void onClick(View v) {
        String number = edit_num.getText().toString();
        int num = Integer.valueOf(number);
        try {
            txt_name.setText(iPerson.queryPerson(num));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        edit_num.setText("");
    }

    private final class PersonConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            iPerson = IPerson.Stub.asInterface(service);
        }
        public void onServiceDisconnected(ComponentName name) {
            iPerson = null;
        }
    }
}

接下來先啟動(dòng)AIDLServivce届囚,然后再啟動(dòng)AIDLClient,輸入查詢序號(hào)是尖,即可獲得對(duì)應(yīng)姓名意系! 當(dāng)然也可以直接啟動(dòng)AIDLClient,也會(huì)獲得同樣效果:
效果圖如下:

2.4 傳遞復(fù)雜數(shù)據(jù)的AIDL通信
  • 上面的例子我們傳遞的只是要給int類型的參數(shù)饺汹,然后服務(wù)端返回一個(gè)String類型的參數(shù)蛔添,看似滿足 我們的基本需求,不過實(shí)際開發(fā)中,我們可能需要考慮傳遞復(fù)雜數(shù)據(jù)類型的情況迎瞧!下面我們來學(xué)習(xí)下 如何向服務(wù)端傳遞復(fù)雜數(shù)據(jù)類型的數(shù)據(jù)夸溶!開始之前我們先來了解Parcelable接口!

Parcelable接口簡(jiǎn)介:

  • Parcelable主要是用于序列化凶硅,除了這個(gè)還有一個(gè)Serializable缝裁,也是用于序列化的,其中Parcelable更加輕量級(jí)足绅,速度更快捷绑,就是寫起來比較麻煩,當(dāng)然還有as的插件來完成序列化氢妈,比如Gson
  1. 首先胎食,需要實(shí)現(xiàn)writeToParcel和readFromParcel方法寫入方法將對(duì)象寫入到包裹parcel中,而讀取方法則從包裹中讀取對(duì)象允懂,請(qǐng)注意厕怜,寫入屬性順序與讀取順序相同
  2. 接著,需要在該類中添加一個(gè)名為CREATOR的static final屬性蕾总,改屬性需要實(shí)現(xiàn):android.os.Parcelable.Creator接口
  3. 接著粥航,需要從寫接口中的兩個(gè)方法,createFromParcel(Parcel source)方法生百,實(shí)現(xiàn)source創(chuàng)建出JavaBean實(shí)例的功能 newArray(int size)递雀,創(chuàng)建一個(gè)類型為T,長(zhǎng)度為size的數(shù)組蚀浆,只有一個(gè)簡(jiǎn)單的return new T[size]缀程,這里的T是Person類
  4. 最后,discribeContents():嗯市俊,這個(gè)直接返回0即可
  5. 另外杨凑,除了String和CharSequence以外,其余俊宇要一個(gè)方向指示符摆昧。方向指示符包括in撩满、out和inout。in表示由客戶端設(shè)置绅你,out表示由服務(wù)端設(shè)置伺帘,inout表示客戶端和服務(wù)端都設(shè)置了該值。

代碼示例

  • 自定義了兩種對(duì)象:Person與Salary忌锯,Person作為調(diào)用遠(yuǎn)程的Serviec的參數(shù)伪嫁,Salary作為返回值!那么首先就要做的是創(chuàng)建Person與Salary類偶垮,同時(shí)實(shí)現(xiàn)Parcelable接口
    1. 服務(wù)端
  1. 創(chuàng)建Person.aidl和Salary.aidl的文件张咳,因?yàn)樗麄冃枰獙?shí)現(xiàn)Parcelable接口驹吮,所以就下面一條語句:
Person.aidl:     parcelable Person; 
Salary.aidl:     parcelable Salary; 
  1. 分別建立Person類與Salary類,需實(shí)現(xiàn)Parcelable接口晶伦,重寫對(duì)應(yīng)的方法!
    因?yàn)槲覀兒竺媸歉鶕?jù)Person對(duì)象來獲取Map集合中的數(shù)據(jù),所以Person.java中我們重寫了hashcode和equals 的方法;而Salary類則不需要!
public class Person implements Parcelable{

    private Integer id;
    private String name;

    public Person() {}

    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

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

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

    public String getName() {
        return name;
    }


    //實(shí)現(xiàn)Parcelable必須實(shí)現(xiàn)的方法,不知道拿來干嘛的,直接返回0就行了
    @Override
    public int describeContents() {
        return 0;
    }


    //寫入數(shù)據(jù)到Parcel中的方法
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //把對(duì)象所包含的數(shù)據(jù)寫入到parcel中
        dest.writeInt(id);
        dest.writeString(name);
    }

    //必須提供一個(gè)名為CREATOR的static final屬性 該屬性需要實(shí)現(xiàn)
    //android.os.Parcelable.Creator<T>接口
    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
        //從Parcel中讀取數(shù)據(jù),返回Person對(duì)象
        @Override
        public Person createFromParcel(Parcel source) {
            return new Person(source.readInt(),source.readString());
        }
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    //因?yàn)槲覀兗先〕鲈氐臅r(shí)候是根據(jù)Person對(duì)象來取得,所以比較麻煩,
    //需要我們重寫hashCode()和equals()方法
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (name == null)
        {
            if (other.name != null)
                return false;
        }
        else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Salary類

public class Salary implements Parcelable {

    private String type;
    private Integer salary;

    public Salary() {
    }

    public Salary(String type, Integer salary) {
        this.type = type;
        this.salary = salary;
    }

    public String getType() {
        return type;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(type);
        dest.writeInt(salary);
    }

    public static final Parcelable.Creator<Salary> CREATOR = new Parcelable.Creator<Salary>() {
        //從Parcel中讀取數(shù)據(jù),返回Person對(duì)象
        @Override
        public Salary createFromParcel(Parcel source) {
            return new Salary(source.readString(), source.readInt());
        }

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

    public String toString() {
        return "工作:" + type + "    薪水: " + salary;
    }
}

3碟狞、創(chuàng)建一個(gè)ISalary.aidl的文件,在里面寫一個(gè)簡(jiǎn)單的獲取工資信息的方法:

package com.jay.example.aidl;
  
import com.jay.example.aidl.Salary;  
import com.jay.example.aidl.Person;  
interface ISalary  
{  
    //定義一個(gè)Person對(duì)象作為傳入?yún)?shù)  
    //接口中定義方法時(shí),需要制定新參的傳遞模式,這里是傳入,所以前面有一個(gè)in  
    Salary getMsg(in Person owner);  
}  

ps:這里可以記得如果使用的是自定義的數(shù)據(jù)類型的話,需要import哦;榕恪W逦帧!切記C诓巍4嘌汀!
Step 4:核心Service的編寫: 定義一個(gè)SalaryBinder類繼承Stub,從而實(shí)現(xiàn)ISalary和IBinder接口;定義一個(gè)存儲(chǔ)信息的Map集合! 重新onBind方法,返回SalaryBinder類的對(duì)象實(shí)例!
AidlService.java

public class AidlService extends Service {  
  
    private SalaryBinder salaryBinder;  
    private static Map<Person,Salary> ss = new HashMap<Person, Salary>();  
    //初始化Map集合,這里在靜態(tài)代碼塊中進(jìn)行初始化,當(dāng)然你可也以在構(gòu)造方法中完成初始化  
    static  
    {  
        ss.put(new Person(1, "Jay"), new Salary("碼農(nóng)", 2000));  
        ss.put(new Person(2, "GEM"), new Salary("歌手", 20000));  
        ss.put(new Person(3, "XM"), new Salary("學(xué)生", 20));  
        ss.put(new Person(4, "MrWang"), new Salary("老師", 2000));  
    }  
      
      
    @Override  
    public void onCreate() {  
        super.onCreate();  
        salaryBinder = new SalaryBinder();  
    }  
      
    @Override  
    public IBinder onBind(Intent intent) {  
        return salaryBinder;  
    }  
  
      
    //同樣是繼承Stub,即同時(shí)實(shí)現(xiàn)ISalary接口和IBinder接口  
    public class SalaryBinder extends Stub  
    {  
        @Override  
        public Salary getMsg(Person owner) throws RemoteException {  
            return ss.get(owner);  
        }  
    }  
      
    @Override  
    public void onDestroy() {  
        System.out.println("服務(wù)結(jié)束沽一!");  
        super.onDestroy();  
    }  
}  

注冊(cè)下Service:

<service android:name=".AidlService">  
    <intent-filter>    
        <action android:name="android.intent.action.AIDLService" />  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>    
</service>

2盖溺、客戶端編寫

Step 1:把服務(wù)端的AIDL文件拷貝下,拷貝后目錄如下:


Step 2:編寫簡(jiǎn)單的布局,再接著就是核心MainActvitiy的實(shí)現(xiàn)了 定義一個(gè)ServciceConnection對(duì)象,重寫對(duì)應(yīng)方法,和前面的普通數(shù)據(jù)的類似 再接著在bindService,然后再Button的點(diǎn)擊事件中獲取Salary對(duì)象并顯示出來铣缠!
MainActivity.java

public class MainActivity extends Activity {  
  
    private ISalary salaryService;  
    private Button btnquery;  
    private EditText editname;  
    private TextView textshow;  
    private ServiceConnection conn = new ServiceConnection() {  
          
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            salaryService = null;  
        }  
          
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            //返回的是代理對(duì)象,要調(diào)用這個(gè)方法哦!  
            salaryService = ISalary.Stub.asInterface(service);  
        }  
    };  
      
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        btnquery = (Button) findViewById(R.id.btnquery);  
        editname = (EditText) findViewById(R.id.editname);  
        textshow = (TextView) findViewById(R.id.textshow);  
          
        Intent it = new Intent();  
        it.setAction("com.jay.aidl.AIDL_SERVICE");  
        bindService(it, conn, Service.BIND_AUTO_CREATE);  
          
        btnquery.setOnClickListener(new OnClickListener() {           
            @Override  
            public void onClick(View v) {  
                try  
                {  
                    String name = editname.getText().toString();  
                    Salary salary = salaryService.getMsg(new Person(1,name));  
                    textshow.setText(name + salary.toString());  
                }catch(RemoteException e){e.printStackTrace();}  
            }  
        });  
          
    }  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        this.unbindService(conn);  
    }  
} 

運(yùn)行截圖:

2.5 注意事項(xiàng)
  1. 經(jīng)過測(cè)試烘嘱,上面的例子可以寫在一個(gè)Demo或者兩個(gè)Demo里面,無論寫在一個(gè)或者兩個(gè)里面蝗蛙,其中AIDL的部分必須復(fù)制


    我是寫在了兩個(gè)demo中蝇庭,測(cè)試了很久都沒成功,最開始client的IPerson的AIDL的接口是存放在com.project.ankie.aidlclientdemo包下捡硅,而不是通過service端復(fù)制過來的哮内,所以會(huì)出現(xiàn)錯(cuò)誤,經(jīng)查是因?yàn)椴皇窃谕粋€(gè)包下所引發(fā)的問題壮韭!

  2. 參考了一下別的工程北发,主要是在主工程下的其他的library中添加的服務(wù),然后通過和服務(wù)綁定喷屋,然后調(diào)用AIDL接口進(jìn)行通信琳拨,此舉主要是這個(gè)AIDL只有一個(gè)就可以了啊,因?yàn)榭梢詫ntent的連接綁定bindService和AIDL接口寫在同一個(gè)library的包下逼蒙,就不會(huì)產(chǎn)生上面我遇到的錯(cuò)誤了从绘,但是兩個(gè)app之間的通信要么采用上面的復(fù)制連包和AIDL寄疏,要么采用其他的方式是牢。

  3. Android在6.0之后必須用顯示了,所以不能Intent然后再setAction了陕截,需要直接指定包了驳棱,由于上面的例子是不同的app,所以采用的是setComponent方法

Intent serviceIntent = new Intent();
// CompontentName 的方法前面是服務(wù)的 包名农曲、后面是class的名
serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));
final boolean b = bindService(serviceIntent, conn, BIND_AUTO_CREATE);
// 可以通過返回值來判斷是否綁定成功社搅,也可以在Service的onBind方法那邊打log查看
Log.e(TAG, "onCreate: " +  " b: " + b );

3. 總結(jié)

  1. 總體思想就是Activity與Service之間的通信驻债,通信依托AIDL的形式進(jìn)行;
  2. 進(jìn)行通信的時(shí)候形葬,在啟動(dòng)Service的方式就應(yīng)該是通過Activity綁定服務(wù)的形式了合呐,而不是StartService的形式了,因?yàn)镾ervice一旦啟動(dòng)之后笙以,可以由其他的Activity來綁定淌实;
  3. 綁定Service的主要方法為bindService(intent,conn, BIND_AUTO_CREATE)方法猖腕,其中intent現(xiàn)在只能是顯示綁定了拆祈,conn是一個(gè)new ServiceConnection的回調(diào),最后一個(gè)參數(shù)是自動(dòng)綁定倘感,直接寫就完了放坏;
  4. 在回調(diào)里由返回的IBand的類型的Service檬姥,然后通過ISalary.Stub.asInterface(service); 方法甚淡,將service轉(zhuǎn)化為AIDL的接口的實(shí)例對(duì)象,這樣就可以調(diào)用AIDL中的接口了
  5. ADIL模式只是支持現(xiàn)有的基本的數(shù)據(jù)模型葵擎,若復(fù)雜的數(shù)據(jù)模型需要序列化蜡豹;
  6. 對(duì)于復(fù)雜類型模型的傳輸互亮,也可以通過將復(fù)雜模型轉(zhuǎn)化為Json串的類型,然后傳輸余素,在接收端將該類型轉(zhuǎn)化為相同過得模型豹休,即可完成序列化和反序列化的形式。
  7. 上文的例子沒有驗(yàn)證桨吊,也只是對(duì)Service的不同情況的分析威根。還是為具體到Service的實(shí)現(xiàn)機(jī)制還待研究。

參考文獻(xiàn)

  1. Android開發(fā):什么是IBinder
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末视乐,一起剝皮案震驚了整個(gè)濱河市洛搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佑淀,老刑警劉巖留美,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伸刃,居然都是意外死亡谎砾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門捧颅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來景图,“玉大人,你說我怎么就攤上這事碉哑≈勘遥” “怎么了亮蒋?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)妆毕。 經(jīng)常有香客問我慎玖,道長(zhǎng),這世上最難降的妖魔是什么笛粘? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任凄吏,我火速辦了婚禮,結(jié)果婚禮上闰蛔,老公的妹妹穿的比我還像新娘痕钢。我一直安慰自己,他們只是感情好序六,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布任连。 她就那樣靜靜地躺著,像睡著了一般例诀。 火紅的嫁衣襯著肌膚如雪随抠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天繁涂,我揣著相機(jī)與錄音拱她,去河邊找鬼。 笑死扔罪,一個(gè)胖子當(dāng)著我的面吹牛秉沼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矿酵,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唬复,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了全肮?” 一聲冷哼從身側(cè)響起敞咧,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辜腺,沒想到半個(gè)月后休建,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡评疗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年测砂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壤巷。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邑彪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胧华,到底是詐尸還是另有隱情寄症,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布矩动,位于F島的核電站有巧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悲没。R本人自食惡果不足惜篮迎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望示姿。 院中可真熱鬧甜橱,春花似錦、人聲如沸栈戳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽子檀。三九已至镊掖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褂痰,已是汗流浹背亩进。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缩歪,地道東北人归薛。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匪蝙,于是被迫代替她去往敵國和親苟翻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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