Android系統中的應用程序之間是不能共享內存的,每一個應用程序都有自己獨有的虛擬機,這樣就保證了數據的安全性,但是這樣就給兩個應用程序之間進行通信帶來了不便,所以我們就知道了兩點:
- 兩個進程是無法直接進行通信的
- 跨進程通信是通過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來實現接口即可。
- AIDL是用於多進程併發通信處理
- Binder是用於非多進程併發處理
- Messenger是用於多進程且下的非併發處理
適用範圍:Messenger < Binder < AIDL 當然複雜度也依次增加。當然實際messenger的本質也是調用的AIDL,只是進行了封裝,開發的時候不用再寫.aidl文件。並且在Service端,Messenger處理Client端的請求是單線程的,而AIDL是多線程的。在Client端使用AIDL獲取返回值是同步的,而Messenger是異步的。
接下來就來說說AIDL如何進行使用
AIDL基本語法
我們有三種方法生成AIDL:
- 直接用記事本進行編寫,這裏我們要注意到,要對應包名的文件夾嵌套,然後通過sdk中的build-tools目錄下最新版本的文件夾下的aidl.exe進行編譯(控制檯)。
- 用eclipse進行編寫,但是eclipse中沒有對應的.aidl文件,所以我們直接new一個text文件,修改後綴就好了。然後我們就可以進行調用了,他已經幫我們生成好了對應的.java文件。
- 用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個步驟:
- 定義AIDL文件
- 服務端實現接口
- 客戶端調用接口
因此我們必須要保證接口一致。
AIDL實現接口時有以下幾個原則:
- 拋出的異常不要返回給調用者,跨進程拋異常處理是不可取的。
- IPC調用的是同步的,如果已知一個IPC服務需要超過幾毫秒的時間才難完成,我們就要避免者Activity的主線程中進行調用,否則可能會導致應用程序導致界面失去響應。
- AIDL運行方法有任何類型的參數和返回值
- AIDL只支持方法,不能中AIDL接口中聲明靜態屬性。
還有我們會發現我們使用了代理(proxy)和存根(stub),那這兩個是什麼東西呢?
我們可以這樣進行理解你到自動取款機上去取款;你就是客戶,取款機就是你的代理;你不會在乎錢具體放在那裏,你只想看到足夠或更多的錢從出口出來(這就是com的透明性)。你同銀行之間的操作完全是取款機代理實現。 你的取款請求通過取款機,傳到另一頭,銀行的服務器,他也沒有必要知道你在哪兒取錢,他所關心的是你的身份,和你取款多少。當他確認你的權限,就進行相應的操作,返回操作結果給取款機,取款機根據服務器返回結果,從保險櫃裏取出相應數量的錢給你。你取出卡後,操作完成。 取款機不是直接同服務器連接的,他們之間還有一個“存根”,取款機與存根通信,服務器與存根通信。從某種意義上說存根就是服務器的代理。(摘自百度Wiki)
這樣我們就能很好的理解代理以及存根的概念。