Android 后臺(tái)任務(wù)型App多進(jìn)程架構(gòu)演化

什么是后臺(tái)任務(wù)型app

類似音樂、錄音機(jī),需要用戶長(zhǎng)時(shí)間在后臺(tái)使用的產(chǎn)品

背景:

筆者之前的項(xiàng)目一直在做跑步app, 用戶的場(chǎng)景是這樣的,用戶開啟跑步模式后,我們需要監(jiān)聽Gps 信號(hào)來統(tǒng)計(jì)用戶的運(yùn)動(dòng)數(shù)據(jù)嘁捷,包括距離,配速显熏,時(shí)間雄嚣。其實(shí)是看似很“簡(jiǎn)單"的用戶場(chǎng)景, 起初筆者也這么認(rèn)為喘蟆,經(jīng)過了一段時(shí)間的迭代完善缓升,現(xiàn)在就來分享一些其中的”不簡(jiǎn)單“。筆者會(huì)從一個(gè)跑步app開發(fā)者的角度分享這樣一個(gè)跑步App的架構(gòu)演化蕴轨。

最初的架構(gòu)

筆者為了盡快實(shí)現(xiàn)產(chǎn)品經(jīng)理的需求港谊,馬不停蹄的完成了app 的最初版,這時(shí)這個(gè)架構(gòu)是這樣的

Activity + Forground Service + Sqlite+Eventbus
其中: Activity 代表UI 層橙弱, Service 代表開啟跑步模式時(shí)啟動(dòng)的forground service,用以記錄運(yùn)動(dòng)數(shù)據(jù)歧寺,Sqlite 代表數(shù)據(jù)的存儲(chǔ)層, eventbus 是一個(gè)事件總線的library,用于模塊間解耦棘脐。

引來的問題

最初版發(fā)出之后斜筐,收到一些用戶反饋,反應(yīng)運(yùn)動(dòng)數(shù)據(jù)里程丟失荆残,記錄不準(zhǔn)奴艾,這樣的問題對(duì)于一款數(shù)據(jù)統(tǒng)計(jì)的運(yùn)動(dòng)app來說是致命的,那么為什么會(huì)有這樣的問題呢内斯?很容易猜到,因?yàn)槲覀僡pp的進(jìn)程被回收了

如何解決

主要做了UI進(jìn)程與Service進(jìn)程分離和一些service毕裉洌活的策略俘闯,主要基于一下兩點(diǎn)原因

  • Android進(jìn)程管理機(jī)制
    這里就不得不提到Android 的對(duì)于進(jìn)程管理的機(jī)制,Android 系統(tǒng)是通過Low Memory Killer 機(jī)制(參考)來管理進(jìn)程的忽冻,對(duì)于進(jìn)程分為幾個(gè)優(yōu)先級(jí):
- native
- persistent
- forground
- visible
- cache

每個(gè)進(jìn)程的優(yōu)先級(jí)取決于系統(tǒng)計(jì)算oom_adj 的值真朗,那么影響oom_adj的因素有哪些呢?主要是進(jìn)程占用內(nèi)存的大小

  • 便于系統(tǒng)回收資源
    對(duì)于跑步這類app而言僧诚,用戶場(chǎng)景很長(zhǎng)時(shí)間是處于后臺(tái)運(yùn)行的狀態(tài)遮婶,前臺(tái)UI只負(fù)責(zé)交互蝗碎,后臺(tái)的service負(fù)責(zé)業(yè)務(wù)的處理,而且UI進(jìn)程的內(nèi)存占遠(yuǎn)大于Sevice的內(nèi)存占用旗扑,所以如果能夠在app切換到后臺(tái)的時(shí)候釋放掉所有的UI資源蹦骑,那么這個(gè)app運(yùn)行時(shí)就能夠 省出大量?jī)?nèi)存。

第二版的修改

基于以上兩點(diǎn)原因臀防, 于是有了第二版的重構(gòu)眠菇,架構(gòu)變成了這樣:

