中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

網(wǎng)站推廣方案途徑網(wǎng)站設(shè)計(jì)公司怎么樣

網(wǎng)站推廣方案途徑,網(wǎng)站設(shè)計(jì)公司怎么樣,網(wǎng)絡(luò)營(yíng)銷整合推廣,微營(yíng)銷是什么前言 我們經(jīng)常開發(fā)過程中經(jīng)常會(huì)聽到線程和進(jìn)程,在講述Android進(jìn)程多進(jìn)程前我打算先簡(jiǎn)單梳理一下這倆者。 了解什么是進(jìn)程與線程 進(jìn)程: 系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,某個(gè)程序一旦運(yùn)行就是一個(gè)進(jìn)程,是資源分配的最小單位&#…

前言

我們經(jīng)常開發(fā)過程中經(jīng)常會(huì)聽到線程和進(jìn)程,在講述Android進(jìn)程多進(jìn)程前我打算先簡(jiǎn)單梳理一下這倆者。

了解什么是進(jìn)程與線程

  1. 進(jìn)程: 系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,某個(gè)程序一旦運(yùn)行就是一個(gè)進(jìn)程,是資源分配的最小單位;
  2. 線程: 程序執(zhí)行的最小單位,包含在進(jìn)程中,一個(gè)進(jìn)程可以包含多個(gè)線程。

Android 應(yīng)用中的多進(jìn)程

1.dalivk虛擬機(jī)

Android 系統(tǒng)的底層任務(wù)管理以及驅(qū)動(dòng)都是基于 Linux 系統(tǒng);一個(gè) Android 系統(tǒng)其實(shí)就是一個(gè) Linux 系統(tǒng),通過 adb shell 進(jìn)入連接的手機(jī),就可以看到 Linux 系統(tǒng)的文件系統(tǒng)
在這里插入圖片描述

像在運(yùn)行一個(gè) Java 程序,我們知道 Linux 系統(tǒng)會(huì)啟動(dòng)一個(gè)Java虛擬機(jī)來運(yùn)行該 Java 程序,而 Android 系統(tǒng)是一個(gè)特殊的 Linux 系統(tǒng),當(dāng)啟動(dòng)一個(gè) APP,系統(tǒng)會(huì)為該 APP 分配一個(gè) Linux 進(jìn)程,而該進(jìn)程中就會(huì)有一個(gè) dalivk 虛擬機(jī)(又稱為 DVM )實(shí)例來運(yùn)行該 APP,所以 dalivk 虛擬機(jī)就是用來運(yùn)行 APP 程序

不同的APP運(yùn)行在不同的進(jìn)程中,對(duì)應(yīng)著不同的dalivk虛擬機(jī),就對(duì)應(yīng)著不同的地址空間。反過來在一個(gè)應(yīng)用內(nèi),如果新開一個(gè)進(jìn)程,那么由于系統(tǒng)在新開進(jìn)程的時(shí)候會(huì)分配獨(dú)立的dalivk虛擬機(jī),那么對(duì)于該APP內(nèi)的不同進(jìn)程的實(shí)例是互相獨(dú)立,互不影響。

  1. 每個(gè)進(jìn)程有獨(dú)立的dalivk虛擬機(jī),對(duì)應(yīng)著單獨(dú)的內(nèi)存空間
  2. 一個(gè)應(yīng)用可以有多個(gè)進(jìn)程,就有多個(gè)dalivk虛擬機(jī),對(duì)應(yīng)多個(gè)內(nèi)存空間
  3. 一個(gè)進(jìn)程可以被多個(gè)應(yīng)用訪問,多個(gè)應(yīng)用可以共享該進(jìn)程

2.Linux 系統(tǒng)組成

在 Linux 系統(tǒng)中,虛擬內(nèi)存空間(我理解的就是運(yùn)行軟件程序的空間,是對(duì)物理空間的映射)只有 4G;最高的 1GB(對(duì)應(yīng)虛擬地址0xC0000000到0xFFFFFFFF)被成為內(nèi)核空間,而較低的 3GB(對(duì)應(yīng)虛擬地址0x00000000到0xBFFFFFFF)被成為用戶空間。內(nèi)核空間是Linux內(nèi)核運(yùn)行空間,用戶空間是應(yīng)用程序的運(yùn)行空間。如圖所示:
在這里插入圖片描述

  1. 內(nèi)核空間:可以訪問受保護(hù)的內(nèi)存空間,有訪問底層硬件設(shè)備的所有權(quán)限
  2. 用戶空間:上層應(yīng)用程序和Native層的運(yùn)行空間,用戶空間沒法直接訪
  3. 內(nèi)核空間,需要系統(tǒng)調(diào)用才可以訪問內(nèi)核空間。

不同進(jìn)程之間,用戶空間是不共享,但是內(nèi)核空間是可以共享。

3.Android定義多進(jìn)程的三種方式

默認(rèn)情況下,啟動(dòng)一個(gè)APP,僅僅啟動(dòng)了一個(gè)進(jìn)程,該進(jìn)程名為包名,那如何定義多進(jìn)程呢? Android 提供了一種方式,就是在 AndroidManifest 文件中可以通過 “android:process” 來指定進(jìn)程:

  1. 不指定 process: 默認(rèn)的進(jìn)程,進(jìn)程名為包名
  2. 指定 process,但以":"開頭: 該進(jìn)程為當(dāng)前APP的私有進(jìn)程,不允許其他APP訪問
  3. 指定process,但以小寫字母開頭的字符串: 該進(jìn)程為全局進(jìn)程 ,其他應(yīng)用可設(shè)置相同的shareUID來共享該進(jìn)程

4.為什么要引入多進(jìn)程?

為什么一個(gè) Android 應(yīng)用要引入多進(jìn)程?多進(jìn)程有哪些應(yīng)用場(chǎng)景呢?

通常在下面兩種情況下需要引入多進(jìn)程:

  1. 由于 Android 系統(tǒng)會(huì)限制每個(gè)應(yīng)用的最大內(nèi)存,所以如果一個(gè)應(yīng)用需要更多可用的內(nèi)存時(shí),就需要引入多進(jìn)程,讓某些模塊運(yùn)行在另外的進(jìn)程中,獲取更多的內(nèi)存
  2. 由于不同的應(yīng)用運(yùn)行在不同的進(jìn)程中,但是如果兩個(gè)不同的應(yīng)用之間需要進(jìn)行數(shù)據(jù)通信

5.跨進(jìn)程通信

既然在 Android 中引入了多進(jìn)程,而對(duì)于進(jìn)程的用戶空間不共享,那么多進(jìn)程之間怎么通信呢?

這種多進(jìn)程通信又稱為IPC(Inter Process Communication)

對(duì)于IPC,并不是Android系統(tǒng)特有的,在Linux系統(tǒng)中就存在的跨進(jìn)程通信,在Linux系統(tǒng)中常見的IPC方式有:

  1. 管道Pipe: 在內(nèi)存中創(chuàng)建一個(gè)共享文件,利用共享文件傳遞信息。該共享文件并不是文件系統(tǒng),只存在于內(nèi)存中;只能在一個(gè)方向上流動(dòng)
  2. 信號(hào)Signal: 異步通信。信號(hào)在用戶空間和內(nèi)核空間之間交互,內(nèi)核可利用信號(hào)來通知用戶空間的進(jìn)程發(fā)生哪些系統(tǒng)事件。不適用于信號(hào)交換,適用于過程中斷控制;
  3. 信號(hào)量Semaphore: 控制多個(gè)進(jìn)程對(duì)共享資源的訪問。主要是進(jìn)程間以及同一進(jìn)程不同線程之間的同步手段;
  4. 消息隊(duì)列 Message Queue: 存放在內(nèi)存中并由消息對(duì)了標(biāo)識(shí)符標(biāo)識(shí),允許一個(gè)或多個(gè)進(jìn)程對(duì)它進(jìn)行讀寫消息。信息會(huì)復(fù)制兩次,不適用于頻繁或信息量大的通信
  5. 共享內(nèi)存Shared Memory: 直接讀寫內(nèi)核的一塊內(nèi)存空間。不需要進(jìn)行數(shù)據(jù)拷貝
  6. 套接字Socket: 不同機(jī)器之間進(jìn)程間通信。

除去 Socket,其他的都是基于 Binder 機(jī)制實(shí)現(xiàn)的

6.多進(jìn)程帶來的問題

