日韩国产精品99成人不卡在线无毒|狠狠躁夜夜爽一级二级精品|亚洲日日噜噜孕妇中文字幕|日韩久草中文三级片

新聞動(dòng)態(tài)

巧用Android多進(jìn)程,微信,微博等主流App都在用

網(wǎng)站建設(shè) 發(fā)布者:cya 2019-12-04 08:36 訪問量:253

作者:nanchen2251

博客:https://juejin.im/post/5d2dc5e95188257b775d3e40


目錄


  1. 前言

  2. 為什么要使用多進(jìn)程?

  3. 為什么需要“跨進(jìn)程通訊”?

  4. 跨進(jìn)程通訊的方式有哪些?

  5. 使用AIDL實(shí)現(xiàn)一個(gè)多進(jìn)程消息推送

  6. 實(shí)現(xiàn)思路

  7. 例子具體實(shí)現(xiàn)

  8. 知其然,知其所以然。

  9. 跨進(jìn)程的回調(diào)接口

  10. DeathRecipient

  11. 權(quán)限驗(yàn)證

  12. 根據(jù)不同進(jìn)程,做不同的初始化工作

  13. 總結(jié)

  14. 結(jié)語(yǔ)


為什么要使用多進(jìn)程


對(duì)于進(jìn)程的概念,來(lái)到這里的都是編程修仙之人,就不再啰嗦了,相信大家倒著、跳著、躺著、各種姿勢(shì)都能背出來(lái)。


相信很多同學(xué)在實(shí)際開發(fā)中,基本都不會(huì)去給app劃分進(jìn)程,而且,在Android中使用多進(jìn)程,還可能需要編寫額外的進(jìn)程通訊代碼,還可能帶來(lái)額外的Bug,這無(wú)疑加大了開發(fā)的工作量,在很多創(chuàng)業(yè)公司中工期也不允許,這導(dǎo)致了整個(gè)app都在一個(gè)進(jìn)程中。


整個(gè)app都在一個(gè)進(jìn)程有什么弊端?

在Android中,虛擬機(jī)分配給各個(gè)進(jìn)程的運(yùn)行內(nèi)存是有限制值的(這個(gè)值可以是32M,48M,64M等,根據(jù)機(jī)型而定),試想一下,如果在app中,增加了一個(gè)很常用的圖片選擇模塊用于上傳圖片或者頭像,加載大量Bitmap會(huì)使app的內(nèi)存占用迅速增加,如果你還把查看過的圖片緩存在了內(nèi)存中,那么OOM的風(fēng)險(xiǎn)將會(huì)大大增加,如果此時(shí)還需要使用WebView加載一波網(wǎng)頁(yè),我就問你怕不怕!


微信,微博等主流app是如何解決這些問題的?

微信移動(dòng)開發(fā)團(tuán)隊(duì)在 《Android內(nèi)存優(yōu)化雜談》 一文中就說(shuō)到:“對(duì)于webview,圖庫(kù)等,由于存在內(nèi)存系統(tǒng)泄露或者占用內(nèi)存過多的問題,我們可以采用單獨(dú)的進(jìn)程。微信當(dāng)前也會(huì)把它們放在單獨(dú)的tools進(jìn)程中”。


下面我們使用adb查看一下微信和微博的進(jìn)程信息(Android 5.0以下版本可直接在“設(shè)置 -> 應(yīng)用程序”相關(guān)條目中查看):

進(jìn)入adb shell后,使用 “ps | grep 條目名稱” 可以過濾出想要查看的進(jìn)程。

可以看到,微信的確有一個(gè)tools進(jìn)程,而新浪微博也有image相關(guān)的進(jìn)程,而且它們當(dāng)中還有好些其它的進(jìn)程,比如微信的push進(jìn)程,微博的remote進(jìn)程等,這里可以看出,他們不單單只是把上述的WebView、圖庫(kù)等放到單獨(dú)的進(jìn)程,還有推送服務(wù)等也是運(yùn)行在獨(dú)立的進(jìn)程中的。一個(gè)消息推送服務(wù),為了保證穩(wěn)定性,可能需要和UI進(jìn)程分離,分離后即使UI進(jìn)程退出、Crash或者出現(xiàn)內(nèi)存消耗過高等情況,仍不影響消息推送服務(wù)。