UI進(jìn)程 + Remote進(jìn)程(service 進(jìn)程

那么問題來了,app從單進(jìn)程變成多進(jìn)程會(huì)存在哪些坑呢袱衷?筆者主要遇到了三個(gè)問題

  • 1.進(jìn)程間如何通信
  • 2.兩個(gè)進(jìn)程如何訪問數(shù)據(jù)保證進(jìn)程安全
  • 3.如何保證進(jìn)程安全的操作sharepreference

針對(duì)第一個(gè)問題捎废,多進(jìn)程通信的方式:
1.Broadcast
這種方式的所有通訊協(xié)議都需要放在intent里面發(fā)送和接受,是一種異步的通訊方式致燥,即調(diào)用后無法立刻得到返回結(jié)果登疗。另外還需要在UI和service段都要注冊(cè)receiver才能達(dá)到他們之間的相互通訊。

2.Messager
Messenger的使用 方法比較簡(jiǎn)單嫌蚤,定義一個(gè)Messenger并指定一個(gè)handler作為通訊的接口辐益,在onBind的時(shí)候返回Messenger的getBinder方 法,并在UI利用返回的IBinder也創(chuàng)建一個(gè)Messenger搬葬,他們之間就可以進(jìn)行通訊了荷腊。這種調(diào)用方法也屬于異步調(diào)用

3.ResultReceiver 跨組件的異步通訊急凰,常用于請(qǐng)求-回調(diào)模式.

4.重寫B(tài)inder
這種通過aidl進(jìn)行通信
我們選擇了最后一種方案:
主進(jìn)程通過bindservice 調(diào)起remote 進(jìn)程女仰,并在onServiceConnection時(shí),注冊(cè)一個(gè)remote 進(jìn)程的callback 回調(diào)抡锈,用于監(jiān)聽疾忍,接收remote進(jìn)程的消息。

  • 首先在AndroidManifest.xml 中聲明
<serviceandroid:name=".RemoteService"
android:process=":remote"
android:label="@string/app_name" />
  • 聲明aidl接口
//aidl service 進(jìn)程持有的對(duì)象
interface IRemoteService {
void registerCallback(IRemoteCallback cb);
void unregisterCallback(IRemoteCallback cb);
}
//回調(diào)更新UI進(jìn)程數(shù)據(jù)的接口
interface IRemoteCallback {
void onDataUpdate(double distance,double duration, double pace, double calorie, double velocity);
}
  • 重寫RemoteService Binder
LocalBinder mBinder = new LocalBinder();
IRemoteCallback mCallback;
class LocalBinder extends IRemoteService.Stub {    
  @Override    
  public void registerCallback(IRemoteCallback cb) throws  RemoteException {        
      mCallback = cb;    
  }    
  @Override    
  public void unregisterCallback(IRemoteCallback cb) throws   RemoteException {        
        mCallback = null;  
  }   
  public IBinder asBinder() {        
        return null;   
   }
}
  • 重寫UI進(jìn)程的Binder
public class RemoteCallback extends IRemoteCallback.Stub {    
@Override    
public void onActivityUpdate(final double distance, final double duration, final double pace, final double calorie, final double velocity) throws RemoteException {        
  //do something
    }    
}
  • onServiceConnection 時(shí)將UI 進(jìn)程的binder 注冊(cè)到remote進(jìn)程
@Override
public void onServiceConnected(ComponentName name, IBinder service) {   
 try {       
    mService = IRemoteService.Stub.asInterface(service);        
    mService.registerCallback(mCallback);    
}  catch (RemoteException e) {      
  e.printStackTrace();   
 }
}
@Override
public void onServiceDisconnected(ComponentName name) {    
try {     
     if (mService != null) { 
         mService.unregisterCallback(mCallback);     
     } 
 } catch (RemoteException e) {    
    e.printStackTrace();    
}    
    mService = null;
}

第二個(gè)問題床三,兩個(gè)進(jìn)程如何訪問數(shù)據(jù)保證一致性:ContentProvider
在Sqlite 上層封裝一層ContentProvider
于是現(xiàn)有的架構(gòu)變成了:

UI process: Activity + eventbus
Remote process : Service + ContentProvider + Sqlite + Eventbus

還有第三個(gè)問題:
用戶需求:多個(gè)進(jìn)程需要獲取跑步的狀態(tài)信息一罩,比如跑步中,跑步暫停還是跑步結(jié)束撇簿。
一個(gè)進(jìn)程的時(shí)候使用SharePreference存儲(chǔ)一個(gè)持久化的狀態(tài),分進(jìn)程之后聂渊,開始使用MODE_MULTI_PROCESS, 而后來發(fā)現(xiàn)文檔注釋被廢棄掉了,multi_process 模式下sharepreference工作不會(huì)可靠四瘫,同步數(shù)據(jù)不會(huì)一致汉嗽,如下描述:

SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.

那么如何解決呢?
兩種方案

性能比較
DPreference setString
called 1000 times cost : 375 ms getString
called 1000 times cost : 186 ms
Tray setString
called 1000 times cost : 13699 ms getString
called 1000 times cost : 3496 ms

方案1還有一個(gè)缺點(diǎn)找蜜,如果將老的SharePreference 數(shù)據(jù)遷移到 用sqlite的方式需要全部拷貝饼暑,而方案二天然的避免了這樣的問題,并且讀寫性能更佳,于是采用了方案二
于是架構(gòu)變成了這樣:

UI process: Activity + eventbus
Remote process : Service + (ContentProvider + Sqlite)+ (ContentProvider + SharePreference) + Eventbus

以上就是筆者在多進(jìn)程開發(fā)中遇到的一些問題和解決方案弓叛,希望可以對(duì)大家有所幫助

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末彰居,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撰筷,更是在濱河造成了極大的恐慌陈惰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闭专,死亡現(xiàn)場(chǎng)離奇詭異奴潘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)影钉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門画髓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人平委,你說我怎么就攤上這事奈虾。” “怎么了廉赔?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵肉微,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蜡塌,道長(zhǎng)碉纳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任馏艾,我火速辦了婚禮劳曹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琅摩。我一直安慰自己铁孵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布房资。 她就那樣靜靜地躺著蜕劝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轰异。 梳的紋絲不亂的頭發(fā)上岖沛,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音搭独,去河邊找鬼烫止。 笑死,一個(gè)胖子當(dāng)著我的面吹牛戳稽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惊奇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼互躬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颂郎,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤吼渡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乓序,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寺酪,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年替劈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寄雀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陨献,死狀恐怖盒犹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眨业,我是刑警寧澤急膀,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站龄捡,受9級(jí)特大地震影響卓嫂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聘殖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一晨雳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧就斤,春花似錦悍募、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绷旗,卻和暖如春喜鼓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衔肢。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工庄岖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人角骤。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓隅忿,卻偏偏與公主長(zhǎng)得像心剥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子背桐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,094評(píng)論 25 707
  • 先對(duì)曾經(jīng)點(diǎn)喜歡或者收藏這篇文章的朋友說聲抱歉优烧,因部分原因個(gè)人決定在簡(jiǎn)書停更并轉(zhuǎn)移駐扎到其他平臺(tái)。本想刪除賬號(hào)链峭,可不...
    OCNYang閱讀 5,599評(píng)論 12 175
  • Jianwei's blog 首頁 分類 關(guān)于 歸檔 標(biāo)簽 巧用Android多進(jìn)程畦娄,微信,微博等主流App都在用...
    justCode_閱讀 5,915評(píng)論 1 23
  • 什么是進(jìn)程 進(jìn)程是一個(gè)具有獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)弊仪。 進(jìn)程是一個(gè)“執(zhí)行中的程序”熙卡。程序是一個(gè)沒...
    MrMagicWang閱讀 1,110評(píng)論 1 1
  • 比起故事的開頭,我更喜歡故事的結(jié)局励饵〔蛋看書或者劇的時(shí)候,我總是在看開頭時(shí)就翻到結(jié)局的部分曲横。不是期待happy end...
    胡邊邊同學(xué)閱讀 408評(píng)論 0 2