在進(jìn)程結(jié)構(gòu)中也提到了用戶空間是不共享的,并且每個(gè)進(jìn)程都是對(duì)應(yīng)單獨(dú)的系統(tǒng)堆棧區(qū)、靜態(tài)區(qū)等,那么多進(jìn)程也引入了一些問題:

  • 每個(gè)進(jìn)程都是保持自己?jiǎn)为?dú)的靜態(tài)成員變量和單例
  • 每個(gè)進(jìn)程都是單獨(dú)的進(jìn)程鎖
  • SharedPreferences可靠性下降,不支持并發(fā)寫
  • 對(duì)于單個(gè)APP的多進(jìn)程,就會(huì)創(chuàng)建多個(gè)Application,每個(gè)進(jìn)程都會(huì)擁有己的Application對(duì)象。

好了,到了這里我們對(duì)進(jìn)程和線程應(yīng)該有了基本認(rèn)識(shí),同時(shí)也知道了常用的跨進(jìn)程通信方式,包括Android常用的幾種IPC方式,接下來我們就對(duì)Android常用的幾種IPC方式做一個(gè)深入的了解。

認(rèn)識(shí)IPC

進(jìn)程間通信(InterProcess Communication縮寫IPC)是指在不同進(jìn)程之間傳播或交換信息。進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。它是操作系統(tǒng)動(dòng)態(tài)執(zhí)行的基本單元。

IPC不是Android中所獨(dú)有的,任何一個(gè)操作系統(tǒng)都需要有相應(yīng)的IPC機(jī)制。在Android系統(tǒng)中一個(gè)進(jìn)程會(huì)對(duì)應(yīng)一個(gè)虛擬機(jī)實(shí)例,不同的虛擬機(jī)在內(nèi)存分配上有不同的地址空間,所以:只有在多進(jìn)程的環(huán)境下才需要考慮使用IPC進(jìn)行通訊。Android中的應(yīng)用程序可以為一個(gè)進(jìn)程,也可以配置成多進(jìn)程,每個(gè)進(jìn)程都在自己獨(dú)立的空間中運(yùn)行。

在Android中有兩個(gè)典型的場(chǎng)景:第一種是應(yīng)用自身需要開啟多個(gè)進(jìn)程,比如多模塊應(yīng)用,由于系統(tǒng)設(shè)置了應(yīng)用獲取的最大內(nèi)存限制,為了獲得更多的內(nèi)存空間將不同的模塊放在不同的線程中。另外一種情況就是獲取其他應(yīng)用里的數(shù)據(jù),典型的案例就是獲取通訊錄和短信。

Android中的多進(jìn)程模式

Android默認(rèn)進(jìn)程是運(yùn)行在默認(rèn)名為包名的進(jìn)程中,除非特別指定,所有的組件都運(yùn)行在默認(rèn)進(jìn)程中??梢酝ㄟ^修改AndroidManifest文件,在 application 標(biāo)簽下添加 android:process 屬性可以修改Android默認(rèn)的進(jìn)程名字:

<applicationandroid:name=".MainApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:process="com.test.process"android:theme="@style/Theme.Sunflower"></application>

在Android中只有給四大組件(Activity Service Broadcast ContentProvider)設(shè)置android:process屬性這一種使用多進(jìn)程的方法。

<serviceandroid:name=".service.TestService"android:process=":services" />

上面的代碼為TestService指定了process屬性,為應(yīng)用增加了一個(gè)新的進(jìn)程。當(dāng)TestService 啟動(dòng)時(shí),系統(tǒng)會(huì)為它創(chuàng)建單獨(dú)的進(jìn)程。使用

adb shell ps | grep [包名] 

查看進(jìn)程信息.

注意上面中的XML代碼,process 屬性有兩種設(shè)置方式,一種是如上文中":"開頭后面接進(jìn)程名,一種則是完整的進(jìn)程名,例如android:process=“com.packages.name.services”。前者會(huì)在進(jìn)程名前加上包名(“com.packages.name:services”),且規(guī)定了進(jìn)程為當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它使用統(tǒng)一進(jìn)程。后者則你寫了什么,進(jìn)程名就是什么,且為全局進(jìn)程,其他應(yīng)用也可以使用該進(jìn)程。

雖然開啟多進(jìn)程的方法很簡(jiǎn)單,但是這種看似簡(jiǎn)單的操作稍有不深就會(huì)帶來巨大的問題:

  1. 問題一:多進(jìn)程會(huì)造成Application的多次創(chuàng)建:當(dāng)一個(gè)組件需要運(yùn)行在新的進(jìn)程中時(shí),實(shí)際的創(chuàng)建過程就相當(dāng)于又重新啟動(dòng)了一次應(yīng)用,應(yīng)用啟動(dòng),必然會(huì)創(chuàng)建Application。而運(yùn)行在不同進(jìn)程中的組件,不僅屬于不同的虛擬機(jī),而且其Application也是不同的。

  2. 問題二:多進(jìn)程會(huì)導(dǎo)致靜態(tài)成員和單例完全無效:由于不同進(jìn)程都被分配了獨(dú)立且不同的虛擬機(jī),其在內(nèi)存分配上有這不同的地址空間。這就會(huì)導(dǎo)致在著一個(gè)類的多個(gè)副本,各自修改互不影響。

  3. 問題三:多進(jìn)程模式下,線程的同步機(jī)制也會(huì)失效:因?yàn)椴煌倪M(jìn)程,其線程不所屬同一內(nèi)存,那么無論是對(duì)象鎖還是類鎖,鎖本身都不是同一個(gè)了。

  4. 問題四:多進(jìn)程模式下SharedPreferences風(fēng)險(xiǎn)會(huì)增大:SharedPreferences底層是通過文件讀寫實(shí)現(xiàn)的,并發(fā)操作可能會(huì)造成問題。

總之:在不同進(jìn)程中的組件,如果使用內(nèi)存來通訊,都會(huì)存在隱患或者直接失敗。這就是多進(jìn)程帶來的最主要的影響。

Android中的跨進(jìn)程通訊(IPC)

接下來講解一下多進(jìn)程間通訊的方式。

1:使用Bundle

在Android開發(fā)中,我們通過Intent啟動(dòng)Activity、Service和Receiver都是可以通過Bundle傳遞參數(shù)的。它實(shí)現(xiàn)了Parcelable接口,并以鍵值對(duì)的方式保存數(shù)據(jù)??梢詫⑵湟暈橐粋€(gè)容器,其支持基本數(shù)據(jù)類型(string、int、boolean、byte、float、long、double)以及它們對(duì)應(yīng)的數(shù)據(jù)。當(dāng)需要傳遞對(duì)象或?qū)ο髷?shù)組時(shí),被傳遞的對(duì)象必須實(shí)現(xiàn)Serialiable或Parcelable接口。

接下來我們通過一個(gè)Activity和一個(gè)Service來看一下如何利用Bundle實(shí)現(xiàn)跨進(jìn)程通訊。

首先,讓二者運(yùn)行在不同的進(jìn)程中:

<activity android:name=".process.TestActivity"/>
<serviceandroid:name=".process.MessengerService"android:process=":services" />

在配置文件指定Service的運(yùn)行線程即可。

在通過 bindService(Intent, ServiceConnection,int)為TestActivity綁定服務(wù)時(shí),為intent添加bundle:

val intent = Intent(this, MessengerService::class.java)
val bundle = Bundle()
bundle.putString("name", "Butler")
bundle.putInt("age", 28)
intent.putExtra("message", bundle)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)

在service中的onBind方法接受數(shù)據(jù):

override fun onBind(intent: Intent?): IBinder? {intent?.getBundleExtra("message")?.apply {Log.e("Name is:", "${this.getString("name")}")Log.e("age is:", "${this.getInt("age") }")}return mMessenger.binder
}

運(yùn)行結(jié)果如下:
在這里插入圖片描述
使用方法很簡(jiǎn)單,跟我們平時(shí)使用Bundle沒有什么區(qū)別。

2:使用文件共享

通過共享文件,不同的進(jìn)程可以使用讀寫文件的方式交換數(shù)據(jù)。在Android中,對(duì)文件的并發(fā)讀寫并沒有什么特殊的要求。雖然這可能造成問題,但是卻依然可以幫我們?cè)诓煌M(jìn)程間傳遞數(shù)據(jù)。我們創(chuàng)建一個(gè)新的Activty(FileActivity),并指定其進(jìn)程android:process=“:file”

在TestActivity中寫入文件并實(shí)現(xiàn)跳轉(zhuǎn):

findViewById<Button>(R.id.file).setOnClickListener {val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)coroutineScope.launch {val message = "name:Butler,age:28"try {val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)fileOutputStream.write(message.toByteArray())fileOutputStream.close()} catch (e: Exception) {print(e.message)} finally {startActivity(Intent(this@TestActivity, FileActivity::class.java))}}
}

在FileActivity讀取文件內(nèi)容:

coroutineScope.launch {try {val fileInputStream = openFileInput(FileName)var n = 0val sBuffer = StringBuffer()while (n != -1) {n = fileInputStream.read()val by = n.toChar()sBuffer.append(by)}Log.e("message:","$sBuffer")} catch (e:Exception){print(e.message)} finally {}
}

運(yùn)行結(jié)果如下:
在這里插入圖片描述

注意??:不要使用SharedPreferences去做跨進(jìn)程通訊,原則上它不支持多進(jìn)程。雖然它本質(zhì)上也是一個(gè)文件,但是由于它在應(yīng)用運(yùn)行時(shí)會(huì)再內(nèi)存中加載緩存,然而進(jìn)程間是不能內(nèi)存共享的,每個(gè)進(jìn)程操作的SharedPreferences都會(huì)是一個(gè)單獨(dú)的實(shí)例,這就會(huì)導(dǎo)致安全性問題,這個(gè)問題只能通過多進(jìn)程間其它的通信方式或者是在確保不會(huì)同時(shí)操作SharedPreferences數(shù)據(jù)的前提下使用SharedPreferences來解決。

3:使用ContentProvider

ContentProvider提供一種應(yīng)用管理其自身和其他應(yīng)用所存儲(chǔ)數(shù)據(jù)的能力,并提供與其他應(yīng)用共享這些數(shù)據(jù)的方法。它會(huì)封裝數(shù)據(jù),并提供用于定義數(shù)據(jù)安全性的機(jī)制。無論你是否需要和其他應(yīng)用分享數(shù)據(jù),你都可以使用ContentProvider去訪問這些數(shù)據(jù),雖然當(dāng)無需和其他應(yīng)用共享數(shù)據(jù)時(shí)沒必要使用它。系統(tǒng)預(yù)置了許多ContentProvider,比如通話記錄,通訊錄,信息等,只需要通過ContentProvider就可以拿到這些信息。ContentProvider以一個(gè)或多個(gè)表的形式將數(shù)據(jù)呈現(xiàn)給外部應(yīng)用,這些表與關(guān)系型數(shù)據(jù)庫中的表類似。行表示提供程序收集的某種類型數(shù)據(jù)的實(shí)例,行中的每一列表示為一個(gè)實(shí)例所收集的單個(gè)數(shù)據(jù)。

通常有兩個(gè)典型的使用場(chǎng)景:一種是通過實(shí)現(xiàn)代碼訪問其他應(yīng)用中的現(xiàn)有的ContentProvider用來獲得數(shù)據(jù);另一種是創(chuàng)建新的ContentProvider,與其他應(yīng)用共享數(shù)據(jù)。

下面我們用一個(gè)最簡(jiǎn)單的例子,演示一下使用它進(jìn)行跨進(jìn)程通訊。為了方便演示,我們?cè)谕粋€(gè)應(yīng)用內(nèi)進(jìn)行:

首先創(chuàng)建一個(gè)ContentProvider。代碼很簡(jiǎn)單,就是一個(gè)使用Room的數(shù)據(jù)持久化,只做了insert和查詢的邏輯處理。

class UserProvider : ContentProvider() {private lateinit var appDatabase: AppDatabaseprivate var userDao: UserDao? = nulloverride fun onCreate(): Boolean {appDatabase =Room.databaseBuilder(context!!, AppDatabase::class.java, "database-provider").build()userDao = appDatabase.userDao()return true}override fun query(uri: Uri,projection: Array<out String>?,selection: String?,selectionArgs: Array<out String>?,sortOrder: String?): Cursor? {return userDao?.getAll()}override fun getType(uri: Uri): String? {return ""}override fun insert(uri: Uri, values: ContentValues?): Uri? {val user = User(System.currentTimeMillis().toInt(),values?.getAsString("name"),values?.getAsInteger("age"))Log.e("-------${user.firstName}", "------------${user.age}")userDao?.insertAll(user)return uri}override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {//不展示return 0}override fun update(uri: Uri,values: ContentValues?,selection: String?,selectionArgs: Array<out String>?): Int {//不展示return 0}
}

接下來在配置文件里配置它,并指明其所在的進(jìn)程process。

<providerandroid:name=".provider.UserProvider"android:authorities="com.hirzy.test.provider"android:permission="com.karl.PROVIDER"android:process=":provider" />

然后創(chuàng)建Activity(ProviderActivity),并讓它運(yùn)行在應(yīng)用的默認(rèn)進(jìn)程里面

class ProviderActivity : AppCompatActivity() {@RequiresApi(Build.VERSION_CODES.O)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_provider)val textName = findViewById<EditText>(R.id.et_name)val textAge = findViewById<EditText>(R.id.et_age)val textView = findViewById<TextView>(R.id.tv)findViewById<Button>(R.id.btn_save).setOnClickListener(){if (textName.text != null && textAge.text != null) {val uri = Uri.parse("content://com.hirzy.test.provider")val contentValue = ContentValues()contentValue.put("name", "${textName.text}")contentValue.put("age", textAge.text.toString().toInt())contentResolver.insert(uri, contentValue)}}findViewById<Button>(R.id.btn_find).setOnClickListener(){textView.text = ""val uri = Uri.parse("content://com.hirzy.test.provider")val query = contentResolver.query(uri, null, null, null)var text = ""while (query?.moveToNext()!!){text += "姓名:${query.getString(1)}  年齡:${query.getInt(2)}\n"}textView.text = text}}
}

運(yùn)行效果如下:
在這里插入圖片描述

可以看到,處于兩個(gè)不同進(jìn)程中的Activity和Provider成功的實(shí)現(xiàn)了數(shù)據(jù)通訊。但是,由于設(shè)計(jì)原因,ContentProvider只支持增刪改查,更像是一個(gè)跨進(jìn)程的數(shù)據(jù)庫。

4:使用Messenger

Messenger是跨進(jìn)程通信的類,通過它可以再不同進(jìn)程中傳遞Message。底層通過AIDL實(shí)現(xiàn),可以理解為是官方為了怕我們使用AIDL太過于繁瑣而提供的一種簡(jiǎn)單的方案。

它是使用也很簡(jiǎn)單,首先創(chuàng)建服務(wù)端Service(MessengerService),并設(shè)置它所在的進(jìn)程

android:process=":services":
class MessengerService : Service() {private val mHandlerThread: HandlerThread = HandlerThread("服務(wù)端")private lateinit var mMessenger: Messengerprivate lateinit var mHandler: Handleroverride fun onCreate() {super.onCreate()mHandlerThread.start()mHandler = object : Handler(mHandlerThread.looper) {override fun handleMessage(msg: Message) {if (msg.what == 0) {val obtain = Message.obtain(msg)obtain.what  = 1obtain.arg2  = 2*obtain.arg1Thread.sleep(2000L)obtain.replyTo.send(obtain)return}super.handleMessage(msg)}}mMessenger = Messenger(mHandler)}
}

接受客戶端傳來的Int值,延遲兩秒后并返回它的倍數(shù)。

接下來在應(yīng)用默認(rèn)進(jìn)程中實(shí)現(xiàn)一下客戶端Activity (MessengerActivity):