可見,合理使用多進(jìn)程不僅僅是有多大好處的問題,我個(gè)人認(rèn)為而且是很有必要的。


所以說(shuō),我們最好還是根據(jù)自身情況,考慮一下是否需要拆分進(jìn)程。這也是本文的初衷:給大家提供一個(gè)多進(jìn)程的參考思路,在遇到上述問題和場(chǎng)景的時(shí)候,可以考慮用多進(jìn)程的方法來(lái)解決問題,又或者,在面試的時(shí)候,跟面試官聊到這方面的知識(shí)時(shí)候也不至于尷尬。


為什么需要“跨進(jìn)程通訊”


Android的進(jìn)程與進(jìn)程之間通訊,有些不需要我們額外編寫通訊代碼,例如:把選擇圖片模塊放到獨(dú)立的進(jìn)程,我們?nèi)钥梢允褂胹tartActivityForResult方法,將選中的圖片放到Bundle中,使用Intent傳遞即可。(看到這里,你還不打算把你項(xiàng)目的圖片選擇弄到獨(dú)立進(jìn)程么?


但是對(duì)于把“消息推送Service”放到獨(dú)立的進(jìn)程,這個(gè)業(yè)務(wù)就稍微復(fù)雜點(diǎn)了,這個(gè)時(shí)候可能會(huì)發(fā)生Activity跟Service傳遞對(duì)象,調(diào)用Service方法等一系列復(fù)雜操作。


由于各個(gè)進(jìn)程運(yùn)行在相對(duì)獨(dú)立的內(nèi)存空間,所以它們是不能直接通訊的,因?yàn)槌绦蚶锏淖兞?、?duì)象等初始化后都是具有內(nèi)存地址的,舉個(gè)簡(jiǎn)單的例子,讀取一個(gè)變量的值,本質(zhì)是找到變量的內(nèi)存地址,取出存放的值。不同的進(jìn)程,運(yùn)行在相互獨(dú)立的內(nèi)存(其實(shí)就可以理解為兩個(gè)不同的應(yīng)用程序),顯然不能直接得知對(duì)方變量、對(duì)象的內(nèi)存地址,這樣的話也自然不能訪問對(duì)方的變量,對(duì)象等。此時(shí)兩個(gè)進(jìn)程進(jìn)行交互,就需要使用跨進(jìn)程通訊的方式去實(shí)現(xiàn)。簡(jiǎn)單說(shuō),跨進(jìn)程通訊就是一種讓進(jìn)程與進(jìn)程之間可以進(jìn)行交互的技術(shù)。


跨進(jìn)程的通訊方式有哪些


  1. 四大組件間傳遞Bundle;

  2. 使用文件共享方式,多進(jìn)程讀寫一個(gè)相同的文件,獲取文件內(nèi)容進(jìn)行交互;

  3. 使用Messenger,一種輕量級(jí)的跨進(jìn)程通訊方案,底層使用AIDL實(shí)現(xiàn)(實(shí)現(xiàn)比較簡(jiǎn)單,博主開始本文前也想了一下是否要說(shuō)一下這個(gè)東西,最后還是覺得沒有這個(gè)必要,Google一下就能解決的問題,就不啰嗦了);

  4. 使用AIDL(Android Interface Definition Language),Android接口定義語(yǔ)言,用于定義跨進(jìn)程通訊的接口;

  5. 使用ContentProvider,常用于多進(jìn)程共享數(shù)據(jù),比如系統(tǒng)的相冊(cè),音樂等,我們也可以通過ContentProvider訪問到;

  6. 使用Socket傳輸數(shù)據(jù)。


