Android中進程和線程的關係和區別
- 線程是CPU調度的最小單元,同時線程是一種有限的系統資源;而進程一般指一個執行單元,在PC和移動設備上指一個程序或者一個應用。
- 進程有自己獨立的地址空間,而進程中的線程共享此地址空間,都可以併發執行。
- 一般來說,一個App程序至少有一個進程,一個進程至少有一個線程(包含與被包含的關係),通俗來講就是,在App這個工廠裏面有一個進程,線程就是裏面的生產線,但主線程(即主生產線)只有一條,而子線程(即副生產線)可以有多個。
爲何需要進程間通信(IPC)?多進程通信可能會出現的問題?
因爲所有運行在不同進程的四大組件(Activity、Service、Receiver、ContentProvider)共享數據都會失敗,這是由於==Android爲每個應用分配了獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這會導致在不同的虛擬機中訪問同一個類的對象會產生多份副本==。比如常用例子(通過開啓多進程獲取更大內存空間、兩個或者多個應用之間共享數據、微信全家桶)。爲了解決數據共享問題需要進行IPC。
一般來說,使用多進程通信會造成如下幾方面的問題:
- 靜態成員和單例模式完全失效:獨立的虛擬機造成。
- 線程同步機制完全失效:獨立的虛擬機造成。
- SharedPreferences的可靠性下降:這是因爲Sp不支持兩個進程併發進行讀寫,有一定機率導致數據丟失。
- Application會多次創建:Android系統在創建新的進程時會分配獨立的虛擬機,所以這個過程其實就是啓動一個應用的過程,自然也會創建新的Application。
Android中IPC方式、各種方式優缺點?
還有==廣播==
Bundle傳遞對象爲什麼需要序列化?Serialzable和Parcelable的區別?
==因爲bundle傳遞數據時只支持基本數據類型,所以在傳遞對象時需要序列化轉換成可存儲或可傳輸的本質狀態(字節流)==。序列化後的對象可以在網絡、IPC(比如啓動另一個進程的Activity、Service和Reciver)之間進行傳輸,也可以存儲到本地。
序列化實現的兩種方式:實現Serializable/Parcelable接口。不同點如圖:
AIDL以及優化多模塊使用AIDL的情況
AIDL(Android Interface Definition Language,Android接口定義語言):如果在一個進程中要調用另一個進程中對象的方法,可使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,通過它客戶端可以實現間接調用服務端對象的方法。
AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:
- AIDL接口:繼承IInterface。
- Stub類:Binder的實現類,服務端通過這個類來提供服務。
- Proxy類:服務端的本地代理,客戶端通過這個類調用服務端的方法。
- asInterface():客戶端調用,將服務端返回的Binder對象,轉換成客戶端所需要的AIDL接口類型的對象。如果客戶端和服務端位於同一進程,則直接返回Stub對象本身,否則返回系統封裝後的Stub.proxy對象。
- asBinder():根據當前調用情況返回代理Proxy的Binder對象。
- onTransact():運行在服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝後交由此方法來處理。
- transact():運行在客戶端,當客戶端發起遠程請求的同時將當前線程掛起。之後調用服務端的onTransact()直到遠程請求返回,當前線程才繼續執行。
多模塊使用AIDL優化
- 當有多個業務模塊都需要AIDL來進行IPC,此時需要爲每個模塊創建特定的aidl文件,那麼相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。解決辦法是建立Binder連接池,即將每個業務模塊的Binder請求統一轉發到一個遠程Service中去執行,從而避免重複創建Service。
- 原理:每個業務模塊創建自己的AIDL接口並實現此接口,然後向服務端提供自己的唯一標識和其對應的Binder對象。服務端只需要一個Service並提供一個queryBinder接口,它會根據業務模塊的特徵來返回相應的Binder對象,不同的業務模塊拿到所需的Binder對象後就可以進行遠程方法的調用了。
private final BookController.Stub stub = new BookController.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookList;
}
@Override
public void addBookInOut(Book book) throws RemoteException {
if (book != null) {
book.setName("服務器改了新書的名字 InOut");
bookList.add(book);
} else {
Log.e(TAG, "接收到了一個空對象 InOut");
}
}
};
...
@Override
public IBinder onBind(Intent intent) {
String type = intent.getStringExtra("type")
if("one".isEqual(type)){
return stub;
}else{
return stub1;
}
...
}
AIDL使用
服務端
- 創建實體類.aidl和對應的實體類.java文件。
Book.aidl
package com.wu.test.aidl;
parcelable Book;
Book.java
public class Book implements Parcelable {
//實體字段、實現序列化方法
}
- 定義接口
interface BookController {
List<Book> getBookList();
void addBookInOut(inout Book book);
}
創建或修改過AIDL文件後需要clean下工程,使系統及時生成我們需要的文件
- 創建Service供客戶端調用
public class AIDLService extends Service {
...
private final BookController.Stub stub = new BookController.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookList;
}
@Override
public void addBookInOut(Book book) throws RemoteException {
if (book != null) {
book.setName("服務器改了新書的名字 InOut");
bookList.add(book);
} else {
Log.e(TAG, "接收到了一個空對象 InOut");
}
}
};
@Override
public IBinder onBind(Intent intent) {
return stub; //返回binder對象Stub
}
}
客戶端
- 把服務端的AIDL文件以及Book類複製過來,將 aidl 文件夾整個複製到和Java文件夾同個層級下,不需要改動任何代碼。創建和服務端Book類所在的相同包名來存放 Book類。
- 綁定服務,獲取服務端的binder接口對象。
...
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookController = BookController.Stub.asInterface(service); //綁定服務後,拿到接口對象
connected = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
connected = false;
}
};
//綁定服務
private void bindService() {
Intent intent = new Intent();
intent.setPackage("com.wu.test");
intent.setAction("com.wu.test.action");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
aidl文件只是用來定義C/S交互的接口,Android在編譯時會自動生成相應的Java類,生成的類中包含了Stub和Proxy靜態內部類,用來封裝數據轉換的過程,實際使用時只關心具體的Java接口類即可。爲什麼Stub和Proxy是靜態內部類呢?這其實只是爲了將三個類放在一個文件中,提高代碼的聚合性。
爲何選擇Binder方式
在討論這個問題之前,我們知道Android也是基於Linux內核,Linux現有的進程通信手段有以下幾種:
- 管道:在創建時分配一個page大小的內存,緩存區大小比較有限;
- 消息隊列:信息複製兩次,額外的CPU消耗;不合適頻繁或信息量大的通信;
- 共享內存:無須複製,共享緩衝區直接附加到進程虛擬地址空間,速度快;但進程間的同步問題操作系統無法實現,必須各進程利用同步工具解決;
- 套接字:作爲更通用的接口,傳輸效率低,主要用於不同機器或跨網絡的通信;
- 信號量:常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。 不適用於信息交換,更適用於進程中斷控制,比如非法內存訪問,殺死某個進程等;
既然有現有的IPC方式,==爲什麼重新設計一套Binder機制呢==。主要是出於三個方面的考量:
-
效率:傳輸效率主要影響因素是內存拷貝的次數,拷貝次數越少,傳輸速率越高。從Android進程架構角度分析:對於消息隊列、Socket和管道來說,數據先從發送方的緩存區拷貝到內核開闢的緩存區中,再從內核緩存區拷貝到接收方的緩存區,一共兩次拷貝。
而對於Binder來說,數據從發送方的緩存區拷貝到內核的緩存區,而接收方的緩存區與內核的緩存區是映射到同一塊物理地址的,節省了一次數據拷貝的過程,如圖: -
穩定性:共享內存不需要拷貝,Binder的性能僅次於共享內存。共享內存的性能優於Binder,那爲什麼不採用共享內存呢,因爲共享內存需要處理併發同步問題,容易出現死鎖和資源競爭,穩定性較差。Socket雖然是基於C/S架構的,但是它主要是用於網絡間的通信且傳輸效率較低。Binder基於C/S架構 ,Server端與Client端相對獨立,穩定性較好。
-
安全性:傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑑別對方身份;而Binder機制爲每個進程分配了UID/PID,且在Binder通信時會根據UID/PID進行有效性檢測。
Binder機制的作用和原理
Linux系統將一個進程分爲用戶空間和內核空間。對於進程之間來說,用戶空間的數據不可共享,內核空間的數據可共享,爲了保證安全性和獨立性,一個進程不能直接操作或者訪問另一個進程,即Android的進程是相互獨立、隔離的,這就需要跨進程之間的數據通信方式。普通的跨進程通信方式一般需要2次內存拷貝,如下圖所示
==一次完整的 Binder IPC 通信過程==通常是這樣:
- 首先 Binder 驅動在內核空間創建一個數據接收緩存區。
- 接着在內核空間開闢一塊內核緩存區,建立內核緩存區和內核中數據接收緩存區之間的映射關係,以及內核中數據接收緩存區和接收進程用戶空間地址的映射關係。(Binder IPC 機制中涉及到的==內存映射通過 mmap() 來實現==,mmap() 是操作系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。)
- 發送方進程通過系統調用 copyfromuser() 將數據 copy 到內核中的內核緩存區,由於內核緩存區和接收進程的用戶空間存在內存映射,因此也就相當於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通信。
數據接收緩衝區映射如何實現?
==Binder驅動實現了mmap()系統調用==,這對字符設備是比較特殊的,因爲mmap()通常用在有物理存儲介質的文件系統上,而象Binder這樣沒有物理介質,純粹用來通信的字符設備沒必要支持mmap()。Binder驅動當然不是爲了在物理介質和用戶空間做映射,而是用來創建數據接收的緩存空間。先看mmap()是如何使用的:
fd = open("/dev/binder", O_RDWR);
mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
這樣Binder的接收方就有了一片大小爲MAP_SIZE的接收緩存區。==mmap()的返回值是內存映射在用戶空間的地址==,不過這段空間是由驅動管理,用戶不必也不能直接訪問(映射類型爲PROT_READ,只讀映射)。
==mmap()分配的內存除了映射進了接收方進程裏,還映射進了內核空間==。所以調用copy_from_user()將數據拷貝進內核空間也相當於拷貝進了接收方的用戶空間,這就是Binder只需一次拷貝的‘祕密’。
Binder框架中ServiceManager的作用
Binder框架 是基於 C/S 架構的。由一系列的組件組成,包括 Client、Server、ServiceManager、Binder驅動,其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。如下圖所示:
- Server&Client:服務器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。
- ServiceManager(如同DNS域名服務器)服務的管理者,==將Binder名字轉換爲Client中對該Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用==。
- Binder驅動(如同路由器):負責進程之間binder通信的建立,計數管理以及數據的傳遞交互等底層支持。
Service Manager是Binder進程間通信的核心之一,它扮演着Binder進程間通信機制上下文管理者的角色,同時負責管理系統的Service組件,並且向Client組件提供獲取Service代理對象的服務。
==Service Manager運行在一個獨立進程中,因此和Client和Service通信也是運用Binder進程間通信機制來交互的==。ServiceManager 和其他進程同樣採用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 實體,其他進程都是 Client,需要通過這個 Binder 的引用來實現 Binder 的註冊,查詢和獲取。ServiceManager 提供的 Binder 比較特殊,它沒有名字也不需要註冊。當一個進程使用 BINDERSETCONTEXT_MGR 命令將自己註冊成 ServiceManager 時 Binder 驅動會自動爲它創建 Binder 實體(這就是那隻預先造好的那隻雞)。其次這個 Binder 實體的引用在所有 Client 中都固定爲 0 而無需通過其它手段獲得。也就是說,一個 Server 想要向 ServiceManager 註冊自己的 Binder 就必須通過這個 0 號引用和 ServiceManager 的 Binder 通信。
==Service Manager主要是負責如下==:
- 打開和映射Binder設備文件;
- 註冊爲Binder上下文管理者;
- 循環等待Client進程請求;
- 管理ServiceManager代理對象;
- Service組件創建,同時需要在註冊ServiceManager;
- 管理Service代理對象;
總結圖
Binder進程間通信模型
Client進程組件、Service進程組件和ServiceManager運行在用戶空間,而Binder驅動程序運行在Linux系統內核空間,其中Client進程組件、Service進程組件和Service Manager均是通過調用Open、mmap和ioctl來訪問虛擬設備文件/dev/binder,從而實現與Binder驅動程序的交互,進而能夠間接地執行進程通信。
Binder就是一種把這四個組件粘合在一起的粘結劑了,其中,核心組件便是Binder驅動程序了,Service Manager提供了輔助管理的功能,Client和Server正是在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。
==Binder具體是如何通信的==?
Binder實體對象、Binder引用對象、Binder本地對象和Binder代理對象交互過程:
- 運行在Client中Binder代理對象通過Binder驅動程序向運行在Server中的Binder本地對象發出一個進程間通信請求,Binder驅動程序接着根據Client傳遞過來的Binder代理對象的句柄值來找到對應的Binder引用對象。
- Binder驅動程序根據前面找到的Binder引用對象找到對應的Binder實體對象,並且創建一個事務來描述該次進程間通信過程。
- Binder驅動程序根據前面找到的Binder實體對象來找到運行在server中的Binder本地對象,並且將Client傳遞過來的通信數據發送給它處理。
- Binder本地對象處理完成Client進程的通信請求之後,就將通信結果返回給Binder驅動程序,Binder驅動程序接着找到前面創建的一個事務。
- Binder驅動程序根據前面找到的事務的相關屬性來找到發出通信請求的Client,並且通知Client將通信結果返回給對應的Binder代理對象處理。
Binder 通信中的代理模式
跨進程通信的過程都有 Binder 驅動的參與,因此在數據流經 Binder 驅動的時候驅動會對數據做一層轉換。==當 A 進程想要獲取 B 進程中的 object 時,驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理對象 objectProxy==,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法並沒有 B 進程中 object 對象那些方法的能力,這些方法只需要把把請求參數交給驅動即可。對於 A 進程來說和直接調用 object 中的方法是一樣的。
當 Binder 驅動接收到 A 進程的消息後,發現這是個 objectProxy 就去查詢自己維護的表單,一查發現這是 B 進程 object 的代理對象。於是就會去通知 B 進程調用 object 的方法,並要求 B 進程把返回結果發給自己。當驅動拿到 B 進程的返回結果後就會轉發給 A 進程,一次通信就完成了。
Binder 的完整定義
- 從進程間通信的角度看,Binder 是一種進程間通信的機制;
- 從 Server 進程的角度看,Binder 指的是 Server 中的 Binder 實體對象;
- 從 Client 進程的角度看,Binder 指的是 Binder 代理對象,是 Binder 實體對象的一個遠程代理;
- 從傳輸過程的角度看,Binder 是一個可以跨進程傳輸的對象;Binder 驅動會對這個跨越進程邊界的對象對一點點特殊處理,自動完成代理對象和本地對象之間的轉換。
https://blog.csdn.net/AndroidStudyDay/article/details/93749470