class MessengerActivity : AppCompatActivity() {private lateinit var mServiceMessenger: Messengerprivate val mServiceConnection = object : ServiceConnection {override fun onServiceConnected(name: ComponentName?, service: IBinder?) {mServiceMessenger = Messenger(service)}override fun onServiceDisconnected(name: ComponentName?) {}}private val handler: Handler = object : Handler(Looper.getMainLooper()) {override fun handleMessage(msg: Message) {if (msg.what == 1) {Log.e("${currentTime()}--客戶端收到的消息:", "${msg.arg2}")}super.handleMessage(msg)}}private val mClientMessenger: Messenger = Messenger(handler)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val intent = Intent(this, MessengerService::class.java)val bundle = Bundle()bundle.putString("name", "Butler")bundle.putInt("age", 28)intent.putExtra("message", bundle)bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)setContentView(R.layout.activity_main_messenger)findViewById<Button>(R.id.Messenger).setOnClickListener {val nextInt = Random().nextInt(100)Log.e("${currentTime()}--客戶端發(fā)送的消息:", "$nextInt")val message = Message.obtain(handler, 0, nextInt, 0)message.replyTo = mClientMessengermServiceMessenger.send(message)}findViewById<Button>(R.id.file).setOnClickListener {val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)coroutineScope.launch {val message = "name:Butler,age:28"try {val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)fileOutputStream.write(message.toByteArray())fileOutputStream.close()} catch (e: Exception) {print(e.message)} finally {startActivity(Intent(this@MainMessengerActivity, FileActivity::class.java))}}}}override fun onDestroy() {unbindService(mServiceConnection)super.onDestroy()}fun currentTime(): String {val formatter = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss")return formatter.format(Date(System.currentTimeMillis()))}
}

Activity啟動(dòng)時(shí)和MessengerService綁定,點(diǎn)擊按鈕向不同進(jìn)程的服務(wù)發(fā)送數(shù)字并接受新的返回值。

Messenger通過Handler將Message發(fā)送到另一個(gè)進(jìn)程,實(shí)現(xiàn)了進(jìn)程間通信,底層依然是使用了Binder機(jī)制,其本質(zhì)上也是基于AIDL實(shí)現(xiàn)的。

Messenger的AIDL文件(IMessenger)內(nèi)容如下:

/* //device/java/android/android/app/IActivityPendingResult.aidl
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/package android.os;import android.os.Message;/** @hide */
oneway interface IMessenger {void send(in Message msg);
}

不難發(fā)現(xiàn),它的作用就是跨進(jìn)程發(fā)送Message。它的大致流程如下:
在這里插入圖片描述

但是,Messenger也有明顯的缺點(diǎn):首先,服務(wù)端是以串行的方式在處理消息,不太適合用來出來大量的并發(fā)請(qǐng)求。其次,它的主要作用是傳遞消息,不太適合用來跨進(jìn)程調(diào)用方法。

5:使用AIDL

通過上面,我們知道Messenger實(shí)現(xiàn)跨進(jìn)程通訊,其本質(zhì)上也是基于AIDL實(shí)現(xiàn)的,這一節(jié)我們使用AIDL自己實(shí)現(xiàn)一個(gè)Messenger,用來演示AIDL的使用以及穿插者學(xué)習(xí)Messenger的工作原理,為了簡(jiǎn)化代碼。我們并不直接使用Message作為我們傳遞的數(shù)據(jù),而是使用一個(gè)Bean作為傳遞的數(shù)據(jù)。其核心代碼如下:

package com.hirzy.test.aidlimport android.os.Parcel
import android.os.Parcelableclass Bean: Parcelable {var name: String? = nullvar age = 0constructor(parcel: Parcel)  {name = parcel.readString()age = parcel.readInt()}}

注意??:在AIDL中,并不是所有的數(shù)據(jù)類型都是可以使用,它支持一下數(shù)據(jù)類型:

  • 基本數(shù)據(jù)類型
  • ArrayList,且里面的元素必須是被AIDL支持的數(shù)據(jù)類型
  • HashMap,且里面的元素必須是被AIDL支持的數(shù)據(jù)類型
  • 實(shí)現(xiàn)了Parcelable接口的對(duì)象
  • AIDL接口本身

所以我們的Bean類要實(shí)現(xiàn)Parcelable接口。同理,上節(jié)中Messenger中傳遞的Message也是實(shí)現(xiàn)了Parcelable接口,源碼如下:

/** Copyright (C) 2006 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package android.os;import android.compat.annotation.UnsupportedAppUsage;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;import com.android.internal.annotations.VisibleForTesting;/**** Defines a message containing a description and arbitrary data object that can be* sent to a {@link Handler}.  This object contains two extra int fields and an* extra object field that allow you to not do allocations in many cases.** <p class="note">While the constructor of Message is public, the best way to get* one of these is to call {@link #obtain Message.obtain()} or one of the* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull* them from a pool of recycled objects.</p>*/
public final class Message implements Parcelable {/*** User-defined message code so that the recipient can identify* what this message is about. Each {@link Handler} has its own name-space* for message codes, so you do not need to worry about yours conflicting* with other handlers.*/public int what;/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/public int arg1;/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/public int arg2;/*** An arbitrary object to send to the recipient.  When using* {@link Messenger} to send the message across processes this can only* be non-null if it contains a Parcelable of a framework class (not one* implemented by the application).   For other data transfer use* {@link #setData}.** <p>Note that Parcelable objects here are not supported prior to* the {@link android.os.Build.VERSION_CODES#FROYO} release.*/public Object obj;/*** Optional Messenger where replies to this message can be sent.  The* semantics of exactly how this is used are up to the sender and* receiver.*/public Messenger replyTo;/*** Indicates that the uid is not set;** @hide Only for use within the system server.*/public static final int UID_NONE = -1;/*** Optional field indicating the uid that sent the message.  This is* only valid for messages posted by a {@link Messenger}; otherwise,* it will be -1.*/public int sendingUid = UID_NONE;/*** Optional field indicating the uid that caused this message to be enqueued.** @hide Only for use within the system server.*/public int workSourceUid = UID_NONE;/** If set message is in use.* This flag is set when the message is enqueued and remains set while it* is delivered and afterwards when it is recycled.  The flag is only cleared* when a new message is created or obtained since that is the only time that* applications are allowed to modify the contents of the message.** It is an error to attempt to enqueue or recycle a message that is already in use.*//*package*/ static final int FLAG_IN_USE = 1 << 0;/** If set message is asynchronous *//*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;/** Flags to clear in the copyFrom method *//*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;@UnsupportedAppUsage/*package*/ int flags;/*** The targeted delivery time of this message. The time-base is* {@link SystemClock#uptimeMillis}.* @hide Only for use within the tests.*/@UnsupportedAppUsage@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)public long when;/*package*/ Bundle data;@UnsupportedAppUsage/*package*/ Handler target;@UnsupportedAppUsage/*package*/ Runnable callback;// sometimes we store linked lists of these things@UnsupportedAppUsage/*package*/ Message next;/** @hide */public static final Object sPoolSync = new Object();private static Message sPool;private static int sPoolSize = 0;private static final int MAX_POOL_SIZE = 50;private static boolean gCheckRecycle = true;/*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}/*** Same as {@link #obtain()}, but copies the values of an existing* message (including its target) into the new one.* @param orig Original message to copy.* @return A Message object from the global pool.*/public static Message obtain(Message orig) {Message m = obtain();m.what = orig.what;m.arg1 = orig.arg1;m.arg2 = orig.arg2;m.obj = orig.obj;m.replyTo = orig.replyTo;m.sendingUid = orig.sendingUid;m.workSourceUid = orig.workSourceUid;if (orig.data != null) {m.data = new Bundle(orig.data);}m.target = orig.target;m.callback = orig.callback;return m;}/*** Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.* @param h  Handler to assign to the returned Message object's <em>target</em> member.* @return A Message object from the global pool.*/public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;}/*** Same as {@link #obtain(Handler)}, but assigns a callback Runnable on* the Message that is returned.* @param h  Handler to assign to the returned Message object's <em>target</em> member.* @param callback Runnable that will execute when the message is handled.* @return A Message object from the global pool.*/public static Message obtain(Handler h, Runnable callback) {Message m = obtain();m.target = h;m.callback = callback;return m;}/*** Same as {@link #obtain()}, but sets the values for both <em>target</em> and* <em>what</em> members on the Message.* @param h  Value to assign to the <em>target</em> member.* @param what  Value to assign to the <em>what</em> member.* @return A Message object from the global pool.*/public static Message obtain(Handler h, int what) {Message m = obtain();m.target = h;m.what = what;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>* members.* @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param obj  The <em>object</em> method to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what, Object obj) {Message m = obtain();m.target = h;m.what = what;m.obj = obj;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,* <em>arg1</em>, and <em>arg2</em> members.** @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param arg1  The <em>arg1</em> value to set.* @param arg2  The <em>arg2</em> value to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what, int arg1, int arg2) {Message m = obtain();m.target = h;m.what = what;m.arg1 = arg1;m.arg2 = arg2;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,* <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.** @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param arg1  The <em>arg1</em> value to set.* @param arg2  The <em>arg2</em> value to set.* @param obj  The <em>obj</em> value to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what,int arg1, int arg2, Object obj) {Message m = obtain();m.target = h;m.what = what;m.arg1 = arg1;m.arg2 = arg2;m.obj = obj;return m;}/** @hide */public static void updateCheckRecycle(int targetSdkVersion) {if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {gCheckRecycle = false;}}/*** Return a Message instance to the global pool.* <p>* You MUST NOT touch the Message after calling this function because it has* effectively been freed.  It is an error to recycle a message that is currently* enqueued or that is in the process of being delivered to a Handler.* </p>*/public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}/*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/@UnsupportedAppUsagevoid recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}/*** Make this message like o.  Performs a shallow copy of the data field.* Does not copy the linked list fields, nor the timestamp or* target/callback of the original message.*/public void copyFrom(Message o) {this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;this.what = o.what;this.arg1 = o.arg1;this.arg2 = o.arg2;this.obj = o.obj;this.replyTo = o.replyTo;this.sendingUid = o.sendingUid;this.workSourceUid = o.workSourceUid;if (o.data != null) {this.data = (Bundle) o.data.clone();} else {this.data = null;}}/*** Return the targeted delivery time of this message, in milliseconds.*/public long getWhen() {return when;}public void setTarget(Handler target) {this.target = target;}/*** Retrieve the {@link android.os.Handler Handler} implementation that* will receive this message. The object must implement* {@link android.os.Handler#handleMessage(android.os.Message)* Handler.handleMessage()}. Each Handler has its own name-space for* message codes, so you do not need to* worry about yours conflicting with other handlers.*/public Handler getTarget() {return target;}/*** Retrieve callback object that will execute when this message is handled.* This object must implement Runnable. This is called by* the <em>target</em> {@link Handler} that is receiving this Message to* dispatch it.  If* not set, the message will be dispatched to the receiving Handler's* {@link Handler#handleMessage(Message)}.*/public Runnable getCallback() {return callback;}/** @hide */@UnsupportedAppUsagepublic Message setCallback(Runnable r) {callback = r;return this;}/*** Obtains a Bundle of arbitrary data associated with this* event, lazily creating it if necessary. Set this value by calling* {@link #setData(Bundle)}.  Note that when transferring data across* processes via {@link Messenger}, you will need to set your ClassLoader* on the Bundle via {@link Bundle#setClassLoader(ClassLoader)* Bundle.setClassLoader()} so that it can instantiate your objects when* you retrieve them.* @see #peekData()* @see #setData(Bundle)*/public Bundle getData() {if (data == null) {data = new Bundle();}return data;}/*** Like getData(), but does not lazily create the Bundle.  A null* is returned if the Bundle does not already exist.  See* {@link #getData} for further information on this.* @see #getData()* @see #setData(Bundle)*/public Bundle peekData() {return data;}/*** Sets a Bundle of arbitrary data values. Use arg1 and arg2 members* as a lower cost way to send a few simple integer values, if you can.* @see #getData()* @see #peekData()*/public void setData(Bundle data) {this.data = data;}/*** Chainable setter for {@link #what}** @hide*/public Message setWhat(int what) {this.what = what;return this;}/*** Sends this Message to the Handler specified by {@link #getTarget}.* Throws a null pointer exception if this field has not been set.*/public void sendToTarget() {target.sendMessage(this);}/*** Returns true if the message is asynchronous, meaning that it is not* subject to {@link Looper} synchronization barriers.** @return True if the message is asynchronous.** @see #setAsynchronous(boolean)*/public boolean isAsynchronous() {return (flags & FLAG_ASYNCHRONOUS) != 0;}/*** Sets whether the message is asynchronous, meaning that it is not* subject to {@link Looper} synchronization barriers.* <p>* Certain operations, such as view invalidation, may introduce synchronization* barriers into the {@link Looper}'s message queue to prevent subsequent messages* from being delivered until some condition is met.  In the case of view invalidation,* messages which are posted after a call to {@link android.view.View#invalidate}* are suspended by means of a synchronization barrier until the next frame is* ready to be drawn.  The synchronization barrier ensures that the invalidation* request is completely handled before resuming.* </p><p>* Asynchronous messages are exempt from synchronization barriers.  They typically* represent interrupts, input events, and other signals that must be handled independently* even while other work has been suspended.* </p><p>* Note that asynchronous messages may be delivered out of order with respect to* synchronous messages although they are always delivered in order among themselves.* If the relative order of these messages matters then they probably should not be* asynchronous in the first place.  Use with caution.* </p>** @param async True if the message is asynchronous.** @see #isAsynchronous()*/public void setAsynchronous(boolean async) {if (async) {flags |= FLAG_ASYNCHRONOUS;} else {flags &= ~FLAG_ASYNCHRONOUS;}}/*package*/ boolean isInUse() {return ((flags & FLAG_IN_USE) == FLAG_IN_USE);}@UnsupportedAppUsage/*package*/ void markInUse() {flags |= FLAG_IN_USE;}/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).*/public Message() {}@Overridepublic String toString() {return toString(SystemClock.uptimeMillis());}@UnsupportedAppUsageString toString(long now) {StringBuilder b = new StringBuilder();b.append("{ when=");TimeUtils.formatDuration(when - now, b);if (target != null) {if (callback != null) {b.append(" callback=");b.append(callback.getClass().getName());} else {b.append(" what=");b.append(what);}if (arg1 != 0) {b.append(" arg1=");b.append(arg1);}if (arg2 != 0) {b.append(" arg2=");b.append(arg2);}if (obj != null) {b.append(" obj=");b.append(obj);}b.append(" target=");b.append(target.getClass().getName());} else {b.append(" barrier=");b.append(arg1);}b.append(" }");return b.toString();}void dumpDebug(ProtoOutputStream proto, long fieldId) {final long messageToken = proto.start(fieldId);proto.write(MessageProto.WHEN, when);if (target != null) {if (callback != null) {proto.write(MessageProto.CALLBACK, callback.getClass().getName());} else {proto.write(MessageProto.WHAT, what);}if (arg1 != 0) {proto.write(MessageProto.ARG1, arg1);}if (arg2 != 0) {proto.write(MessageProto.ARG2, arg2);}if (obj != null) {proto.write(MessageProto.OBJ, obj.toString());}proto.write(MessageProto.TARGET, target.getClass().getName());} else {proto.write(MessageProto.BARRIER, arg1);}proto.end(messageToken);}public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR= new Parcelable.Creator<Message>() {public Message createFromParcel(Parcel source) {Message msg = Message.obtain();msg.readFromParcel(source);return msg;}public Message[] newArray(int size) {return new Message[size];}};public int describeContents() {return 0;}public void writeToParcel(Parcel dest, int flags) {if (callback != null) {throw new RuntimeException("Can't marshal callbacks across processes.");}dest.writeInt(what);dest.writeInt(arg1);dest.writeInt(arg2);if (obj != null) {try {Parcelable p = (Parcelable)obj;dest.writeInt(1);dest.writeParcelable(p, flags);} catch (ClassCastException e) {throw new RuntimeException("Can't marshal non-Parcelable objects across processes.");}} else {dest.writeInt(0);}dest.writeLong(when);dest.writeBundle(data);Messenger.writeMessengerOrNullToParcel(replyTo, dest);dest.writeInt(sendingUid);dest.writeInt(workSourceUid);}private void readFromParcel(Parcel source) {what = source.readInt();arg1 = source.readInt();arg2 = source.readInt();if (source.readInt() != 0) {obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);}when = source.readLong();data = source.readBundle();replyTo = Messenger.readMessengerOrNullFromParcel(source);sendingUid = source.readInt();workSourceUid = source.readInt();}
}

接下創(chuàng)建AIDL文件BeanAidl.aidl:

package com.hirezy.test.aidl;parcelable Bean;
interface BeanAidl {Bean send(in Bean bean);
}

注意??:自定義的對(duì)象必須import進(jìn)來。除此之外,AIDL中除了基本數(shù)據(jù)類型,其他類型的參數(shù)必須標(biāo)上方向:in、out或者inout:

  • in: 對(duì)象中的變量可以從客戶端傳到服務(wù)端, 而服務(wù)端對(duì)該對(duì)象的修改不會(huì)影響到客戶端.
  • out: 對(duì)象中的變量無法從客戶端傳到服務(wù)端, 服務(wù)端收到的是一個(gè)內(nèi)部變量全為初始值的變量, 但是服務(wù)端對(duì)該對(duì)象的的修改可以影響到客戶端
  • inout: 對(duì)象中的變量既可以傳到服務(wù)端, 服務(wù)隊(duì)對(duì)其的修改也可以影響到客戶端

