Android進程間通信之Service篇,Messenger與AIDL

本文Demo見:https://github.com/w1374720640/IPCThroughServices

結合Demo閱讀本文效果更好

利用Service進行進程間通信有兩種方式,分別是Messenger和AIDL,Messenger底層是基于AIDL的封裝,使用更加簡潔高效,無需考慮并發問題,只能串行通信,有并發需求的只能用AIDL,不能用Messenger,一般情況下使用Messenger即可滿足日常需求。Messenger和AIDL跨進程通信只能傳遞基本數據類型及實現Parcelable接口的類。

通常提供服務的進程稱為服務端,獲取服務的稱為客戶端,客戶端通過bindService()的方式綁定服務端,獲取IBinder的實例。本文創建了兩個項目,包名分別為com.example.servicecom.example.client,對應服務端和客戶端,下文不再重復說明。

Messenger

原理

查看Messenger源碼可以發現,Messenger包含一個IMessenger的成員變量mTarget,通過mTarget可以向Handler傳遞Message消息。獲取mTarget對象有兩種方式,一種是在構造器中利用Handler獲取mTarget的實例:

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

另一種方法是在構造器中通過IBinder對象獲取mTarget的實例:

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

注:可能有的同學發現Android sdk中沒有IMessenger類,顯示紅色字體,那是因為IMessenger是一個AIDL文件,完整路徑為android.os.IMessenger.aidl,只有一個抽象方法void send(in Message msg);(暫時忽略Message前面的in),AIDL文件編譯后會生成同名的Java文件,Android sdk中不包含AIDL文件及編譯后生成的臨時文件,所以系統找不到IMessenger類,下文會詳細介紹相關知識。

服務端創建Messenger對象用第一個構造器,客戶端綁定服務端時用第二個構造器獲取Messenger對象,兩個進程間傳遞的對象為Messenger對象中的mTarget變量,通過mTarget對象可以跨進程發送Message給Handler:

//Messenger.java
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

使用方法

模擬客戶端從服務端獲取一個100以內的隨機數。

在服務端新建一個RemoteMessengerService繼承Service,并在AndroidManifest.xml中注冊隱式啟動方式

<service android:name=".RemoteMessengerService">
    <intent-filter>
        <action android:name="com.example.service.RemoteMessengerService"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

未指定隱式啟動的要加屬性android:exported="true"表示可以被其他進程啟動,添加<intent-filter>標簽后默認為true。

在RemoteMessengerService中創建Handler對象mRemoteHandler,調用Messenger的第一個構造器,利用mRemoteHandler創建Messenger對象:

Messenger mRemoteMessenger = new Messenger(mRemoteHandler);

重寫Service的onBind方法,返回Messenger中的mTarget變量:

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mRemoteMessenger.getBinder();
}

在客戶端新建一個MessengerActivity,啟動時綁定服務端:

Intent intent = new Intent();
intent.setAction("com.example.service.RemoteMessengerService");
//Android 5.0以上需要設置包名
intent.setPackage("com.example.service");
bindService(intent,mConnection,BIND_AUTO_CREATE);

綁定成功后根據服務端返回的IBinder對象調用Messenger的第二個構造器,創建Messenger對象,通過Messenger對象可以向服務端發送消息:

ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteMessenger = new Messenger(service);
        isConnect = true;
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        isConnect = false;
    }
};

向服務端發送消息:

Message message = Message.obtain();
message.what = 0;
try {
    mRemoteMessenger.send(message);
} catch (RemoteException e) {
   e.printStackTrace();
}

然后在服務端注冊的Handler就可以接收到客戶端發送的Message了。

一個簡單的進程間通信就基本完成了,這時只能由客戶端向服務端發送消息,服務端無法向客戶端傳遞數據,要解決這個問題,需要在客戶端新建一個Handler及Messenger,在發送消息時將Messenger對象傳遞給Message的replyTo變量,服務端的Handler收到客戶端的Message后,獲取replyTo變量,通過獲取到的Messenger對象向客戶端發送消息。

修改后的客戶端代碼:

//MessengerActivity.java
Handler mClientHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 0:
                Log.d(TAG,"Client receive message:" + msg.arg1);
                break;
            default:
                break;
        }
    }
};
private Messenger mClientMessenger = new Messenger(mClientHandler);
private void sendMessage(){
    Log.d(TAG,"Client sendMessage()");
    if (!isConnect) return;
    Message message = Message.obtain();
    message.what = 0;
//    將客戶端的Messenger對象傳遞到服務端,
//    不設置則只能單向通信,服務端無法向客戶端傳遞信息
    message.replyTo = mClientMessenger;
    try {
//       向服務端發送消息
        mRemoteMessenger.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

修改后的服務端代碼:

//RemoteMessengerService.java
Handler mRemoteHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        Log.d(TAG,"Service receive client message,msg.what=" + msg.what);
        switch (msg.what){
            case 0:
                Message message = Message.obtain();
                message.what = 0;
                Random random = new Random();//獲取隨機數
                message.arg1 = random.nextInt(100);
//              msg的replyTo變量是客戶端生成的Messenger對象
//              如果為空則不能由服務端向客戶端傳遞消息,只能單向通信
                Messenger mClientMessenger = msg.replyTo;
                if (mClientMessenger == null) return;
                try {
//                  向客戶端回傳信息
                    mClientMessenger.send(message);
                    Log.d(TAG,"Service reply client message,random Num is:" + message.arg1);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
};

先運行服務端,再運行客戶端,客戶端向服務端發送消息后Log如下(注意包名):

Messenger測試Log.png

AIDL

在Android studio中AIDL文件位于app/src/main/aidl目錄下,客戶端與服務端AIDL文件相同,包名為服務端包名,只能傳遞基本數據類型及實現Parcelable接口的類。

AIDL文件實際為模板文件,用于生成復雜但套路固定的Java文件,生成的Java文件位于app/build/generated/source/aidl/debug/<packagemane>目錄下,通過Java文件實現IPC通信,生成的Java文件單獨使用具有同等效果,有興趣的可以看看具體的實現,這里不過多講解。

使用方式

模擬客戶端通過用戶ID查詢用戶信息、向服務端添加用戶、服務端調用客戶端方法實現雙向通信。

新建Person類:先設置Person類的成員變量,然后實現Parcelable接口,在類名上alt + enter兩次即可快捷生成Parcelable模板代碼(Parcelable接口具體使用方式自行google),如下所示:

/*
若Person.java文件放在aidl目錄下,需要在app/build.gradle的android標簽中添加
   sourceSets {
      main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
      }
    }
*/
public class Person implements Parcelable{
    private int id;
    private String name;
    private int age;
    private String phone;

    public Person(){
    }

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

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

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

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

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

    /**
     * 實現Parcelable接口時不會自動創建此方法,
     * 但如果aidl文件中Person類添加了out或inout標簽時必須手動實現此方法
     */
    public void readFromParcel(Parcel dest) {
        //注意,此處的讀值順序應當是和writeToParcel()方法中一致的
        id = dest.readInt();
        name = dest.readString();
        age = dest.readInt();
        phone = dest.readString();
    }

    @Override
    public String toString() {
        return "\"id=" + id + ",name=" + name + ",age=" + age + ",phone=" + phone + "\"";
    }

    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; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
}

需要注意的是,模板代碼并沒有生成readFromParcel()方法,需要我們按照writeToParcel()的寫入順序依次讀取參數,若未實現readFromParcel()方法,則aidl中只能用in修飾Person類,不能用outinout修飾,如上文中提到的IMessenger唯一抽象方法void send(in Message msg);。那三個修飾符有什么區別呢?以IMessenger的send方法為例:

  • 如果用in修飾,那msg對象為原對象的副本,msg值的變化不會影響原對象。
  • 如果用out修飾,無論msg傳入的值是什么,都會在方法內部創建一個新的對象,方法執行結束會將新對象寫入原msg對象,也就是說,無論你輸入什么都忽視,結束后再把你的值改掉(夠霸道的)。
  • 如果用inout修飾,則會復制輸入對象的值,方法執行完后再寫入原對象。
  • in修飾的對象執行效率最高,也最常用,outinout因為需要回寫,效率較低,盡量少用。
  • 傳遞基本數據類型、String、aidl文件不用也不能添加標簽,默認為in

創建AIDL文件:在服務端app/src/main目錄下新建aidl/com/example/service文件夾,然后鼠標點擊File>new>AIDL>AIDL File,輸入文件名,創建RemoteInterface.aidl文件:

// RemoteAidlInterface.aidl
package com.example.service;

//即使在同一個包下,也需要手動導入類
import com.example.service.ClientCallback;
import com.example.service.Person;

//編譯后生成的Java文件在app/build/generated/source/aidl/dubug/<packagename>目錄下
//服務端創建,客戶端調用
interface RemoteInterface {
//    根據用戶ID獲取用戶信息
    Person getPersonById(int id);
//    添加用戶,此處用in修飾
    void addPerson(in Person person);
//    向服務端注冊監聽
    void registClientCallback(ClientCallback callback);
//    取消注冊
    void unRegistClientCallback(ClientCallback callback);
}

ClientCallback.aidl文件如下:

// ClientAidlCallback.aidl
package com.example.service;

//客戶端向服務端注冊,客戶端創建,服務端調用
interface ClientCallback {
//    啟動客戶端
    void start();
//    停止客戶端
    void stop();
}

如果想在進程間傳遞對象,除了需要實現Parcelable接口外,還需要創建一個aidl文件申明該類可用于進程間通信:

// Person.aidl
package com.example.service;

//注意!不是用interface開頭,用parcelable開頭,p小寫,表示Person類可以進行進程間通信
//用interface開頭會生成同名的Java文件,用parcelable開頭不會,只有一個Person.java文件
parcelable Person;

創建完成,clean一下工程,如果編譯報錯說明aidl文件編寫有問題,比如是否正確導包(即使包名相同也需要手動導包),parcelable是否是小寫,傳遞序列化對象前必須加in|out|inout標記,重命名文件時其他地方不會自動替換新文件名,總之,編寫aidl文件時基本沒有任何提示,編譯不通過肯定是你aidl文件寫的有問題。編譯通過后檢查一下在app/build/generated/source/aidl/dubug/<packagename>目錄下是否生成相應Java文件。

復制aidl目錄下文件夾及所有文件到客戶端相同目錄下,編譯。

aidl目錄如下圖:

aidl目錄

編寫服務端及客戶端代碼

在服務端新建RemoteAidlService繼承Service,在AndroidManifest.xml文件中注冊:

<service android:name=".RemoteAidlService">
    <intent-filter>
        <action android:name="com.example.service.RemoteAidlService"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

創建RemoteInterface.Stub對象,RemoteInterface.Stub類是RemoteInterface.aidl文件編譯后生成的靜態內部類,創建RemoteInterface.Stub對象需要實現RemoteInterface.aidl中定義的抽象方法,重寫onBind()方法,返回創建的RemoteInterface.Stub對象:

public class RemoteAidlService extends Service {
    private static final String TAG = "AidlTest";
    private static final int START_ALL_CLIENT = 0;
    private static final int STOP_ALL_CLIENT = 1;
//    模擬服務端存儲客戶端傳遞的數據
    private List<Person> mPersonList = new ArrayList<>();
//    一個服務端可以對應多個客戶端,即包含多個ClientCallback對象,
//    使用RemoteCallbackList可以在客戶端意外斷開連接時移除ClientCallback,防止DeadObjectException
    private RemoteCallbackList<ClientCallback> mCallbackList = new RemoteCallbackList<>();
//    通過修改值確定是否在regist后start客戶端,默認不啟動
    private boolean isAutoStartAfterRegist = false;

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

    @Override
    public void onDestroy() {
        super.onDestroy();
//        服務結束是注意移除所有數據
        mCallbackList.kill();
    }

    /**
     * RemoteInterface.Stub為Android根據aidl文件生成的實現類,
     * 實現了RemoteInterface接口,間接實現了IBinder接口,
     * 客戶端綁定時將mRemoteInterface對象返回給客戶端,
     * 在服務端定義,在客戶端調用
     */
    private RemoteInterface.Stub mRemoteInterface = new RemoteInterface.Stub() {
        @Override
        public Person getPersonById(int id) throws RemoteException {
//            返回固定值
            Person person = new Person();
            person.setId(id);
            person.setName("小紅");
            person.setAge(18);
            person.setPhone("120");
            Log.d(TAG, "Service getPersonById()");
            return person;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            Log.d(TAG, "Service addPerson(),person="
                    + (person == null ? null : person.toString()));
        }

        @Override
        public void registClientCallback(ClientCallback callback) throws RemoteException {
//            向服務端注冊回調
            mCallbackList.register(callback);
            Log.d(TAG, "Service registClientCallback()");
            if (isAutoStartAfterRegist) {
                mHandler.sendEmptyMessageDelayed(START_ALL_CLIENT, 3 * 1000);
            }
        }

        @Override
        public void unRegistClientCallback(ClientCallback callback) throws RemoteException {
//            服務端取消注冊回調
            mCallbackList.unregister(callback);
            Log.d(TAG, "Service unRegistClientCallback()");
        }

    };

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case START_ALL_CLIENT:
                    startAllClient();
                    mHandler.sendEmptyMessageDelayed(STOP_ALL_CLIENT, 3 * 1000);
                    break;
                case STOP_ALL_CLIENT:
                    stopAllClient();
                    break;
            }
        }
    };

    /**
     * 調用所有客戶端的start()方法
     */
    public void startAllClient() {
        Log.d(TAG, "Service startAllClient()");
//        從列表中取數據時先調用beginBroadcast()方法獲取總數,循環取出數據后finishBroadcast()
        int size = mCallbackList.beginBroadcast();
        for (int i = 0;i < size;i++){
            try {
                mCallbackList.getBroadcastItem(i).start();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mCallbackList.finishBroadcast();
    }

    /**
     * 調用所有客戶端的stop()方法
     */
    public void stopAllClient() {
        Log.d(TAG, "Service stopAllClient()");
        int size = mCallbackList.beginBroadcast();
        for (int i = 0;i < size;i++){
            try {
                mCallbackList.getBroadcastItem(i).stop();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mCallbackList.finishBroadcast();
    }
}

客戶端創建AidlActivity,里邊有四個按鈕,分別對應RemoteInterface.aidl定義的四個方法:getPersonById()、addPerson()、registClientCallback()、unRegistClientCallback(),點擊按鈕調用相應方法。同時實現了ClientCallback.Stub類,向服務端注冊后服務端可以調用客戶端相應方法。

public class AidlActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "AidlTest";
    private boolean isConnect;

//    服務端的RemoteInterface對象,綁定服務時創建
    private RemoteInterface mRemoteInterface = null;

//    客戶端的ClientCallback對象
//    在服務端注冊后服務端可以調用客戶端方法
    private ClientCallback.Stub mClientCallback = new ClientCallback.Stub() {
        @Override
        public void start() throws RemoteException {
            Log.d(TAG, "Client start");
        }

        @Override
        public void stop() throws RemoteException {
            Log.d(TAG, "Client stop");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        findViewById(R.id.bt_get).setOnClickListener(this);
        findViewById(R.id.bt_add).setOnClickListener(this);
        findViewById(R.id.bt_regist).setOnClickListener(this);
        findViewById(R.id.bt_unregist).setOnClickListener(this);

        connectService();
    }

    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isConnect = true;
//            綁定服務后從服務端獲取RemoteInterface對象
            mRemoteInterface = RemoteInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isConnect = false;
          //            與服務端意外斷開時自動重連
            connectService();
        }
    };

    private void connectService() {
        Intent intent = new Intent();
        intent.setAction("com.example.service.RemoteAidlService");
//        Android 5.0以上需要設置包名
        intent.setPackage("com.example.service");
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    private void disConnectService() {
        unbindService(mConnection);
        isConnect = false;
    }

    @Override
    public void onClick(View v) {
        if (!isConnect) return;
        switch (v.getId()) {
            case R.id.bt_get:
                Person person = getPerson(10);
                Log.d(TAG, "Client getPerson return, person=" +
                        (person == null ? null : person.toString()));
                break;
            case R.id.bt_add:
                Person person1 = new Person();
                person1.setId(100);
                person1.setName("小花");
                person1.setAge(16);
                person1.setPhone("110");
                addPerson(person1);
                break;
            case R.id.bt_regist:
                registCallback();
                break;
            case R.id.bt_unregist:
                unRegistCallback();
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        disConnectService();
    }

    /**
     * 從服務端獲取數據
     */
    private Person getPerson(int id) {
        Log.d(TAG,"Client getPerson()");
        if (!isConnect) return null;
        Person person = null;
        try {
            person = mRemoteInterface.getPersonById(id);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return person;
    }

    /**
     * 向服務端添加數據
     */
    private void addPerson(Person person) {
        Log.d(TAG,"Client addPerson()");
        if (!isConnect) return;
        try {
            mRemoteInterface.addPerson(person);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 向服務端注冊回調,注冊后服務端才能調用客戶端方法
     */
    private void registCallback() {
        Log.d(TAG,"Client registCallback()");
        if (!isConnect) return;
        try {
            mRemoteInterface.registClientCallback(mClientCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 取消注冊
     */
    private void unRegistCallback() {
        Log.d(TAG,"Client unRegistCallback()");
        if (!isConnect) return;
        try {
            mRemoteInterface.unRegistClientCallback(mClientCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

先運行服務端,再運行客戶端,分別點擊客戶端的四個按鈕,觀察Log輸出如下

getPersonById()
addPerson()
registCallback()
unRegistCallback()

將的RemoteAidlService的isAutoStartAfterRegist屬性改為true后點擊注冊按鈕,Log如下,注冊后3秒自動調用客戶端的start()方法,再3秒后調用客戶端的stop()方法。

isAutoStartAfterRegist=true

結語

Demo見頂部鏈接,文章參考:

Android:學習AIDL,這一篇文章就夠了(上)

你真的理解AIDL中的in,out,inout么?

Android進程間通信之----Aidl傳遞對象

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

推薦閱讀更多精彩內容