新聞動(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 目錄 前言 為什么要使用多進(jìn)程? 為什么需要“跨進(jìn)程通訊”? 跨進(jìn)程通訊的方式有哪些? 使用AIDL實(shí)現(xiàn)一個(gè)多進(jìn)程消息推送 實(shí)現(xiàn)思路 例子具體實(shí)現(xiàn) 知其然,知其所以然。 跨進(jìn)程的回調(diào)接口 DeathRecipient 權(quán)限驗(yàn)證 根據(jù)不同進(jìn)程,做不同的初始化工作 總結(jié) 結(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)程的通訊方式有哪些 四大組件間傳遞Bundle; 使用文件共享方式,多進(jìn)程讀寫一個(gè)相同的文件,獲取文件內(nèi)容進(jìn)行交互; 使用Messenger,一種輕量級(jí)的跨進(jìn)程通訊方案,底層使用AIDL實(shí)現(xiàn)(實(shí)現(xiàn)比較簡(jiǎn)單,博主開始本文前也想了一下是否要說(shuō)一下這個(gè)東西,最后還是覺得沒有這個(gè)必要,Google一下就能解決的問題,就不啰嗦了); 使用AIDL(Android Interface Definition Language),Android接口定義語(yǔ)言,用于定義跨進(jìn)程通訊的接口; 使用ContentProvider,常用于多進(jìn)程共享數(shù)據(jù),比如系統(tǒng)的相冊(cè),音樂等,我們也可以通過ContentProvider訪問到; 使用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)程通訊這一步了。 UI和消息推送的Service分兩個(gè)進(jìn)程; UI進(jìn)程用于展示具體的消息數(shù)據(jù),把用戶發(fā)送的消息,傳遞到消息Service,然后發(fā)送到遠(yuǎn)程服務(wù)器; Service負(fù)責(zé)收發(fā)消息,并和遠(yuǎn)程服務(wù)器保持長(zhǎng)連接,UI進(jìn)程可通過Service發(fā)送消息到遠(yuǎn)程服務(wù)器,Service收到遠(yuǎn)程服務(wù)器消息通知UI進(jìn)程; 即使UI進(jìn)程退出了,Service仍需要保持運(yùn)行,收取服務(wù)器消息。 實(shí)現(xiàn)思路 先來(lái)整理一下實(shí)現(xiàn)思路: 創(chuàng)建UI進(jìn)程(下文統(tǒng)稱為客戶端); 創(chuàng)建消息Service(下文統(tǒng)稱為服務(wù)端); 把服務(wù)端配置到獨(dú)立的進(jìn)程(AndroidManifest.xml中指定process標(biāo)簽); 客戶端和服務(wù)端進(jìn)行綁定(bindService); 讓客戶端和服務(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è)流程: 客戶端使用bindService方法綁定服務(wù)端; 服務(wù)端在onBind方法返回Binder對(duì)象; 客戶端拿到服務(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)程 創(chuàng)建客戶端 -> MainActivity; 創(chuàng)建服務(wù)端 -> MessageService; 把服務(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 使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags); 使用startService方法 -> startService(Intent service); bindService & startService區(qū)別: 正如上面所說(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 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),我翻譯一下大概意思: 上述的MessageModel為消息的實(shí)體類,該類在AIDL中傳遞,實(shí)現(xiàn)了Parcelable序列化接口,代碼如下: 手動(dòng)實(shí)現(xiàn)Parcelable接口比較麻煩,安利一款A(yù)S自動(dòng)生成插件android-parcelable-intellij-plugin 對(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)用步驟如下: 在客戶端的onServiceConnected方法中,拿到服務(wù)端返回的Binder對(duì)象; 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl對(duì)應(yīng)的操作接口; 取得MessageSender對(duì)象后,像普通接口一樣調(diào)用方法即可。 此時(shí)客戶端代碼如下: 在客戶端中我們調(diào)用了MessageSender的sendMessage方法,向服務(wù)端發(fā)送了一條消息,并把生成的MessageModel對(duì)象作為參數(shù)傳遞到了服務(wù)端,最終服務(wù)端打印的結(jié)果如下: 這里有兩點(diǎn)要說(shuō): 服務(wù)端已經(jīng)接收到客戶端發(fā)送過來(lái)的消息,并正確打印; 服務(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)用流程: MessageSender messageSender = MessageSender.Stub.asInterface(service); messageSender.sendMessage(messageModel); 拋開其它無(wú)關(guān)代碼,客戶端調(diào)跨進(jìn)程方法就這兩個(gè)步驟,而這兩個(gè)步驟都封裝在 MessageSender.aidl 最終生成的 MessageSender.java 源碼(具體路徑為:build目錄下某個(gè)子目錄,自己找,不爽你來(lái)打我啊
下面我們就用即時(shí)聊天軟件為例,手動(dòng)去實(shí)現(xiàn)一個(gè)多進(jìn)程的推送例子,具體需求如下:
這一步其實(shí)是屬于Service組件相關(guān)的知識(shí),在這里就比較簡(jiǎn)單地說(shuō)一下,啟動(dòng)服務(wù)可以通過以下兩種方式:
使用bindService方式,多個(gè)Client可以同時(shí)bind一個(gè)Service,但是當(dāng)所有Client unbind后,Service會(huì)退出,通常情況下,如果希望和Service交互,一般使用bindService方法,使用onServiceConnected中的IBinder對(duì)象可以和Service進(jìn)行交互,不需要和Service交互的情況下,使用startService方法即可。
多進(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ù)展開討論(可不能把人給帶偏了是吧)
被“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跳過編組步驟以獲得更好的性能。
創(chuàng)建完MessageModel這個(gè)實(shí)體類,別忘了還有一件事要做:”在AIDL中傳遞的對(duì)象,需要在類文件相同路徑下,創(chuàng)建同名、但是后綴為.aidl的文件,并在文件中使用parcelable關(guān)鍵字聲明這個(gè)類“。代碼如下:
【推薦閱讀】
如何說(shuō)服企業(yè)做網(wǎng)站做出決策?
行業(yè)動(dòng)態(tài)網(wǎng)站設(shè)計(jì)
關(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)保留
晨展解決方案
晨展新聞