Android 后台任务型App多进程架构演化

什么是后台任务型app

类似音乐、录音机,需要用户长时间在后台使用的产品

背景:

笔者之前的项目一直在做跑步app, 用户的场景是这样的,用户开启跑步模式后,我们需要监听Gps 信号来统计用户的运动数据,包括距离,配速,时间。其实是看似很“简单"的用户场景, 起初笔者也这么认为,经过了一段时间的迭代完善,现在就来分享一些其中的”不简单“。笔者会从一个跑步app开发者的角度分享这样一个跑步App的架构演化。

最初的架构

笔者为了尽快实现产品经理的需求,马不停蹄的完成了app 的最初版,这时这个架构是这样的

Activity + Forground Service + Sqlite+Eventbus
其中: Activity 代表UI 层, Service 代表开启跑步模式时启动的forground service,用以记录运动数据,Sqlite 代表数据的存储层, eventbus 是一个事件总线的library,用于模块间解耦。

引来的问题

最初版发出之后,收到一些用户反馈,反应运动数据里程丢失,记录不准,这样的问题对于一款数据统计的运动app来说是致命的,那么为什么会有这样的问题呢?很容易猜到,因为我们app的进程被回收了

如何解决

主要做了UI进程与Service进程分离和一些service保活的策略,主要基于一下两点原因

  • Android进程管理机制
    这里就不得不提到Android 的对于进程管理的机制,Android 系统是通过Low Memory Killer 机制(参考)来管理进程的,对于进程分为几个优先级:
- native
- persistent
- forground
- visible
- cache

每个进程的优先级取决于系统计算oom_adj 的值,那么影响oom_adj的因素有哪些呢?主要是进程占用内存的大小

  • 便于系统回收资源
    对于跑步这类app而言,用户场景很长时间是处于后台运行的状态,前台UI只负责交互,后台的service负责业务的处理,而且UI进程的内存占远大于Sevice的内存占用,所以如果能够在app切换到后台的时候释放掉所有的UI资源,那么这个app运行时就能够 省出大量内存。

第二版的修改

基于以上两点原因, 于是有了第二版的重构,架构变成了这样:

UI进程 + Remote进程(service 进程

那么问题来了,app从单进程变成多进程会存在哪些坑呢?笔者主要遇到了三个问题

  • 1.进程间如何通信
  • 2.两个进程如何访问数据保证进程安全
  • 3.如何保证进程安全的操作sharepreference

针对第一个问题,多进程通信的方式:
1.Broadcast
这种方式的所有通讯协议都需要放在intent里面发送和接受,是一种异步的通讯方式,即调用后无法立刻得到返回结果。另外还需要在UI和service段都要注册receiver才能达到他们之间的相互通讯。

2.Messager
Messenger的使用 方法比较简单,定义一个Messenger并指定一个handler作为通讯的接口,在onBind的时候返回Messenger的getBinder方 法,并在UI利用返回的IBinder也创建一个Messenger,他们之间就可以进行通讯了。这种调用方法也属于异步调用

3.ResultReceiver 跨组件的异步通讯,常用于请求-回调模式.

4.重写Binder
这种通过aidl进行通信
我们选择了最后一种方案:
主进程通过bindservice 调起remote 进程,并在onServiceConnection时,注册一个remote 进程的callback 回调,用于监听,接收remote进程的消息。

  • 首先在AndroidManifest.xml 中声明
<serviceandroid:name=".RemoteService"
android:process=":remote"
android:label="@string/app_name" />
  • 声明aidl接口
//aidl service 进程持有的对象
interface IRemoteService {
void registerCallback(IRemoteCallback cb);
void unregisterCallback(IRemoteCallback cb);
}
//回调更新UI进程数据的接口
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进程的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 时将UI 进程的binder 注册到remote进程
@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;
}

第二个问题,两个进程如何访问数据保证一致性:ContentProvider
在Sqlite 上层封装一层ContentProvider
于是现有的架构变成了:

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

还有第三个问题:
用户需求:多个进程需要获取跑步的状态信息,比如跑步中,跑步暂停还是跑步结束。
一个进程的时候使用SharePreference存储一个持久化的状态,分进程之后,开始使用MODE_MULTI_PROCESS, 而后来发现文档注释被废弃掉了,multi_process 模式下sharepreference工作不会可靠,同步数据不会一致,如下描述:

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还有一个缺点,如果将老的SharePreference 数据迁移到 用sqlite的方式需要全部拷贝,而方案二天然的避免了这样的问题,并且读写性能更佳,于是采用了方案二
于是架构变成了这样:

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

以上就是笔者在多进程开发中遇到的一些问题和解决方案,希望可以对大家有所帮助

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容