接下來創(chuàng)建服務(wù)端代碼AIdlService:

class AIdlService : Service() {override fun onBind(intent: Intent?): IBinder? {return iService.asBinder()}private val iService = object : BeanAidl.Stub() {override fun send(bean: Bean?): Bean {Thread.sleep(5000L)return bean}}
}

我們需要做的是創(chuàng)建一個(gè)BeanAidl.Stub(自動(dòng)生成的代碼,稍后講解)的實(shí)例,并將其作為IBinder返回即可。而在BeanAidl.Stub的具體實(shí)現(xiàn)中,我們實(shí)現(xiàn)了BeanAidl.aidl中的send方法。當(dāng)客戶端通過AIDL調(diào)用send方法時(shí),最終服務(wù)端的send方法會(huì)被調(diào)用。并返回一個(gè)bean對(duì)象。

接下來我們看一下IMessenger中對(duì)應(yīng)的實(shí)現(xiàn)。首先,aidl文件中定義的send方法在Handler有具體實(shí)現(xiàn):

 private final class MessengerImpl extends IMessenger.Stub {public void send(Message msg) {msg.sendingUid = Binder.getCallingUid();Handler.this.sendMessage(msg);}}

可見,它將客戶端的消息,使用Handler又發(fā)了一遍。這就是上文中,我們需要?jiǎng)?chuàng)建Handler,并使用handleMessage來處理客戶端發(fā)送的消息。那么Handler哪里來的呢,又是怎么和Messenger關(guān)聯(lián)了呢?我們接著看MessengerService.kt的代碼:

private val mHandlerThread: HandlerThread = HandlerThread("服務(wù)端")
private lateinit var mMessenger: Messenger
private lateinit var mHandler: Handleroverride fun onCreate() {super.onCreate()mHandlerThread.start()mHandler = object : Handler(mHandlerThread.looper) {override fun handleMessage(msg: Message) {if (msg.what == 0) {val obtain = Message.obtain(msg)obtain.what  = 1obtain.arg2  = 2*obtain.arg1Thread.sleep(2000L)obtain.replyTo.send(obtain)return}super.handleMessage(msg)}}mMessenger = Messenger(mHandler)
}

不難看到,在創(chuàng)建Messenger實(shí)例時(shí),必須傳入一個(gè)Handler對(duì)象。因?yàn)樗挥幸粋€(gè)構(gòu)造方法:

private final IMessenger mTarget;public Messenger(Handler target) {mTarget = target.getIMessenger();
}

而Handler的getIMessenger方法實(shí)現(xiàn)如下:

@UnsupportedAppUsage
final IMessenger getIMessenger() {synchronized (mQueue) {if (mMessenger != null) {return mMessenger;}mMessenger = new MessengerImpl();return mMessenger;}
}

注意里面的MessengerImpl ,正是上文中AIDL的send方法的實(shí)現(xiàn)處。就是那個(gè)Handler里的內(nèi)部類。 而在services的onBinder方法中

public IBinder getBinder() {return mTarget.asBinder();
}

可以明顯看到調(diào)用了asBinder方法。

注意上文中,我們的服務(wù)端代碼會(huì)延時(shí)五秒返回給客戶端信息。此時(shí),我們會(huì)發(fā)現(xiàn),應(yīng)用的頁面被阻塞了,無法響應(yīng)任何事件。而且,有時(shí)還會(huì)觸發(fā)ANR異常。這是因?yàn)?#xff1a;客戶端調(diào)用的方法是運(yùn)行在服務(wù)端的Binder線程池中,這時(shí)客戶端線程會(huì)掛起,此時(shí)如果服務(wù)端方法執(zhí)行比較耗時(shí),會(huì)導(dǎo)致客戶端線程的阻,如果是UI線程的話,就觸發(fā)ANR異常。因此,如果明確知道服務(wù)端要進(jìn)行耗時(shí)操作,一定要在子線程里去訪問服務(wù)端的代碼。然而,服務(wù)端則不同,它本身就是運(yùn)行在Binder線程池中的,所以無需開啟新的子線程,唯一需要注意的就是,當(dāng)多個(gè)客戶端和服務(wù)端通信時(shí)需要保證線程安全。

AIDL同樣支持接口回調(diào)的監(jiān)聽方式。首先創(chuàng)建一個(gè)新的AILD(ChangeListener)

package com.hirezy.test.aidl;parcelable Bean;interface ChangeListener {void onChangeListener(in Bean bean);
}

然后在BeanAidl里增加如下方法:

void sendBean(in Bean bean);
void setListener(in ChangeListener listener);
void removeListener(in ChangeListener listener);

修改AIdlService:

private var listener: ChangeListener? = null
private val iService = object : BeanAidl.Stub() {override fun send(bean: Bean?): Bean {bean!!.age = 2 * bean!!.ageThread.sleep(5000L)listener?.onChangeListener(bean)return bean}override fun sendBean(bean: Bean?) {listener?.onChangeListener(bean)}override fun setListener(on: ChangeListener?) {listener = on}override fun removeListener(on: ChangeListener?) {listener = null}}

客戶端AidlActivity添加如下代碼即可實(shí)現(xiàn)監(jiān)聽:

private val listener = object : ChangeListener.Stub() {override fun onChangeListener(bean: Bean) {textView.text = bean.toString()Log.i("bean","bean info is:${bean.toString()}")}
}

AIDL的工作原理

Android 接口定義語言 (Android Interface Definition Language) 是一款可供用戶用來抽象化IPC的工具,具體通信還是得用Binder來進(jìn)行。以在 .aidl 文件中指定的接口為例,各種構(gòu)建系統(tǒng)都會(huì)使用 aidl 二進(jìn)制文件構(gòu)造 C++ 或 Java 綁定,以便跨進(jìn)程使用該接口。

AIDL 可以在 Android 中的任何進(jìn)程之間使用:在平臺(tái)組件之間使用或在應(yīng)用之間使用均可。但是,AIDL 絕不能用作應(yīng)用的API。

AIDL 使用 Binder 內(nèi)核驅(qū)動(dòng)程序進(jìn)行調(diào)用。當(dāng)發(fā)出調(diào)用時(shí),系統(tǒng)會(huì)將方法標(biāo)識(shí)符和所有對(duì)象打包到某個(gè)緩沖區(qū)中,然后將其復(fù)制到遠(yuǎn)程進(jìn)程,該進(jìn)程中有一個(gè)Binder線程正在等待讀取數(shù)據(jù)。Binder 線程收到某個(gè)事務(wù)的數(shù)據(jù)后,該線程會(huì)在本地進(jìn)程中查找原生樁對(duì)象,然后此類會(huì)解壓縮數(shù)據(jù)并調(diào)用本地接口對(duì)象。此本地接口對(duì)象正是服務(wù)器進(jìn)程所創(chuàng)建和注冊(cè)的對(duì)象。當(dāng)在同一進(jìn)程和同一后端中進(jìn)行調(diào)用時(shí),不存在代理對(duì)象,因此直接調(diào)用即可,無需執(zhí)行任何打包或解壓縮操作。

Binder就是一個(gè)很深的話題,由于它本身很復(fù)雜。這里我們就不過多的探討它的底層原理了。在Android開發(fā)中,Binder主要用在Service中,其中普通Service中的Binder不涉及進(jìn)程間通信。這里主要講跨進(jìn)程的只是,在AIDL和Messenger中Messenger的底層其實(shí)是AIDL,所以用AIDL來探究一下Binder的工作機(jī)制。

我們新建一個(gè)最簡(jiǎn)單的AIDL(TestAidl)文件

package com.hirezy.aidl;interface TestAidl {void fun(int val);void func(int val);
}

此時(shí),SDK會(huì)為我們自動(dòng)生成AIDL所生成的Binder類,它處于build/generated目錄下:

/** This file is auto-generated.  DO NOT MODIFY.*/
package com.hirezy.aidl;
public interface TestAidl extends android.os.IInterface
{/** Default implementation for TestAidl. */public static class Default implements com.hirezy.aidl.TestAidl{@Override public void fun(int val) throws android.os.RemoteException{}@Override public void func(int val) throws android.os.RemoteException{}@Overridepublic android.os.IBinder asBinder() {return null;}}/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.hirezy.aidl.TestAidl{private static final java.lang.String DESCRIPTOR = "com.hirezy.aidl.TestAidl";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.hirezy.aidl.TestAidl interface,* generating a proxy if needed.*/public static com.hirezy.aidl.TestAidl asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.hirezy.aidl.TestAidl))) {return ((com.hirezy.aidl.TestAidl)iin);}return new com.hirezy.aidl.TestAidl.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{java.lang.String descriptor = DESCRIPTOR;switch (code){case INTERFACE_TRANSACTION:{reply.writeString(descriptor);return true;}case TRANSACTION_fun:{data.enforceInterface(descriptor);int _arg0;_arg0 = data.readInt();this.fun(_arg0);reply.writeNoException();return true;}case TRANSACTION_func:{data.enforceInterface(descriptor);int _arg0;_arg0 = data.readInt();this.func(_arg0);reply.writeNoException();return true;}default:{return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.hirezy.aidl.TestAidl{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 void fun(int val) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(val);boolean _status = mRemote.transact(Stub.TRANSACTION_fun, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {getDefaultImpl().fun(val);return;}_reply.readException();}finally {_reply.recycle();_data.recycle();}}@Override public void func(int val) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(val);boolean _status = mRemote.transact(Stub.TRANSACTION_func, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {getDefaultImpl().func(val);return;}_reply.readException();}finally {_reply.recycle();_data.recycle();}}public static com.hirezy.aidl.TestAidl sDefaultImpl;}static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);public static boolean setDefaultImpl(com.hirezy.aidl.TestAidl impl) {if (Stub.Proxy.sDefaultImpl == null && impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.hirezy.aidl.TestAidl getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}public void fun(int val) throws android.os.RemoteException;public void func(int val) throws android.os.RemoteException;
}

上面的代碼是系統(tǒng)根據(jù)DemoAidl.aidl生成的。它是一個(gè)實(shí)現(xiàn)了IInterface這個(gè)接口的接口,要想使用Binder機(jī)制實(shí)現(xiàn)跨進(jìn)程通訊就必須實(shí)現(xiàn)該接口。這個(gè)類主要有結(jié)構(gòu)如下:

首先,聲明兩個(gè)方法fun(int val)和func(int val),也就是我們?cè)贏IDL文件中定義的兩個(gè)方法。同時(shí),聲明了兩個(gè)靜態(tài)常量:

static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

標(biāo)識(shí)這兩個(gè)方法。目的是為了在onTransact中標(biāo)識(shí)到底是哪個(gè)方法被調(diào)用。

接著,定義了一個(gè)內(nèi)部類Stub,它繼承了Binder并實(shí)現(xiàn)了DemoAidl這個(gè)接口,很明顯它就是Binder。同時(shí),這個(gè)內(nèi)部類里還有一個(gè)代理類Proxy。當(dāng)客戶端和服務(wù)端在同一個(gè)進(jìn)程內(nèi)時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的Transact過程。當(dāng)不在一個(gè)進(jìn)程內(nèi)時(shí),就會(huì)通過內(nèi)部的代理類來完成。那么它是怎么判斷呢?

我們看一下其asInterface的代碼

public static com.hirezy.test.aidl.BeanAidl asInterface(android.os.IBinder obj)
{if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.karl.butler.aidl.BeanAidl))) {return ((com.hirezy.test.aidl.BeanAidl)iin);}return new com.hirezy.test.aidl.BeanAidl.Stub.Proxy(obj);
}

可以看到,它是通過queryLocalInterface首先判斷本地是否有對(duì)應(yīng)的Binder對(duì)象。也就是當(dāng)前線程內(nèi)是否有,如果有,則說明客戶端和服務(wù)端位于同一進(jìn)程,那么此方法返回的就是服務(wù)端的Stub對(duì)象本身。否則,就是在不同的進(jìn)程內(nèi),返回的是Stub.proxy對(duì)象。

接著看一下onTransact方法,它是運(yùn)行在服務(wù)端的Binder線程池中的,當(dāng)客戶端發(fā)起跨進(jìn)程請(qǐng)求時(shí),遠(yuǎn)程請(qǐng)求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。

最后看一下Proxy中的fun和func方法:方法運(yùn)行在客戶端,當(dāng)方法被調(diào)用時(shí),首先準(zhǔn)備數(shù)據(jù),然后發(fā)起遠(yuǎn)程請(qǐng)求并掛起當(dāng)前線程,經(jīng)過底層驅(qū)動(dòng)調(diào)用度無端的onTransact方法會(huì)。返回結(jié)果后,當(dāng)前線程繼續(xù)執(zhí)行。

流程大致如下:
在這里插入圖片描述
其實(shí),AIDL可以理解為Binder規(guī)定的一種規(guī)范。是為了便于我們開發(fā)而用來自動(dòng)生成代碼的工具。我們完全可以自己來寫代碼,而無需借助AIDL文件完成Binder。

Binder實(shí)現(xiàn)原理

Binder 是一種進(jìn)程間通信機(jī)制,基于開源的 OpenBinder 實(shí)現(xiàn)。

  • Binder是一種CS架構(gòu)的進(jìn)程間通訊機(jī)制(機(jī)制)
  • 一個(gè)虛擬的物理設(shè)備(驅(qū)動(dòng))
  • 在應(yīng)用層是一個(gè)能發(fā)起通訊的Java類。

當(dāng)應(yīng)用中使用多進(jìn)程通訊時(shí)就需要Binder。而由于虛擬機(jī)分配給各個(gè)進(jìn)程的運(yùn)行內(nèi)存是有限制的,同時(shí)LMK也會(huì)優(yōu)先回收占用資源較多的進(jìn)程。所以我們可以運(yùn)用多進(jìn)程:

  • 規(guī)避內(nèi)存限制,如大量占用內(nèi)存的圖庫
  • 提高穩(wěn)定性,使用獨(dú)立的進(jìn)程保持長(zhǎng)連接
  • 規(guī)避內(nèi)存泄露,獨(dú)立的webView進(jìn)程
  • 規(guī)避風(fēng)險(xiǎn),使用獨(dú)立進(jìn)程運(yùn)行高風(fēng)險(xiǎn)的功能,避免影響主業(yè)務(wù)

同時(shí),Android應(yīng)用是有四大組件(Activity、Service、Broadcast Receiver 和 Content Provide)組成的。有時(shí)這些組件運(yùn)行在同一進(jìn)程,有時(shí)運(yùn)行在不同的進(jìn)程。這些進(jìn)程間的通信就依賴于 Binder IPC 機(jī)制。同時(shí),android中的AMS、PMS都是基于Binder機(jī)制來實(shí)現(xiàn)的。所以,組件的啟動(dòng)流程、通信機(jī)制以及AIDL都離不開Binder。

先了解一下系統(tǒng)中的內(nèi)存劃分。操作系統(tǒng)會(huì)將內(nèi)存劃分為兩塊:

  • 用戶空間:用戶程序代碼運(yùn)行的地方,應(yīng)用獨(dú)享
  • 內(nèi)核空間:內(nèi)核代碼運(yùn)行的空間,所有進(jìn)程共享

為了確保安全性,它們是隔離開的。而且不同進(jìn)程之間也是隔離開的,內(nèi)存是不共享的,無法直接訪問不同進(jìn)程間的數(shù)據(jù)。

不同進(jìn)程之間依賴內(nèi)核空間作為橋梁進(jìn)行通訊:通過將A進(jìn)程中的數(shù)據(jù)copy到內(nèi)核空間(copy from user),再由內(nèi)核空間copy到B進(jìn)程中(copy to user),實(shí)現(xiàn)A進(jìn)程和B進(jìn)程之間的通訊。本質(zhì)上是:通過內(nèi)核內(nèi)存空間,拷貝賦值來完成進(jìn)程間的數(shù)據(jù)傳輸

在這里插入圖片描述
Linux提供了管道(Handler使用了管道機(jī)制+epoll)、信號(hào)量、 消息隊(duì)列、共享內(nèi)存(Fresco)和 Socket 等 IPC 機(jī)制。而Binder相比它們具有以下優(yōu)勢(shì):

  • Binder 只需要一次數(shù)據(jù)拷貝,性能上僅次于共享內(nèi)存,而其他的則需拷貝兩次。
  • Binder 基于 C/S 架構(gòu),內(nèi)存共享機(jī)制需要自行管理和控制并發(fā)和同步等,穩(wěn)定性和便捷性不如Binder,且所有進(jìn)程都可以訪問,安全性低。
  • Binder通過在內(nèi)核中獲取PID添加身份標(biāo)識(shí),而非程序自己生成,安全性更高。
  • Binder是一對(duì)多的關(guān)系,一個(gè)服務(wù)端可以有多個(gè)客戶端與之通訊,而管道只支持一對(duì)一的通訊方式。

在Linux中,使用的虛擬內(nèi)存尋址方式:

  1. 用戶空間的虛擬內(nèi)存地址是映射到物理內(nèi)存中的
  2. 對(duì)虛擬內(nèi)存的讀寫實(shí)際上是對(duì)物理內(nèi)存的讀寫,通過系統(tǒng)調(diào)用mmap()來實(shí)現(xiàn)內(nèi)存映射。

Binder借助了內(nèi)存映射的方法,在內(nèi)核空間和接收方用戶空間的數(shù)據(jù)緩存區(qū)之間做了一層內(nèi)存映射,就相當(dāng)于直接拷貝到了接收方用戶空間的數(shù)據(jù)緩存區(qū),從而減少了一次數(shù)據(jù)拷貝。

在這里插入圖片描述
由于個(gè)人對(duì)Binder的了解也不多,這里只對(duì)其基本概念做一個(gè)簡(jiǎn)單的描述。想更深入的學(xué)習(xí)Binder機(jī)制,推薦去看:Android Binder設(shè)計(jì)與實(shí)現(xiàn) - 設(shè)計(jì)篇,介紹的非常詳細(xì)。

總結(jié)

我認(rèn)識(shí)了進(jìn)程和線程的區(qū)別

  1. 進(jìn)程: 系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,某個(gè)程序一旦運(yùn)行就是一個(gè)進(jìn)程,是資源分配的最小單位;
  2. 線程: 程序執(zhí)行的最小單位,包含在進(jìn)程中,一個(gè)進(jìn)程可以包含多個(gè)線程。

也知道了常用了Android IPC通信方式

  1. Bundle: 實(shí)現(xiàn)了Parcelable接口,常用于Activity、Service、
  2. BroadcastReceive之間的通信
  3. 文件共享: 常用于無并發(fā),交換實(shí)時(shí)性不高的數(shù)據(jù)
  4. Messenger: 低并發(fā)的一對(duì)多即時(shí)通信。串行的方式處理Client發(fā)來的消息,只能傳輸數(shù)據(jù),不能方法調(diào)用(RPC)
  5. ContentProvider: 存儲(chǔ)和獲取數(shù)據(jù),不同程序之間共享數(shù)據(jù)。一對(duì)多的數(shù)據(jù)共享
  6. AIDL
  7. Socket: 網(wǎng)絡(luò)數(shù)據(jù)交換

注意??:除去 Socket,其他的都是基于 Binder 機(jī)制實(shí)現(xiàn)的

常見多進(jìn)程通信IPC(Inter Process Communication)有如下幾種

  1. 管道Pipe: 在內(nèi)存中創(chuàng)建一個(gè)共享文件,利用共享文件傳遞信息。該共享文件并不是文件系統(tǒng),只存在于內(nèi)存中;只能在一個(gè)方向上流動(dòng)
  2. 信號(hào)Signal: 異步通信。信號(hào)在用戶空間和內(nèi)核空間之間交互,內(nèi)核可利用信號(hào)來通知用戶空間的進(jìn)程發(fā)生哪些系統(tǒng)事件。不適用于信號(hào)交換,適用于過程中斷控制
  3. 信號(hào)量Semaphore: 控制多個(gè)進(jìn)程對(duì)共享資源的訪問。主要是進(jìn)程間以及同一進(jìn)程不同線程之間的同步手段
  4. 消息隊(duì)列 Message Queue: 存放在內(nèi)存中并由消息對(duì)了標(biāo)識(shí)符標(biāo)識(shí),允許一個(gè)或多個(gè)進(jìn)程對(duì)它進(jìn)行讀寫消息。信息會(huì)復(fù)制兩次,不適用于頻繁或信息量大的通信
  5. 共享內(nèi)存Shared Memory: 直接讀寫內(nèi)核的一塊內(nèi)存空間。不需要進(jìn)行數(shù)據(jù)拷貝
  6. 套接字Socket: 不同機(jī)器之間進(jìn)程間通信。

Android引入多進(jìn)程的目的

  1. 由于 Android 系統(tǒng)會(huì)限制每個(gè)應(yīng)用的最大內(nèi)存,所以如果一個(gè)應(yīng)用需要更多可用的內(nèi)存時(shí),就需要引入多進(jìn)程,讓某些模塊運(yùn)行在另外的進(jìn)程中,獲取更多的內(nèi)存
  2. 由于不同的應(yīng)用運(yùn)行在不同的進(jìn)程中,但是如果兩個(gè)不同的應(yīng)用之間需要進(jìn)行數(shù)據(jù)通信
http://www.risenshineclean.com/news/22031.html

相關(guān)文章:

  • ubuntu apache php mysql wordpress某個(gè)網(wǎng)站seo分析實(shí)例
  • 網(wǎng)頁設(shè)計(jì)站點(diǎn)規(guī)劃蘇州seo營(yíng)銷
  • 免費(fèi)的網(wǎng)頁設(shè)計(jì)成品詳解seo黑帽教學(xué)網(wǎng)
  • 大連省建設(shè)廳網(wǎng)站seo研究中心教程
  • 網(wǎng)站建設(shè)企業(yè) 熊賬號(hào)aso優(yōu)化app推廣
  • 北京網(wǎng)站建設(shè)天下公司網(wǎng)絡(luò)推廣營(yíng)銷方式
  • 如何在建設(shè)銀行網(wǎng)站查驗(yàn)回單全國免費(fèi)發(fā)布信息平臺(tái)
  • 做網(wǎng)站賺幾百萬媒體網(wǎng)站
  • wordpress添加搜索插件北京seo顧問服務(wù)
  • 本網(wǎng)站服務(wù)器設(shè)在美國服務(wù)器保護(hù)友情鏈接交易平臺(tái)源碼
  • 網(wǎng)站備案和服務(wù)器備案嗎北京seo站內(nèi)優(yōu)化
  • 備案號(hào)鏈接工信部網(wǎng)站免費(fèi)創(chuàng)建個(gè)人博客網(wǎng)站
  • 江蘇建設(shè)集團(tuán)有限公司董事長(zhǎng)seo網(wǎng)絡(luò)排名優(yōu)化方法
  • 濟(jì)寧網(wǎng)站建設(shè)神華科技推廣網(wǎng)站多少錢
  • 購物網(wǎng)站建設(shè) 屬于信息系統(tǒng)管理與設(shè)計(jì)么?百度網(wǎng)頁入口
  • 高端網(wǎng)站制作上海軟文素材
  • 網(wǎng)站集成微信登錄seo數(shù)據(jù)
  • 網(wǎng)站建設(shè)建設(shè)百度學(xué)術(shù)論文查重官網(wǎng)
  • wordpress 軍事主題快速網(wǎng)站排名優(yōu)化
  • 云南網(wǎng)站設(shè)計(jì)外包注冊(cè)公司流程和費(fèi)用
  • 萊蕪區(qū)宣傳部網(wǎng)站seo排名優(yōu)化
  • 網(wǎng)站什么也沒動(dòng)怎么不收錄啦免費(fèi)入駐的電商平臺(tái)
  • 怎么做社交網(wǎng)站日本積分榜最新排名
  • 網(wǎng)站還沒上線怎么做品牌推廣友情鏈接的四個(gè)技巧
  • 哪有做網(wǎng)站的公司b站推廣網(wǎng)站入口2023的推廣形式
  • 蚌埠網(wǎng)站制作哪家好如何搭建個(gè)人網(wǎng)站
  • 電商網(wǎng)站前端設(shè)計(jì)方案山東大學(xué)經(jīng)濟(jì)研究院
  • webydo生成的網(wǎng)站能下載代碼嗎廣東seo教程
  • 有誰幫做網(wǎng)站seo排名如何
  • 自己做pc網(wǎng)站建設(shè)如何優(yōu)化關(guān)鍵詞