想當初在第一次拜讀《Android藝術開發探索》時,深感真的是一本很「藝術」的書(由於當初菜的看不懂..),隨着本身的成長和屢次閱讀,從開始的徹底不懂到如今的有所理解、使用和總結,才體會到其中探索的奧妙,如今跟着安卓高級開發的學習路線,進一步學習、總結和梳理知識。java
多進程做爲Android開發者邁向高級開發者的第一關,也使許多初級開發者望而卻步,這也是每一個開發者必經階段,正好筆者在公司的開發項目中也一直使用了多進程,以前只是在使用階段、和平時零散的知識點,而對Binder的原理和理解並不深刻,本文結合最近所看的文章和實際使用,從開發者的角度總結多進程和Binder的使用,即爲本身梳理知識也但願幫助有須要的人。android
android:process=":consume"
android:process="com.alex.kotlin.myapplication.consume"
複製代碼
關於進程間的通訊首先想到的是Binder機制,固然開發中若是使用多進程,那Binder自當是首當其衝要了解和學習的,下文也會重點介紹Binder,在此以前來看看咱們實際開發中使用的、或者能夠跨進程通訊的機制git
public class Book implements Serializable {
private static final long serialVersionUID = 871136882801008L;
String name;
int age;
public Book(String name, int age) {
this.name = name;
this.age = age;
}
}
複製代碼
在儲存數據時只需將對象序列化在磁盤中,在須要使用的地方反序列化便可獲取Java實例,使用過程以下:github
//序列化
val book = Book("Android",20)
val file = File(cacheDir,"f.txt")
val out = ObjectOutputStream(FileOutputStream(file))
out.writeObject(book)
out.close()
//反序列化
val file = File(cacheDir,"f.txt")
val input = ObjectInputStream(FileInputStream(file))
val book: Book = input.readObject() as Book
input.close()
複製代碼
針對上面的serialVersionUID可能有的認爲不設置也可使用,但若是不設置serialVersionUID值,Java對象一樣能夠序列化,可是當Java類改變時,這時若是去反序列化的化就會報錯,由於serialVersionUID是輔助序列化和反序列化的,只有二者的serialVersionUID一致纔可實現反序列化,因此你不指定serialVersionUID時,系統會默認使用當前類的Hash值,當java對象改變時其Hash值也改變了,因此反序列化時就找不到對應的Java類了。面試
對於Parcelable和Serializable的選擇使用:Serializable是Java的序列化接口,使用時開銷大,須要大量的IO操做,Parcelable是Android提供的序列化接口,適合Android效率更高,對於二者的選擇,若是隻是在內存上序列化使用Parcelable,若是須要在磁盤上序列化使用Serializable便可。bash
在網上看了需對關於Binder的文章,有的深刻Binder源碼和底層去分析Binder的源碼和實現,固然這裏面的代碼我是看不懂,本文主要從Android開發的角度,對Binder的通訊的模型和方式作一個介紹,多線程
當Binder收到A進程的請求後,Binder驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來如出一轍的代理對象 objectProxy,這個 objectProxy 具備和 object 一摸同樣的方法,可是這些方法並無 B 進程中 object 對象那些方法的能力,這些方法只須要把把請求參數交給驅動便可;app
而對於進程A卻傻傻不知道它覺得拿到了B 進程中 object 對象,因此直接調用了Object的方法,當 Binder 驅動接收到 A 進程的消息後,發現這是個 objectProxy 就去查詢本身維護的表單,一查發現這是 B 進程 object 的代理對象。因而就會去通知 B 進程調用 object 的方法,並要求 B 進程把返回結果發給本身。當驅動拿到 B 進程的返回結果後就會轉發給 A 進程,一次通訊就完成了,因此中間的代理就只是一個面具和傳輸的媒介。框架
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
複製代碼
實現一個Messenger分爲兩步,即服務端和客戶端的實現ide
//建立Handler
class HandlerService : Handler() {
override fun handleMessage(msg: Message?) {
when (msg?.what) {
MSG_WHAT -> {
Log.e("MyService", "MyServer")
}
else -> super.handleMessage(msg)
}
}
}
//使用Handler實例建立Messenger實例
private val messenger = Messenger(HandlerService())
//服務經過 onBind() 使其返回客戶端
override fun onBind(intent: Intent): IBinder {
return messenger.binder
}
複製代碼
private var messenger: Messenger? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, iBinder: IBinder?) {
messenger = Messenger(iBinder) // 綁定service後初始化 Messenger
}
override fun onServiceDisconnected(p0: ComponentName?) {
messenger = null
}
}
var message = Message.obtain(null, MSG_WHAT, 100,0) // 建立Message
messenger?.send(message) // 發送Message
//輸出結果
07-24 14:00:38.604 18962-18962/com.example.administrator.memory E/MyService:MyServer 100
複製代碼
若服務端想回應客戶端,那客戶端就要像服務端同樣建立一個接受信息的Handler和Messenger實例,在發送Message時使用msg.replyTo將Messenger實例發送給服務端,服務端就可使用此實例迴應客戶端信息;
//客戶端發送Messenger到Service
msg.replyTo = mGetReplyMessenger;
// 在Service端接收客戶端的Messenger
Messenger msg = msg.replyTo;
複製代碼
AIDL 對於進程通訊來講,可能實際在項目中使用的可能更多的仍是AIDL,因此做爲本文的最後也是重點講解,並結合實際的代碼分析多進程的使用,Aidl支持的數據類型:
AIDL的使用分爲三步:AIDL接口建立、服務端、客戶端實現,下面實際代碼分析,咱們作一個簡單的Demo,在主進程中輸入帳戶密碼,而後在服務進程中驗證登錄,並將結果返回調用進程;
import com.alex.kotlin.myapplication.User;
interface ILoginBinder {
void login(String name ,String pasd);
boolean isLogin();
User getUser();
}
複製代碼
上面的Aidl文件中使用了User類,因此在Java代碼中建立User類,但初次以外也要建立User.aidl文件且包名要和Java中的同樣,並在ILoginBinder中導入User文件的包;
package com.alex.kotlin.myapplication;
parcelable User ;
複製代碼
此時點擊MakePeoject系統會自動編譯出AIDL文件對應的java代碼 ILoginBinder類,能夠在build包相應的路徑下能夠查看此類,代碼結構以下:
public interface ILoginBinder extends android.os.IInterface{
.....
public static abstract class Stub extends android.os.Binder implements com.alex.kotlin.myapplication.binder.ILoginBinder{
......
private static class Proxy implements com.alex.kotlin.myapplication.binder.ILoginBinder{
.....
}
......
}
複製代碼
class LoginBinder : ILoginBinder.Stub() {
override fun login(name: String?, pasd: String?) {
Log.e("======","name = $name ; pasd = $pasd")
user = User(name)
}
override fun isLogin(): Boolean {
return user != null
}
override fun getUser(): User? {
return user
}
}
複製代碼
class BinderMangerService : Service() {
val binder = LoginBinder()
override fun onBind(intent: Intent) : IBinder?{
return binder
}
}
複製代碼
設置Service的進程
<service
android:name=".binder.BinderMangerService"
android:process=":service">
</service>
複製代碼
runOnThread {
val intent = Intent(contextWrapper, BinderMangerService::class.java)
contextWrapper.bindService(intent, serviceConnect,Context.BIND_AUTO_CREATE)
binderSuccessCallback?.success()
}
......
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
iBinderManger = IBinderManger.Stub.asInterface(service)
}
複製代碼
此時獲取到IBinderManger的代理類後便可調用方法,下面咱們調用login()方法登錄,查看輸出信息:
2018-12-08 22:24:26.675 349-363/com.alex.kotlin.myapplication:service E/======: name = AAAAA ; pasd = 11111
複製代碼
此時在service進程中收到了默認進成發送的登錄信息,即兩者之間的通訊完成,但服務的鏈接會在某個時機由於某種緣由時斷開,爲了獲取斷開的時間或保持鏈接的穩定性Android提供了Binder鏈接的死亡監聽類IBinder.DeathRecipient,在綁定成功時給獲取的Ibinder綁定IBinder.DeathRecipient實例,在鏈接斷開時會收到死亡回調,咱們能夠斷開鏈接後繼續重連,使用以下:
//建立IBinder.DeathRecipient實例
var deathRecipient : IBinder.DeathRecipient? = null
deathRecipient = IBinder.DeathRecipient {
//斷開鏈接
iBinderManger?.asBinder()?.unlinkToDeath(deathRecipient,0)
iBinderManger = null
//從新鏈接
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
iBinderManger = IBinderManger.Stub.asInterface(service)
//設置死亡監聽
service?.linkToDeath(deathRecipient,0)
countDownLatch.countDown()
}
複製代碼
但此時的通訊是單向的,若是想在登錄成功或失敗的時候通知默認進程,即進程間的回調,覺得兩者處於不一樣進程間,因此普通的接口回調不能知足,此時的接口也必須是跨進程的AIDl接口,因此建立ILoginCallback文件:
interface ILoginCallback {
void loginSuccess();
void loginFailed();
}
複製代碼
在ILoginBinder的文件中添加註冊和解除監聽的方法:
void registerListener(ILoginCallback iLoginCallback);
void unregisterListener(ILoginCallback iLoginCallback);
複製代碼
在ILoginBinder的實現類中實現這兩個方法,這裏須要說明的是Android爲多進程中的接口註冊問題提供了專門的類:RemoteCallbackList,因此在Stub的實現類中建立RemoteCallbackList,並在兩個方法中添加和刪除ILoginCallback的實例
private val remoteCallbackList = RemoteCallbackList<ILoginCallback>()
override fun registerListener(iLoginCallback: ILoginCallback?) {
remoteCallbackList.register(iLoginCallback)
}
override fun unregisterListener(iLoginCallback: ILoginCallback?) {
remoteCallbackList.unregister(iLoginCallback)
}
複製代碼
對於RemoteCallbackList的遍歷也有所不一樣,必須beginBroadcast()和finishBroadcast()的成對使用,下面在登錄成功或失敗後回調接口:
f (name != null && pasd != null){
user = User(name)
val number = remoteCallbackList.beginBroadcast()
for (i in 0 until number){
remoteCallbackList.getBroadcastItem(i).loginSuccess()
}
remoteCallbackList.finishBroadcast()
}else{
val number = remoteCallbackList.beginBroadcast()
for (i in 0 until number){
remoteCallbackList.getBroadcastItem(i).loginFailed()
}
remoteCallbackList.finishBroadcast()
}
複製代碼
在LoginActivity中建立ILoginCallback.Stub的子類,並調用方法註冊接口,
private val loginCallback = object : ILoginCallback.Stub(){
override fun loginSuccess() {
Log.e("======","登錄成功")
}
override fun loginFailed() {
}
}
loginBinder?.registerListener(loginCallback)
複製代碼
此時再次運行結果:
2018-12-08 22:46:48.366 792-810/com.alex.kotlin.myapplication:service E/======: name = AAAAA ; pasd = 11111
2018-12-08 22:46:48.367 747-747/com.alex.kotlin.myapplication:login E/======: 登錄成功
複製代碼
到這裏進程間的相互通訊已經完成了,如今能夠在兩者之間實現數據或邏輯的相互調用,是否是很happy,可是你能夠調用別人也能夠調用,那怎麼讓只有本身才能調用呢?那就用到最後的一點就是Binder的權限驗證
默認狀況下遠程服務任何人均可以鏈接,權限驗證也就是阻攔那些不想讓他鏈接的人,驗證的地方有兩處:
驗證的方式也有兩種:
下面分別使用二者進行服務端的驗證,首先在清單文件中添加自定義權限,並默認聲明此權限
<uses-permission android:name="com.alex.kotlin.myapplication.permissions.BINDER_SERVICE"/>
<permission android:name="com.alex.kotlin.myapplication.permissions.BINDER_SERVICE"
android:protectionLevel="normal"/>
複製代碼
在onBind()中判斷此權限,若是經過則返回Binder實例,不然返回null
override fun onBind(intent: Intent) : IBinder?{
val check = checkCallingOrSelfPermission("com.alex.kotlin.myapplication.permissions.BINDER_SERVICE")
if (check == PackageManager.PERMISSION_DENIED){
return null
}
return binder
}
複製代碼
另外一中就是在服務端的onTransact()中驗證權限和包名,只有兩者都經過返回true,不然返回false
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val check = checkCallingOrSelfPermission("com.alex.kotlin.myapplication.permissions.BINDER_SERVICE")
if (check == PackageManager.PERMISSION_DENIED){
return false
}
val packages = packageManager.getPackagesForUid(Binder.getCallingUid())
if (packages != null && !packages.isEmpty()){
val packageName = packages[0]
if (!packageName.startsWith("com.alex")){
return false
}
}
return super.onTransact(code, data, reply, flags)
}
複製代碼
到此本文的全部內容都介紹完畢了,從安卓開發和使用來講已能知足工做中的需求,文末附上一個Aidl的Demo,以商店購買商品爲例,使用Binder鏈接池實現登錄、售貨員、商店、和消費者四個進程的通訊;
<activity android:name=".ConsumeActivity"
android:process=":consume">
</activity>
<activity
android:name=".LoginActivity"
android:process=":login">
</activity>
<service
android:name=".binder.BinderMangerService"
android:process=":service">
</service>
<activity
android:name=".ProfuctActivity"
android:process=":product">
</activity>
複製代碼