接下來(lái)本文將重點(diǎn)介紹使用AIDL進(jìn)行多進(jìn)程通訊,因?yàn)锳IDL是Android提供給我們的標(biāo)準(zhǔn)跨進(jìn)程通訊API,非常靈活且強(qiáng)大(貌似面試也經(jīng)常會(huì)問到,但是真正用到的也不多…)。上面所說(shuō)的Messenger也是使用AIDL實(shí)現(xiàn)的一種跨進(jìn)程方式,Messenger顧名思義,就像是一種串行的消息機(jī)制,它是一種輕量級(jí)的IPC方案,可以在不同進(jìn)程中傳遞Message對(duì)象,我們?cè)贛essage中放入需要傳遞的數(shù)據(jù)即可輕松實(shí)現(xiàn)進(jìn)程間通訊。但是當(dāng)我們需要調(diào)用服務(wù)端方法,或者存在并發(fā)請(qǐng)求,那么Messenger就不合適了。而四大組件傳遞Bundle,這個(gè)就不需要解釋了,把需要傳遞的數(shù)據(jù),用Intent封裝起來(lái)傳遞即可,其它方式不在本文的討論范圍。


下面開始對(duì)AIDL的講解,各位道友準(zhǔn)備好渡劫了嗎?


使用AIDL使用一個(gè)跨進(jìn)程消息推送


像圖片選擇這樣的多進(jìn)程需求,可能并不需要我們額外編寫進(jìn)程通訊的代碼,使用四大組件傳輸Bundle就行了,但是像推送服務(wù)這種需求,進(jìn)程與進(jìn)程之間需要高度的交互,此時(shí)就繞不過進(jìn)程通訊這一步了。
下面我們就用即時(shí)聊天軟件為例,手動(dòng)去實(shí)現(xiàn)一個(gè)多進(jìn)程的推送例子,具體需求如下:


  1. UI和消息推送的Service分兩個(gè)進(jìn)程;


  2. UI進(jìn)程用于展示具體的消息數(shù)據(jù),把用戶發(fā)送的消息,傳遞到消息Service,然后發(fā)送到遠(yuǎn)程服務(wù)器;


  3. Service負(fù)責(zé)收發(fā)消息,并和遠(yuǎn)程服務(wù)器保持長(zhǎng)連接,UI進(jìn)程可通過Service發(fā)送消息到遠(yuǎn)程服務(wù)器,Service收到遠(yuǎn)程服務(wù)器消息通知UI進(jìn)程;


  4. 即使UI進(jìn)程退出了,Service仍需要保持運(yùn)行,收取服務(wù)器消息。


實(shí)現(xiàn)思路


先來(lái)整理一下實(shí)現(xiàn)思路:

  1. 創(chuàng)建UI進(jìn)程(下文統(tǒng)稱為客戶端);


  2. 創(chuàng)建消息Service(下文統(tǒng)稱為服務(wù)端);


  3. 把服務(wù)端配置到獨(dú)立的進(jìn)程(AndroidManifest.xml中指定process標(biāo)簽);


  4. 客戶端和服務(wù)端進(jìn)行綁定(bindService);


  5. 讓客戶端和服務(wù)端具備交互的能力。(AIDL使用);


例子具體實(shí)現(xiàn)

為了閱讀方便,下文中代碼將省略非重點(diǎn)部分,可以把本文完整代碼Clone到本地再看文章:

https://github.com/V1sk/AIDL


Step0. AIDL調(diào)用流程概覽


開始之前,我們先來(lái)概括一下使用AIDL進(jìn)行多進(jìn)程調(diào)用的整個(gè)流程:


  1. 客戶端使用bindService方法綁定服務(wù)端;


  2. 服務(wù)端在onBind方法返回Binder對(duì)象;


  3. 客戶端拿到服務(wù)端返回的Binder對(duì)象進(jìn)行跨進(jìn)程方法調(diào)用;

整個(gè)AIDL調(diào)用過程概括起來(lái)就以上3個(gè)步驟,下文中我們使用上面描述的例子,來(lái)逐步分解這些步驟,并講述其中的細(xì)節(jié)。


Step1.客戶端使用bindService方法綁定服務(wù)端


1.1 創(chuàng)建客戶端和服務(wù)端,把服務(wù)端配置到另外的進(jìn)程


  1. 創(chuàng)建客戶端 -> MainActivity;


  2. 創(chuàng)建服務(wù)端 -> MessageService;


  3. 把服務(wù)端配置到另外的進(jìn)程 -> android:process=”:remote”


上面描述的客戶端、服務(wù)端、以及把服務(wù)端配置到另外進(jìn)程,體現(xiàn)在AndroidManifest.xml中,如下所示:

開啟多進(jìn)程的方法很簡(jiǎn)單,只需要給四大組件指定android:process標(biāo)簽。


1.2 綁定MessageService到MainActivity


創(chuàng)建MessageService:

此時(shí)的MessageService就是剛創(chuàng)建的模樣,onBind中返回了null,下一步中我們將返回一個(gè)可操作的對(duì)象給客戶端。

客戶端MainActivity調(diào)用bindService方法綁定MessageService


這一步其實(shí)是屬于Service組件相關(guān)的知識(shí),在這里就比較簡(jiǎn)單地說(shuō)一下,啟動(dòng)服務(wù)可以通過以下兩種方式:


  1. 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags);


  2. 使用startService方法 -> startService(Intent service);


bindService & startService區(qū)別:
使用bindService方式,多個(gè)Client可以同時(shí)bind一個(gè)Service,但是當(dāng)所有Client unbind后,Service會(huì)退出,通常情況下,如果希望和Service交互,一般使用bindService方法,使用onServiceConnected中的IBinder對(duì)象可以和Service進(jìn)行交互,不需要和Service交互的情況下,使用startService方法即可。


正如上面所說(shuō),我們是要和Service交互的,所以我們需要使用bindService方法,但是我們希望unbind后Service仍保持運(yùn)行,這樣的情況下,可以同時(shí)調(diào)用bindService和startService(比如像本例子中的消息服務(wù),退出UI進(jìn)程,Service仍需要接收到消息),代碼如下:

Stpe2.服務(wù)端在onBind方法返回Binder對(duì)象


2.1 首先,什么是Binder?


要說(shuō)Binder,首先要說(shuō)一下IBinder這個(gè)接口,IBinder是遠(yuǎn)程對(duì)象的基礎(chǔ)接口,輕量級(jí)的遠(yuǎn)程過程調(diào)用機(jī)制的核心部分,該接口描述了與遠(yuǎn)程對(duì)象交互的抽象協(xié)議,而Binder實(shí)現(xiàn)了IBinder接口,簡(jiǎn)單說(shuō),Binder就是Android SDK中內(nèi)置的一個(gè)多進(jìn)程通訊實(shí)現(xiàn)類,在使用的時(shí)候,我們不用也不要去實(shí)現(xiàn)IBinder,而是繼承Binder這個(gè)類即可實(shí)現(xiàn)多進(jìn)程通訊。


2.2 其次,這個(gè)需要在onBind方法返回的Binder對(duì)象從何而來(lái)?


在這里就要引出本文中的主題了——AIDL
多進(jìn)程中使用的Binder對(duì)象,一般通過我們定義好的 .adil 接口文件自動(dòng)生成,當(dāng)然你可以走野路子,直接手動(dòng)編寫這個(gè)跨進(jìn)程通訊所需的Binder類,其本質(zhì)無(wú)非就是一個(gè)繼承了Binder的類,鑒于野路子走起來(lái)麻煩,而且都是重復(fù)步驟的工作,Google提供了 AIDL 接口來(lái)幫我們自動(dòng)生成Binder這條正路,下文中我們圍繞 AIDL 這條正路繼續(xù)展開討論(可不能把人給帶偏了是吧)


2.3 定義AIDL接口


很明顯,接下來(lái)我們需要搞一波上面說(shuō)的Binder,讓客戶端可以調(diào)用到服務(wù)端的方法,而這個(gè)Binder又是通過AIDL接口自動(dòng)生成,那我們就先從AIDL搞起,搞之前先看看注意事項(xiàng),以免出事故:


AIDL支持的數(shù)據(jù)類型:


  • Java 編程語(yǔ)言中的所有基本數(shù)據(jù)類型(如 int、long、char、boolean 等等)


  • String和CharSequence


  • Parcelable:實(shí)現(xiàn)了Parcelable接口的對(duì)象


  • List:其中的元素需要被AIDL支持,另一端實(shí)際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 接口


  • Map:其中的元素需要被AIDL支持,包括 key 和 value,另一端實(shí)際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口


其他注意事項(xiàng):


  • 在AIDL中傳遞的對(duì)象,必須實(shí)現(xiàn)Parcelable序列化接口;


  • 在AIDL中傳遞的對(duì)象,需要在類文件相同路徑下,創(chuàng)建同名、但是后綴為.aidl的文件,并在文件中使用parcelable關(guān)鍵字聲明這個(gè)類;


  • 跟普通接口的區(qū)別:只能聲明方法,不能聲明變量;


  • 所有非基礎(chǔ)數(shù)據(jù)類型參數(shù)都需要標(biāo)出數(shù)據(jù)走向的方向標(biāo)記??梢允?in、out 或 inout,基礎(chǔ)數(shù)據(jù)類型默認(rèn)只能是 in,不能是其他方向。


下面繼續(xù)我們的例子,開始對(duì)AIDL的講解~


2.4 創(chuàng)建一個(gè)AIDL接口,接口中提供發(fā)送消息的方法(Android Studio創(chuàng)建AIDL:項(xiàng)目右鍵 -> New -> AIDL -> AIDL File),代碼如下:

一個(gè)比較尷尬的事情,看了很多文章,從來(lái)沒有一篇能說(shuō)清楚in、out、inout這三個(gè)參數(shù)方向的意義,后來(lái)在stackoverflow上找到能理解答案(https://stackoverflow.com/questions/4700225/in-out-inout-in-a-aidl-interface-parameter-value),我翻譯一下大概意思:


被“in”標(biāo)記的參數(shù),就是接收實(shí)際數(shù)據(jù)的參數(shù),這個(gè)跟我們普通參數(shù)傳遞一樣的含義。在AIDL中,“out” 指定了一個(gè)僅用于輸出的參數(shù),換而言之,這個(gè)參數(shù)不關(guān)心調(diào)用方傳遞了什么數(shù)據(jù)過來(lái),但是這個(gè)參數(shù)的值可以在方法被調(diào)用后填充(無(wú)論調(diào)用方傳遞了什么值過來(lái),在方法執(zhí)行的時(shí)候,這個(gè)參數(shù)的初始值總是空的),這就是“out”的含義,僅用于輸出。而“inout”顯然就是“in”和“out”的合體了,輸入和輸出的參數(shù)。區(qū)分“in”、“out”有什么用?這是非常重要的,因?yàn)槊總€(gè)參數(shù)的內(nèi)容必須編組(序列化,傳輸,接收和反序列化)。in/out標(biāo)簽允許Binder跳過編組步驟以獲得更好的性能。


上述的MessageModel為消息的實(shí)體類,該類在AIDL中傳遞,實(shí)現(xiàn)了Parcelable序列化接口,代碼如下:

手動(dòng)實(shí)現(xiàn)Parcelable接口比較麻煩,安利一款A(yù)S自動(dòng)生成插件android-parcelable-intellij-plugin
創(chuàng)建完MessageModel這個(gè)實(shí)體類,別忘了還有一件事要做:”在AIDL中傳遞的對(duì)象,需要在類文件相同路徑下,創(chuàng)建同名、但是后綴為.aidl的文件,并在文件中使用parcelable關(guān)鍵字聲明這個(gè)類“。代碼如下:

對(duì)于沒有接觸過aidl的同學(xué),光說(shuō)就能讓人懵逼,來(lái)看看此時(shí)的項(xiàng)目結(jié)構(gòu)壓壓驚:

我們剛剛新增的3個(gè)文件:

  • MessageSender.aidl -> 定義了發(fā)送消息的方法,會(huì)自動(dòng)生成名為MessageSender.Stub的Binder類,在服務(wù)端實(shí)現(xiàn),返回給客戶端調(diào)用


  • MessageModel.java -> 消息實(shí)體類,由客戶端傳遞到服務(wù)端,實(shí)現(xiàn)了Parcelable序列化


  • MessageModel.aidl -> 聲明了MessageModel可在AIDL中傳遞,放在跟MessageModel.java相同的包路徑下


OK,相信此時(shí)懵逼已解除~


2.5 在服務(wù)端創(chuàng)建MessageSender.aidl這個(gè)AIDL接口自動(dòng)生成的Binder對(duì)象,并返回給客戶端調(diào)用,服務(wù)端MessageService代碼如下:

MessageSender.Stub是Android Studio根據(jù)我們MessageSender.aidl文件自動(dòng)生成的Binder對(duì)象(至于是怎樣生成的,下文會(huì)有答案),我們需要把這個(gè)Binder對(duì)象返回給客戶端。


2.6 客戶端拿到Binder對(duì)象后調(diào)用遠(yuǎn)程方法


調(diào)用步驟如下:


  1. 在客戶端的onServiceConnected方法中,拿到服務(wù)端返回的Binder對(duì)象;


  2. 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl對(duì)應(yīng)的操作接口;


  3. 取得MessageSender對(duì)象后,像普通接口一樣調(diào)用方法即可。


此時(shí)客戶端代碼如下:

在客戶端中我們調(diào)用了MessageSender的sendMessage方法,向服務(wù)端發(fā)送了一條消息,并把生成的MessageModel對(duì)象作為參數(shù)傳遞到了服務(wù)端,最終服務(wù)端打印的結(jié)果如下:

這里有兩點(diǎn)要說(shuō):


  1. 服務(wù)端已經(jīng)接收到客戶端發(fā)送過來(lái)的消息,并正確打印;


  2. 服務(wù)端和客戶端區(qū)分兩個(gè)進(jìn)程,PID不一樣,進(jìn)程名也不一樣;


到這里,我們已經(jīng)完成了最基本的使用AIDL進(jìn)行跨進(jìn)程方法調(diào)用,也是Step.0的整個(gè)細(xì)化過程,可以再回顧一下Step.0,既然已經(jīng)學(xué)會(huì)使用了,接下來(lái)…全劇終。。。

如果寫到這里全劇終,那跟咸魚有什么區(qū)別…


知其然,知其所以然


我們通過上述的調(diào)用流程,看看從客戶端到服務(wù)端,都經(jīng)歷了些什么事,看看Binder的上層是如何工作的,至于Binder的底層,這是一個(gè)非常復(fù)雜的話題,本文不深究。(如果看到這里你又想問什么是Binder的話,請(qǐng)手動(dòng)倒帶往上看…)


我們先來(lái)回顧一下從客戶端發(fā)起的調(diào)用流程:


  1. MessageSender messageSender = MessageSender.Stub.asInterface(service);


  2. messageSender.sendMessage(messageModel);


拋開其它無(wú)關(guān)代碼,客戶端調(diào)跨進(jìn)程方法就這兩個(gè)步驟,而這兩個(gè)步驟都封裝在 MessageSender.aidl 最終生成的 MessageSender.java 源碼(具體路徑為:build目錄下某個(gè)子目錄,自己找,不爽你來(lái)打我啊

關(guān)鍵字: Android多進(jìn)程 跨進(jìn)程通訊 晨展科技

文章連接: http://m.hsjyfc.com.cn/wzjss/631.html

版權(quán)聲明:文章由 晨展科技 整理收集,來(lái)源于互聯(lián)網(wǎng)或者用戶投稿,如有侵權(quán),請(qǐng)聯(lián)系我們,我們會(huì)立即刪除。如轉(zhuǎn)載請(qǐng)保留