Android系統編程入門系列之服務Service中的進程間通信


上篇文章以線程間的通信方式Handler類結尾,服務Service還支持的進程間通信,又是具體怎么實現的呢?這就要用到加載服務一文中提到的AIDL語言規范了。

AIDL是 Android Interface Definition Language 的縮寫,即Android接口定義語言,使用其定義的規范編程,可實現Android系統上不同進程間的通信。官網ADIL概述中以服務端和客戶端通信為例做了大致講述。與線程間的通信類似,不同進程間的通信也是分為通信消息內容、消息發送方、消息接收方三個部分的,其中的消息內容也就是AIDL支持的數據類型;而由於服務已經位於當前應用程序所在的默認進程,所以服務中所實現的接口內容即對接收消息的處理,也就是消息接收方;相對的,其他需要發送消息的進程都可以看做是客戶端進程,只要綁定服務之后都可以調用服務的接口方法,也就是消息發送方。

確定通信的消息內容

在AndroidStudio中,通信的消息內容以 .aidl 后綴格式的文件默認保存在開發目錄 /src/main/aidl 下。在 .adil 格式的文件中,編碼規則與Java語言一致,其中內容與Java語言中的接口定義類似。
其中支持通信的消息類型分為兩類,一類是基本類型,使用基本類型時不需要在文件開頭增加導入包名的聲明,這些基本類型包括Java語言中的八種基本數據類型, String, CharSequence, List, Map;還有一類是附加類型,除了第一類基本類型,其他在 .java 格式的文件中定義的任何實現android.os.Parcelable接口的類都可以作為附加類型消息傳遞。附加類型在使用時需要在 .aidl 文件開頭增加導入該類型所在包名的聲明。

關於附加類型的定義,以com.java.process.myinterface.Person類型為例。
不僅在 /src/main/java/com/java/process/myinterface 目錄下創建 Person.java ,在其中定義Person類並實現Parcelable接口的相關方法。
還要在 /src/main/aidl/com/java/process/myinterface 目錄下創建對應的 Person.aidl 文件,在其中除了指定包名外,還要聲明該類為parcelable接口,示例代碼如下

package com.java.process.myinterface;
parcelable Person;

.aidl.java 格式的文件中定義的接口相比有兩處不同,其一是無需訪問修飾符,其二是對基本數據類型增加數據流向標識符(包括數據流入接收方的in,數據在接收方修改后可流出的out, 數據可流向接收方並流出的inout )。

關於通信消息的定義,以com.java.process.myinterface.DataInterface類型為例。
只需要在 /src/main/aidl/com/java/process/myinterface 目錄下創建 DataInterface.aidl 文件,在其中定義相關接口即可。示例代碼如下

package com.java.process.myinterface;
import com.java.process.myinterface.Person;
interface DataInterface{
    void setVersion(int version);
    int getVersion();
    void updatePerson(in Person person);
    Person getMainPerson();
}

在通信消息定義之后,可以使用AndroidStudio的編譯指令 Build - Make Project 編譯當前項目,以使得AndroidStudio自動生成消息定義的 .java 文件。上文 DataInterface.aidl 文件在項目編譯之后,會在 /build/generated/aidl_source_output_dir/debug/out/com/java/process/myinterface 目錄下生成不可編輯的 DataInterface.java 文件,之后即可在項目中通過導包import com.java.process.myinterface.DataInterface;的形式正常使用該接口類了。

通信接收方的服務接收處理

通信接收方就是常說的服務端,通常是自定義的服務Service類,主要負責實現上述通信消息內容的接口,以此接收消息內容並處理。
針對自定義的服務Service類,這里就用到在加載服務文章中提到的綁定服務的生命周期了。重寫onBind(Intent intent)方法,在該方法中返回android.os.IBinder類型的對象實例。

而這個IBinder對象是怎么創建的呢?在上文通信消息定義之后自動生成的接口類中,也自動生成了其Stub內部類,在創建該內部類的無參構造方法時,即可自動實現其相關接口方法,繼續使用上文示例,這里在服務Service中的接口實現代碼如下

package com.java.process.myinterface;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import com.java.process.myinterface.DataInterface;

public class ProcessService extends Service {

    private String versionName;
    private Person person;
    private final DataInterface.Stub binder=new DataInterface.Stub() {

        @Override
        public String getVersionName() throws RemoteException {
            //這里可以返回消息versionName給客戶端
            return versionName;
        }

        @Override
        public void setVersionName(String versionName) throws RemoteException {
            //這里接收處理來自客戶端的消息versionName
            ProcessService.this.versionName=versionName;
        }

        @Override
        public void updatePerson(Person person) throws RemoteException {
            //這里接收處理來自客戶端的消息person
            ProcessService.this.person.setName(person.getName());
            ProcessService.this.person.setAge(person.getAge());
        }

        @Override
        public Person getMainPerson() throws RemoteException {
            //這里可以返回消息person給客戶端
            return ProcessService.this.person;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        versionName="defaultName";
        person=new Person();
        person.setName("initName");
        person.setAge(10);
    }

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

通信發送方的服務綁定

通信發送方就是所謂的客戶端了,發送方是與上文中的接收方不在同一個進程的,所以發送方通常需要先綁定接收方的自定義服務Service,也就是在發送方通過上下文環境Context對象調用bindService(Intent service, ServiceConnection conn, int flags)方法,這里的參數在加載服務中已有詳細說明。
conn 參數的onServiceConnected(ComponentName name, IBinder service)方法中通過調用靜態方法(通信消息定義接口的內部類Stub.asInterface(IBinder service)方法)得到通信消息的定義接口的實例化對象。在當前客戶端綁定自定義服務Service成功之后,回調該方法,即可將通信消息的定義接口的實例化對象賦值給全局變量使用。
conn 參數的onServiceDisconnected(ComponentName name)方法中,要記得將全局通信消息定義接口變量的實例化對象置為空,否則在當前客戶端與自定義服務Service斷開連接后,還調用全局變量的通信消息定義接口的實例化對象的相關方法,可能會出現OOM內存泄露的問題。

最后在需要發送消息的位置,只需要調用全局通信消息定義接口變量的相關方法即可。其中用到的示例代碼如下

    private DataInterface dataInterface;
    private ServiceConnection conn=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            dataInterface = DataInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            dataInterface = null;
        }

    //此處省略對文本編輯控件editText的定義
    //該方法在點擊editText控件時回調
    public void clickName(View view) {
        try {
            String setName = editText.getText().toString();
            dataInterface.setVersionName(setName);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    };


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM