Android進程間的通信

Android系統中的應用程序之間是不能共享內存的,每一個應用程序都有自己獨有的虛擬機,這樣就保證了數據的安全性,但是這樣就給兩個應用程序之間進行通信帶來了不便,所以我們就知道了兩點:

  1. 兩個進程是無法直接進行通信的
  2. 跨進程通信是通過Android系統底層進行間接通信

Android中的通信主要有下面四種:Activity,Broadcast,ContentProvider以及AIDL,我們可以發現這四種方法是分別基於四大組件進行實現的。

Activity

Activity的跨進程訪問與進程內訪問略有不同。雖然它們都需要Intent對象,但跨進程訪問並不需要指定Context對象和Activity的 Class對象,而需要指定的是要訪問的Activity所對應的Action。有些Activity還需要指定一個Uri(通過 Intent構造方法的第2個參數指定)。 在android系統中有很多應用程序提供了可以跨進程訪問的Activity。

Intent callIntent = new  Intent(Intent.ACTION_CALL, Uri.parse("tel:"+ edit.getText().toString()));  
startActivity(callIntent);

Broadcast

廣播是一種被動跨進程通訊的方式。當某個程序向系統發送廣播時,其他的應用程序只能被動地接收廣播數據。在應用程序中發送廣播很簡單,只需要調用sendBroadcast方法即可。該方法需要一個Intent對象。通過Intent對象可以發送需要廣播的數據。

ContentProvider

ContentProvider向我們提供了我們在應用程序之前共享數據的一種機制,而我們知道每一個應用程序都是運行在不同的應用程序的,數據和文件在不同應用程序之間達到數據的共享不是沒有可能,而是顯得比較複雜,而正好Android中的ContentProvider則達到了這一需求,比如有時候我們需要操作手機裏的聯繫人,手機裏的多媒體等一些信息,我們都可以用到這個ContentProvider來達到我們所需。
ContentProvider強調的是數據之間的共享,我們使用文件進行共享,也可以是認爲這種方式進行進程間的通信。

ADIL

AIDL(Android Interface Definition Language):Android接口定義語言。他定義了客戶端與服務端的一個標準規範,AIDL強調的是客戶端調用Service的相關方法。我們要注意到AIDL畢竟是跨進程進行通信的,所以他所消耗的內存比較大,因此不能隨意去使用AIDL,要不然會影響我們應用程序的效果。所以.只有當你需要來自不同應用的客戶端通過IPC(進程間通信)通信來訪問你的服務時,並且想在服務裏處理多線程的業務,這時就需要使用AIDL。如果你不需要同時對幾個應用進程IPC操作,你最好通過實現Binder接口來創建你的接口。如果你仍需要執行IPC操作,但不需要處理多線程,使用Messenger來實現接口即可。

  1. AIDL是用於多進程併發通信處理
  2. Binder是用於非多進程併發處理
  3. Messenger是用於多進程且下的非併發處理

適用範圍:Messenger < Binder < AIDL 當然複雜度也依次增加。當然實際messenger的本質也是調用的AIDL,只是進行了封裝,開發的時候不用再寫.aidl文件。並且在Service端,Messenger處理Client端的請求是單線程的,而AIDL是多線程的。在Client端使用AIDL獲取返回值是同步的,而Messenger是異步的。
接下來就來說說AIDL如何進行使用

AIDL基本語法

我們有三種方法生成AIDL:

  1. 直接用記事本進行編寫,這裏我們要注意到,要對應包名的文件夾嵌套,然後通過sdk中的build-tools目錄下最新版本的文件夾下的aidl.exe進行編譯(控制檯)。
  2. 用eclipse進行編寫,但是eclipse中沒有對應的.aidl文件,所以我們直接new一個text文件,修改後綴就好了。然後我們就可以進行調用了,他已經幫我們生成好了對應的.java文件。
  3. 用Android Studio進行編寫,在Android Studio中就沒有eclipse在那麼隨便了,我們需要先new出一個AIDL Folder,之後在這個文件夾中new出.aidl文件,最後進行編譯,因爲它不同於eclipse,它生成的.java文件是默認的。

然後我們就來說下AIDL的基本語法,它是一個接口定義語言,所以我們要知道我們定義的是一個接口。下面就是直接生成的aidl文件:

// IMyAidlInterface.aidl
package com.gin.xjh.aidltest;//包名

// Declare any non-default types here with import statements
//interface 關鍵字
//IMyAidlInterface 接口名
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

然後我們對這個文件進行修改,再進行編譯:

// IMyAidlInterface.aidl
package com.gin.xjh.aidltest;//包名

// Declare any non-default types here with import statements
//interface 關鍵字
//IMyAidlInterface 接口名
interface IMyAidlInterface {

    //方法
    int getPid(int text);
    
}

這樣我們就能在MainActivity中進行調用了。然後我們可以在Project目錄下看到這個生成的.java文件。
視圖

AIDL功能實現

我們主要是實現一個客戶端輸入兩個數,我們將這兩個數上傳到服務器端,計算兩個數的和,將結果返回客戶端。所以首先我們要按照之前的步驟寫好aidl文件:

package com.gin.xjh.aidl;

interface IAidl {

    //計算兩個int的和
    int add(int num1,int num2);

}

然後我們就要定義一個類繼承自Service,因爲aidl要在藉助Service進行實現。之後我們要實現我們所定義的接口,在Service的onBind方法中進行返回。

package com.gin.xjh.aidl;

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

public class IRemoteService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return iBinder;
    }

    private IBinder iBinder = new IAidl.Stub() {

        @Override
        public int add(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }
    };
}

但是我們要注意我們的服務端需要進行註冊,並且設置允許遠程才能正常使用,要不然之後客戶端在請求結果的傷害就會奔潰。

<service android:name=".IRemoteService"
    android:process=":remote"
    android:exported="true"/>

接下來我們就要開始編寫客戶端。在客戶端最重要的就是界面,界面我們就不多說了,特別簡單的UML:

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

    <EditText
        android:id="@+id/ed_num1"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content" />

    <TextView
        android:text="+"
        android:gravity="center"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/ed_num2"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content" />

    <Button
        android:text="="
        android:id="@+id/submit"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_res"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content" />

</LinearLayout>

然後我們將我們在服務器端寫的aidl文件複製到客戶端來,並且我們需要注意**客戶端和服務端相同aidl文件的包名是一致的,要不然導致應用強制退出。**之後我們就需要在客戶端對於服務端進行鏈接,之前我們已經說過了,服務器端是一個Service,所以我們先將Activity中的基本操作寫好:

package com.gin.xjh.aidlclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.gin.xjh.aidl.IAidl;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText mEtNum1,mEtNum2;
    private Button mBtSubmit;
    private TextView mTvRes;
    private IAidl iAidl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        bindService();
        initEvent();
    }

    private void initView() {
        mEtNum1 = findViewById(R.id.ed_num1);
        mEtNum2 = findViewById(R.id.ed_num2);
        mBtSubmit = findViewById(R.id.submit);
        mTvRes = findViewById(R.id.tv_res);
    }
    
    private void initEvent() {
        mBtSubmit.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        
    }
}

然後我們當然就要實現bindService方法,在這裏面我們首先需要通過bind方式讓Service與Activity進行綁定,並且我們不要忘記對於Service的解綁:

private void bindService() {
     //獲取到服務端
     Intent intent = new Intent();
     //顯式Intent啓動服務
     //ComponentName組件名
     intent.setComponent(new ComponentName("com.gin.xjh.aidl","com.gin.xjh.aidl.IRemoteService"));
     bindService(intent,conn, Context.BIND_AUTO_CREATE);
 }
@Override
protected void onDestroy() {
    super.onDestroy();
    unbindService(conn);
}

我們現在就發現了我們需要一個ServiceConnection對象,這個就是讓我們拿到IAidl對象,IAidl對象代表的就是遠程服務的代理,讓我們這點擊時能將數據進行上傳服務端進行處理。當然我們在服務斷開的時候需要對資源進行回收,放在內存泄露。

