多進程間通信AIIDL使用

AIDL 是Android上提供的一套進程間通訊的機制,在多進程間的通信上他是比較常見的,那爲什麼要用aidl 呢,我們的四大組件都可以實現跨進程,我只能說使用的場景有所不同罷了。

1.activity 可以跨進程調用另一個進程 的activity 並且也可以攜帶參數,但是侷限性比較大,只能通過intent 在啓動時傳遞。

2.BroadcastReceiver 廣播接收器,他可以無限次的發送全局廣播,但是它的數據可能不是安全的,並且通信的效率也沒有aidl 高,並且維護成本也很高,不能處理耗時操作。

3.ContentProvider 是一種數據共享型組件,用於向其他組件乃至其他應用共享數據。在它內部維持着一份數據集合, 這個數據集合既可以通過數據庫來實現,雖然很強大,但是有點重了,實現起來比較麻煩,但是對於大量數據共享是非常有用的。

Service 我們叫它服務,他可以在後臺執行一些耗時任務。可以單獨啓動執行任務,也可以綁定activity交互數據,這種只能在本進程的數據通信。其次就是通過aidl 來實現進程的通信,當然也可以通過信使Messenger 進程間通信。

介紹完了上邊四大組件的跨進程通信方式,對跨進程通信的方式有了一個大概瞭解,下面來着重介紹aidl 的使用。

AIDL 是什麼能幹什麼?

AIDL是Android中IPC(Inter-Process Communication)方式中的一種,AIDL是Android Interface definition language的縮寫,可以綁定其他進程service 通過aidl 實現進程間數據的通信。

具體的使用步驟:

1.建立兩個module ,一個用於服務端app (aidlapplication),一個用於客戶端app(clientservice)

2.服務端新建aidl類定義aidl 接口和方法及自定義數據類,配置build.gradle (看情況配置)

3.服務端新建service類(MyService),並且新建內部類(MyBinder)繼承aidl 接口類的Stub抽象類

4.服務端 MyService 重寫onBind 函數 並返回內部類MyBinder 實例

5.服務端 manifest註冊service

6.客戶端 ,將服務端定義的aidl 及自定義數據類,複製到服務端,注意包名要完全一致

7.客戶端 ,通過包名和action 綁定服務端的service

8.客戶端 ,通過綁定成功後返回的service 獲取定義的aidl 接口

9.客戶端 ,通過接口和服務端通信了。

總共分爲9部,這裏分的比較細,下面按步驟來學習:

先來假定一個業務邏輯然後我們來實現,客戶端可以通過接口直接獲取服務端的基本數據類型,可以獲取服務端的自定義數據類型,可以修改添加服務端的自定義數據類型。

1.新建兩個app這個就不用說,掠過

2.新建aidl 類:

   新建aidl 接口類IMyAidlInterface  及自定義數據類Person 類,IMyAidlInterface 新建完成後,會自動新建一個接口方法

 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

他只是告訴我們,他支持的基本數據類型,這些基本數據類型不需要import 方式導包,沒啥用可以刪除了,其他自定義的數據類型以及其他類型數據都要使用import 方式來導包。

創建Person 類並且實現Parcelable 接口

public class Person implements Parcelable {
    int age;
    int height;
    String name;

    public Person(int age, int height, String name) {
        this.age = age;
        this.height = height;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getName() {
        return name;
    }

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

    public Person() {
    }

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

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

    protected Person(Parcel in) {
        this.age = in.readInt();
        this.height = in.readInt();
        this.name = in.readString();
    }

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

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

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", height=" + height +
                ", name='" + name + '\'' +
                '}';
    }
}

   添加IMyAidlInterface 接口的方法,定義我們的業務方法。

package com.example.aidlapplication;

// Declare any non-default types here with import statements
//導入Person 包路徑
import com.example.aidlapplication.Person;
import java.util.List;
//自定義數據類型必須parcelable 關鍵字標註否則會找不到類
parcelable Person;
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     //定義提供的接口 返回值是一個基本數據類型
     String getName();
     // 返回值是一個自定義的數據類型集合,用於獲取本進程內的數據 不在基本數據類型內的,都要導包
     List<Person> getPerson();
     // 提供遠程操作本進程內數據的接口方法
     void addPerson(in Person person);

}

注意,此處有坑,先來看這些類的包路徑情況

1坑.  我們剛纔生成的類都在aidl 文件夾下,這時就會報找不Person 的錯誤,這時要配置下build.gradle文件,注意是module 的,添加如下

sourceSets {
    main {
        java.srcDirs = ["src/main/java", "src/main/aidl"]
    }
}

但是如果放在java 目錄包下就不用配置了,因爲默認是去java下找類的。

 

2坑.  自定義數據類型一定要用 parcelable 關鍵字標註 如

parcelable Person;

其次必須要正確的導入Person 的包路徑,否則也會找不到類錯誤。

完成上面的操作後我們需要構建下項目,用於通過aidl  生成java 接口文件。

生成的位置位於:

3.服務端新建service類(MyService),並且新建內部類(MyBinder)繼承aidl 接口類的Stub抽象類,實現aidl 的所有接口,至於問什麼要繼承Stub 類,這要去看下構建生成的IMyAidlInterface 類,本篇只講使用。

 

public class MyService extends Service {
    private static final String TAG = "ClientService";
    List<Person> personList = new ArrayList<>();

    public MyService() {
    }

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

    class MyBinder extends IMyAidlInterface.Stub {


        @Override
        public String getName() throws RemoteException {
            Log.d(TAG, "getName: 遠程調用獲取基本類型數據");
            return "幺妹子";
        }

        @Override
        public List<Person> getPerson() throws RemoteException {
            Log.d(TAG, "getPerson: 遠程調用獲取自定義類型數據");
            return personList;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            Log.d(TAG, "addPerson: " +"收到添加進來的person" + person.toString());
            personList.add(person);
        }


    }
}

4.服務端 MyService 重寫onBind 函數 並返回內部類MyBinder 實例

  MyBinder 類繼承自Stub抽象類,stub實現了IMyAidlInterface 接口並且繼承自

binder 類,binder實現了IBinder 接口。這裏返回MyBinder 實例,實際返回類型就是IBinder類,當綁定service時返回的就是這個MyBinder 實例。
public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

5.服務端 manifest註冊service

 這部非常關鍵,四大組件都必須在manifest 中註冊,並且要簡單配置下,以便可以遠程調用。

 <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <action android:name="com.example.service.aidl"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
 enabled:是否這個service能被系統實例化,如果能則爲true,否則爲false。默認爲true。
 exported :是否支持其它應用調用當前組件  
 process :創建本應用的私有的新進程
 Action:Action屬性的值爲一個字符串,它代表了系統中已經定義了一系列常用的動作。
 Category:Category屬性用於指定當前動作(Action)被執行的環境

通過Intent Filter 隱式啓動service  

6.客戶端 ,將服務端定義的aidl 及自定義數據類,複製到服務端,注意包名要完全一致.

將服務端 aidl 文件夾複製客戶端

注意配置要和服務端一致。

7.客戶端 ,通過包名和action 綁定服務端的service

注意包名是服務端的包名,action 啓動標示 ,用於匹配intent-filter 啓動service

 Intent intent = new Intent();
        intent.setPackage("com.example.aidlapplication");
        intent.setAction("com.example.service.aidl");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);

8.客戶端 ,通過綁定成功後返回的service 獲取定義的aidl 接口

 綁定成功後onServiceConnected 方法會返回來兩個參數,其中IBinder就是我上邊說的onBind 返回的接口類,

通過IMyAidlInterface.Stub.asInterface方法轉爲我們定義的接口

iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);

9.客戶端 ,通過接口和服務端通信了

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    IMyAidlInterface iMyAidlInterface;
    private TextView getAidlTest;
    private TextView getAidlCustom;
    private TextView getAidlAddCustom;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setPackage("com.example.aidlapplication");
        intent.setAction("com.example.service.aidl");
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, BIND_AUTO_CREATE);
        initView();
    }

    private void initView() {
        getAidlTest = findViewById(R.id.get_aidl_Test);
        getAidlAddCustom = findViewById(R.id.get_aidl_add_custom);
        getAidlCustom = findViewById(R.id.get_aidl_custom);
        getAidlTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_LONG).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        getAidlCustom.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    List<Person> personList = iMyAidlInterface.getPerson();
                    Iterator<Person> pe = personList.iterator();
                    for (int i = 0; i < personList.size(); ++i) {
                        Person person = pe.next();
                        Log.d(TAG, "onClick: " + person.toString());
                    }

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        getAidlAddCustom.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exception();
                try {
                    iMyAidlInterface.addPerson(new Person(34,56,"這是一條插入的人名" + System.currentTimeMillis() +""));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void exception() {
        if (iMyAidlInterface == null) {
            try {
                throw new IllegalAccessException("接口爲空");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return;
        }
    }
}

xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginTop="20dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/get_aidl_Test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="獲取基本數據類型信息"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/get_aidl_custom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="獲取自定義數據類型"
            android:textSize="18sp" />
        <TextView
            android:id="@+id/get_aidl_add_custom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="輸入自定義數據類型"
            android:textSize="18sp" />
    </LinearLayout>

</merge>

 

最後記得構建項目,然後測試的時候先啓動服務端,然後啓動客戶端。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章