1. 
          

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

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

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

            作者:nanchen2251

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


            目錄


            1. 前言

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

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

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

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

            6. 實(shí)現思路

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

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

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

            10. DeathRecipient

            11. 權限驗證

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

            13. 總結

            14. 結語(yǔ)


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


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


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


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

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


            微信,微博等主流app是如何解決這些問(wèn)題的?

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


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

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

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


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


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


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


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


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


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


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


            1. 四大組件間傳遞Bundle;

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

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

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

            5. 使用ContentProvider,常用于多進(jìn)程共享數據,比如系統的相冊,音樂(lè )等,我們也可以通過(guò)ContentProvider訪(fǎng)問(wèn)到;

            6. 使用Socket傳輸數據。


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


            下面開(kāi)始對AIDL的講解,各位道友準備好渡劫了嗎?


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


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


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


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


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


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


            實(shí)現思路


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

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


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


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


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


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


            例子具體實(shí)現

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

            https://github.com/V1sk/AIDL


            Step0. AIDL調用流程概覽


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


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


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


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

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


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


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


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


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


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


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

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


            1.2 綁定MessageService到MainActivity


            創(chuàng )建MessageService:

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

            客戶(hù)端MainActivity調用bindService方法綁定MessageService


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


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


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


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


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

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


            2.1 首先,什么是Binder?


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


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


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


            2.3 定義AIDL接口


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


            AIDL支持的數據類(lèi)型:


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


            • String和CharSequence


            • Parcelable:實(shí)現了Parcelable接口的對象


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


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


            其他注意事項:


            • 在A(yíng)IDL中傳遞的對象,必須實(shí)現Parcelable序列化接口;


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


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


            • 所有非基礎數據類(lèi)型參數都需要標出數據走向的方向標記??梢允?in、out 或 inout,基礎數據類(lèi)型默認只能是 in,不能是其他方向。


            下面繼續我們的例子,開(kāi)始對AIDL的講解~


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

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


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


            上述的MessageModel為消息的實(shí)體類(lèi),該類(lèi)在A(yíng)IDL中傳遞,實(shí)現了Parcelable序列化接口,代碼如下:

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

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

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

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


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


            • MessageModel.aidl -> 聲明了MessageModel可在A(yíng)IDL中傳遞,放在跟MessageModel.java相同的包路徑下


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


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

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


            2.6 客戶(hù)端拿到Binder對象后調用遠程方法


            調用步驟如下:


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


            2. 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl對應的操作接口;


            3. 取得MessageSender對象后,像普通接口一樣調用方法即可。


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

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

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


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


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


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

            如果寫(xiě)到這里全劇終,那跟咸魚(yú)有什么區別…


            知其然,知其所以然


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


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


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


            2. messageSender.sendMessage(messageModel);


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

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

            文章連接: http://www.gostscript.com/wzjss/631.html

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

            双腿国产亚洲精品无码不卡|国产91精品无码麻豆|97久久久久久久极品|无码人妻少妇久久中文字幕
                1.