private ServiceConnection conn = new ServiceConnection() {
    //綁定服務時
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        //拿到了遠程的服務的代理
        iAidl = IAidl.Stub.asInterface(iBinder);
    }

    //服務斷開時
    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        //回收資源
        iAidl = null;
    }
};

最後就是對於OnClick方法的編寫,我們獲取到輸入框的值,調用遠程服務的代理,將數據傳入就達到了我們想要的效果了。

@Override
public void onClick(View view) {
    int num1 = Integer.parseInt(mEtNum1.getText().toString());
    int num2 = Integer.parseInt(mEtNum2.getText().toString());
    try {
        int res = iAidl.add(num1, num2);
        mTvRes.setText(res+"");
    } catch (RemoteException e) {
        e.printStackTrace();
        mTvRes.setText("錯誤");
    }
}

AIDL數據傳遞類型

因爲AIDL是不能傳遞大型數據的,因爲在操作系統底層所傳遞的消息是最基本的,它所支持的數據類型主要是有下面4種:

  • 基本數據類型(除short以外,以外Parcel中沒有對於writeShor方法)
  • String,CharSequence
  • List,Map(所放的元素一定要是上面出現過的類型,並且我們需要對這個參數進行描述:in,out,inout,代表的是輸入修改,輸出修改和輸入輸出修改的,因爲作系統底層所傳遞的消息是最基本的,所以對於大型的數據會被打碎(打包),然後傳遞,再到另一方進行組合(拆包),所以我們標記了好了輸入輸出就能減少資源)
  • Pracelable(對於自定義類實現Pracelable接口,完成裏面的兩個方法,在writeToParcel方法中將數據寫入Parcel中writeXxx()方法進行寫入,然後再定義如下方法完成數據的解析:
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];
	}
};

然後我們就編寫Person的一個新的構造方法就好了。但是我們要注意到一件事情,我們是按什麼順序加進來的,就要按什麼順序取出來。

public Person(Parcel source){
	this.name = source.readString();
	this.age = source.readInt();
}

之後我們在aidl文件中需要將Person文件導入進aidl文件中,並且我們需要建立一個aidl文件對於Person進行描述。

package com.gin.xjh.aidl;

pracelable Person;

並且我們需要記住在Person前面加上in或者out或者inout類型

package com.gin.xjh.aidl;

import com.gin.xjh.aidl.Person;

interface Aidl{
	List<Person> add(in Person person);
}

然後要將Person類連同包一起復制到客戶端上,這樣才能保持包的路徑是一致的,之後就和上面的沒有什麼區別了。

總結

我們可以發現AIDL操作中主要是下面3個步驟:

  1. 定義AIDL文件
  2. 服務端實現接口
  3. 客戶端調用接口

因此我們必須要保證接口一致。
AIDL
AIDL實現接口時有以下幾個原則:

  • 拋出的異常不要返回給調用者,跨進程拋異常處理是不可取的。
  • IPC調用的是同步的,如果已知一個IPC服務需要超過幾毫秒的時間才難完成,我們就要避免者Activity的主線程中進行調用,否則可能會導致應用程序導致界面失去響應。
  • AIDL運行方法有任何類型的參數和返回值
  • AIDL只支持方法,不能中AIDL接口中聲明靜態屬性。

還有我們會發現我們使用了代理(proxy)和存根(stub),那這兩個是什麼東西呢?
我們可以這樣進行理解你到自動取款機上去取款;你就是客戶,取款機就是你的代理;你不會在乎錢具體放在那裏,你只想看到足夠或更多的錢從出口出來(這就是com的透明性)。你同銀行之間的操作完全是取款機代理實現。 你的取款請求通過取款機,傳到另一頭,銀行的服務器,他也沒有必要知道你在哪兒取錢,他所關心的是你的身份,和你取款多少。當他確認你的權限,就進行相應的操作,返回操作結果給取款機,取款機根據服務器返回結果,從保險櫃裏取出相應數量的錢給你。你取出卡後,操作完成。 取款機不是直接同服務器連接的,他們之間還有一個“存根”,取款機與存根通信,服務器與存根通信。從某種意義上說存根就是服務器的代理。(摘自百度Wiki)
這樣我們就能很好的理解代理以及存根的概念。

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