一、Linux知識點
- Linux環境下,程序位址空間互相獨立,每個程序各自有不同的使用者位址空間。
- 程序隔離:是一個程序不能直接操作或通路另一個程序
- 核心空間(Kernel Space)是Linux核心運作空間,使用者空間(User Space)是使用者程式運作空間;核心空間的資料是共享的,而使用者空間則不可以資料共享。
- 系統調用:使用者空間需要通路核心空間,就需要借助系統調用來實作,這是使用者空間通路核心空間的唯一方式,提升了系統安全性和穩定性。
- 使用者程序互動:通過系統函數和核心空間進行互動,在核心中開辟一塊緩存區,程序1把資料從使用者空間拷貝到核心緩存區,程序2再從核心緩存區把資料讀取,這就是一次IPC,需要拷貝兩次。
copy_from_user 将使用者空間的資料拷貝到核心空間的核心緩存區
copy_to_user 将核心空間的核心緩存區的資料拷貝到使用者空間
缺點:接收資料的緩存是要由接收方提供,但接收方不知道需要多大的緩存
方案:
- 開辟盡量大的,浪費空間,
- 先擷取消息大小,浪費時間
- Linux提供了多種程序間通信機制,這些都是需要核心空間做支援,主要有管道(pipe),信号(sinal),消息隊列(Message),共享記憶體(Share Memory),套接字(socket)。
- 程序A和程序B可以通過系統函數和核心空間進行互動,得copy兩次,效率低下,接收資料的緩存是要由接收方提供,但接收方不知道需要多大的緩存(解決方式1:開辟盡量大的,浪費空間,方式2:先擷取消息大小,浪費時間)
copy_from_user 将使用者空間的資料拷貝到核心空間的核心緩存區
copy_to_user 将核心空間的核心緩存區的資料拷貝到使用者空間
- 記憶體映射
記憶體映射(Memory-mapped)使一個磁盤檔案與存儲空間中的一個緩存區相映射,當從緩存區中取資料,就相當于讀檔案中的相應位元組,于此類似,将資料存入緩存區,則相應的位元組就自動寫入檔案。
使用這種方法,首先應通知核心,将一個指定檔案映射到存儲區域中,這個映射工作通過mmap函數來實作。
二、Binder
1、Binder驅動
Binder不是Linux系統核心的一部分,要實作IPC通行,得益于Linux的動态核心可加載子產品(LKM)的機制;子產品是具有獨立功能的程式,它可以被單獨編譯,但是不能獨立運作,它在運作時被連結到核心作為核心的一部分運作。Android系統通過動态添加一個核心子產品運作在核心空間,這個核心子產品就是Binder驅動。
2、Binder IPC底層通信原理
通信步驟如下:
- Binder驅動在核心空間建立一個資料接收緩存區
- 建立核心緩存區與資料接收緩存區(在核心中)之間的映射關系,以及資料接收緩存區和接收程序使用者空間位址的映射關系
- 發送方程序通過系統調用copy_from_user()函數将資料copy到核心中的核心緩存區中,由于核心緩存區和接收程序的使用者空間存在記憶體映射,是以也就相當于把資料發送到了接收程序的使用者空間,整個過程隻用了1次拷貝,就完成了一次單行通行。
3、Binder通信過程
- 一個程序通過Binder驅動将自己注冊成為ServiceManager
- servicer通過驅動向ServiceManager中注冊Binder(Service的binder實體),驅動為這個Binder建立實體的引用,并在ServiceManager中加入到查找表。
- client通過名字,在Binder驅動下從ServiceManager中擷取對Binder實體的引用,通過這個引用就能實作和Server程序的通信。
4、Binder通信中的代理模式
A程序想擷取B程序的object時,驅動并不會真的把object傳回給A,而是傳回了一個object看起來一模一樣的代理對象objectProxy,在A程序中調用objectProxy的方法,會通知Binder驅動,Binder查詢自己維護的表單,發現是B程序object的代理對象,于是通知B程序調用該方法,并要求B程序把傳回結構發給自己。當驅動拿到B程序傳回的結果後會轉發給A程序,一次通信就完成了。
三、程序間通信
(1)程序間通信方式對比
名稱 | 優點 | 缺點 | 适用場景 |
intent | 簡單易用 | 資料類型為了Bundle支援的 | 四大元件的程序間通信 |
檔案共享 | 簡單易用 | 不适合高并發 | 資料共享 |
AIDL | 功能強大,支援一對多并發實時通信 | 線程同步 | 複雜的程序間通信,最常用 |
Messenger | 比aidl簡單 | 串行 | 簡單的程序間通信 |
ContentProvider | 資料共享 | ||
RemoteViews | |||
Socket | 跨主機,通信範圍廣 | 傳輸原始的位元組流 | 網絡通信 |
(2)開啟多程序
在Androidmenifest中給四大元件指定android:process開啟多程序模式
(3)多程序造成的問題(各個程序都是獨立的虛拟機)
- 靜态成員和單例模式完全失效
- 線程同步機制完全失效
- SharedPreferences的可靠性下降:因為sp不支援兩個程序并發進行讀寫,有一定幾率導緻資料丢失
- Application會多次建立
四、序列化
(1)Bundle傳遞對象為啥需要序列化
因為Bundle傳遞資料時隻支援基本資料類型,是以在傳遞對象時需要序列化轉換成可存儲或可傳輸的本質狀态(位元組流)。
(2)序列化的兩種方式
Serializable接口 | Parcelable接口 | |
平台 | java提供的 | android提供的 |
序列化原理 | 将一個對象裝換成可存儲或可存儲的狀态 | 将一個對象程序分解,分解後的每一部分都是傳遞可支援的資料類型 |
優缺點 | 簡單,效率低,開銷大,大量I/O操作 | 高效,使用較麻煩 |
使用場景 | 适合将對象序列化到儲存設備或将對象系列化通過網絡裝置傳輸 | 主要用在記憶體的序列化 |
(3)代碼示例
public class Student implements Serializable {
private static final long serialVersionUID = -4211449776975163975L;
private String name;
}
public class Student implements Parcelable {
private String name;
protected Student(Parcel in) {
name = in.readString();
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
//反序列化
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
}
五、Messenger
Messenger是一種輕量級的IPC方案,它的底層實作是AIDL,它是以串行的方式來處理用戶端發來的消息,是以在服務端我們不需要考慮線程同步的問題。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
實作事例:
服務端:onbind方法傳回自己封裝好的Messenger,msg擷取的replyTo屬性是用戶端封裝的messenger
public class MessengerService extends Service {
private static class Messengerhandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//接受用戶端用msg鍵發送的消息
msg.getData().getString("msg")
//給用戶端發消息
Messenger client=msg.replyTo;
client.send(message);
break;
}
super.handleMessage(msg);
}
}
private final Messenger mMessenger = new Messenger(new Messengerhandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
用戶端:onserviceConnected擷取服務端傳回的messenger,發送消息時給msg.replyTo指派為自己封裝的messenger
private Messenger mService;
void bindMyMessengerService(){
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
sendData();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//服務端發來的消息
msg.getData().getString("reply"));
break;
}
void sendData(){
Message msg = Message.obtain(null,1);
msg.replyTo = mCilentMessenger;//設定用戶端接受消息的Messenger
Bundle data = new Bundle();
data.putString("msg","用戶端的消息");
msg.setData(data);
try{
mService.send(msg);
}catch (RemoteException e) {
e.printStackTrace();
}
}
六、AIDL
(1)AIDL的定義
全稱Android Interface Definition Language,Android接口定義語言;如果在一個程序中要調用另一個程序中對象的方法,可使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,通過它用戶端實作間接調用服務端對象的方法。
本質是系統提供了一套可快速實作BInder的工具。
(2)AIDL檔案支援的資料類型:
- 基本資料類型(int long char boolean double等)
- String和CharSequence
- List:隻支援ArrayList,裡面每個元素都必須能夠被AIDL支援
- Map:值支援HashMap,裡面的每個元素都必須被AIDL支援,包括key和value
- Parcelable :所有實作了Parcelable接口的對象
- AIDL:所有的AIDL接口本身也可以在AIDL檔案中使用, 其中自定義的Parcelable對象和AIDL對象必須顯示import進來,并且還要建立同名的.aidl檔案
(3)aidl運用
服務端:
(1)建立一個aidl類型的檔案(裡面定義了一個接口),檔案最好在main檔案夾下建立一個檔案夾aidl,不要在java檔案夾下
package aidl;
// Declare any non-default types here with import statements
interface MyAIDLService {
int getNum(int num1,int num2);
}
然後build下項目,我們可以在build-->genetated-->aidl-->debug下生一個同名的aidl檔案,說明aidl檔案便宜成功。如下圖:
(2)建立Service,并建立中間類,中間類要實作上面定義的aidl.Stub,執行個體代碼如下
package shop.lantian.com.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import aidl.MyAIDLService;
public class AidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyServiceBind();
}
class MyServiceBind extends MyAIDLService.Stub {
@Override
public int getNum(int num1, int num2) throws RemoteException {
return num1 + num2;
}
}
}
(3)配置AndroidManifest.xml,執行個體如下,
<service android:name=".AidlService">
<intent-filter>
<action android:name="shop.lantian.com.service.AidlService"/>
</intent-filter>
</service>
用戶端:
(1)複制服務端的aidl檔案原封不動的複制到用戶端(注意,包名路徑要一模一樣)
(2)建立ServiceConnection接口的執行個體,覆寫onServiceConnected方法和onServiceDisConnected,重點在OnServiceConnection方法
private MyAIDLService mBinder;
public class MainServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = MyAIDLService.Stub.asInterface(service);
if (mBinder != null) {
try {
int end = mBinder.getNum(1, 2);
Log.e("aaa", String.valueOf(end));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
(3)調用service
MainServiceConnection connection = new MainServiceConnection();
Intent intent = new Intent();
intent.setAction("shop.lantian.com.service.AidlService");
//從 Android 5.0開始 隐式Intent綁定服務的方式已不能使用,是以這裡需要設定Service所在服務端的包名
intent.setPackage("shop.lantian.com.service");
bindService(intent, connection, BIND_AUTO_CREATE);
七、AIDL源碼分析
Binder是Android的一個類,它實作了IBinder的接口,是Android中的一種跨程序通信方式。
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.xsx.ipcdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.xsx.ipcdemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.xsx.ipcdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.xsx.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.xsx.ipcdemo.IBookManager))) {
return ((com.xsx.ipcdemo.IBookManager) iin);
}
return new com.xsx.ipcdemo.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.xsx.ipcdemo.Booker> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.xsx.ipcdemo.Booker _arg0;
if ((0 != data.readInt())) {
_arg0 = com.xsx.ipcdemo.Booker.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.xsx.ipcdemo.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.xsx.ipcdemo.Booker> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.xsx.ipcdemo.Booker.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((booker != null)) {
_data.writeInt(1);
booker.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException;
public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException;
}
- 實作AIDL的接口都得實作android.os.IInterface接口
- 内部類Stub類,BInder的實作類,服務端聽過這個類來提供服務。
- DESCRIPTOR----唯一辨別,辨別的是哪個Stub,一般用目前Binder的類名的完整路徑表示,
- asInterface(android.os.IBinder obj)
用于将服務端的Binder對象轉換為用戶端所需要的AIDL接口類型的對象,這種轉換過程是區分程序的,如果用戶端和服務端位于同一程序,那麼此方法傳回的就是服務端的Stub本身,否則傳回的是系統封裝後的Stub.proxy對象。
- asBinder() 根據目前調用情況傳回代理Proxy的Binder對象
- onTransact()
運作在服務端中的Binder線程池中,當用戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交由此方法來處理
public Boolean onTransact(int code,android.os.parcel data,android.os.Parcel reply,int flags)
傳回值為false辨別用戶端的請求會失敗,可以利用這個特性來做權限驗證;code是辨別用戶端調用的方法,data是目标方法所需要的參數,reply是目标方法執行完畢後,向reply中寫入傳回值。
- transact()
運作在用戶端,當用戶端發起遠端請求的同時将目前線程挂起,之後調用服務端的onTransact()直到遠端請求傳回,目前線程才繼續執行
- Proxy#用戶端定義的method
當用戶端調用此方法時,首先建立該方法所需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和傳回值對象,然後把該方法的參數資訊寫入data中,接着調用transact方法來發起RPC(遠端過程調用)請求,同時目前線程挂起,然後服務端的onTransact方法會被調用,知道RPC過程傳回後,目前線程繼續執行,并從_reply中取出傳回的結果。
注:當用戶端發起請求時,由于目前線程會被挂起直至伺服器程序傳回資料,是以如果一個遠端方法是很耗時的,那麼不能再UI線程中發起遠端請求;其次由于服務端的Binder方法運作在BInder的線程池中,是以Binder方法不管是否耗時都應該采用同步的方式去實作,因為它已經運作在一個線程中了。