上篇文章提到当需要扩大应用内存时可以使用多进程,所以IPC也包括同个应用的多进程通信,下面说一下多进程
如何开启多进程
1.Android中开启多进程只有一种方法,便是给四大组件指定android:process属性,除此之外没有其他方法。
2.是通过JNI利用C/C++,调用fork()方法来生成子进程,一般开发者会利用这种方法来做一些daemon进程,来实现防杀保活等效果,但属于特殊情况,不进行考虑。
注意:不能指定某一个线程或者实体类指定其所运行的进程。
多进程分类及优先级
优先级:
前台进程>可见进程>服务进程>后台进程>空进程
前台进程:该进程包含正在与用户进行交互的界面组件,比如一个Activity。在接收关键生命周期方法时会让一个进程临时提升为前台进程,包括任何服务的生命周期方法onCreate()和onDestroy()和任何广播接收器onReceive()方法。这样做确保了这些组件的操作是有效的原子操作,每个组件都能执行完成而不被杀掉。
可见进程:该进程中的组件虽然没有和用户交互,但是仍然可以被看到。activity可见的时候不一定在前台。一个简单的例子是前台的 activity 使用对话框启动了一个新的 activity 或者一个透明 activity 。另一个例子是当调用运行时权限对话框时(事实上它就是一个 activity!)。
服务进程:该进程包含在执行后台操作的服务组件,比如播放音乐的Service。对于许多在后台做处理(如加载数据)而没有立即成为前台服务的应用都属于这种情况。
请特别注意从onStartCommand()返回的常量,如果服务由于内存压力被杀掉,它表示控制什么发生什么:
START_STICKY:希望系统可用的时候自动重启服务,但不关心是否能获得最后一次的 Intent (例如,可以重建自己的状态或者控制自己的 start/stop 生命周期)。
START_REDELIVER_INTENT:是为那些在被杀死之后重启时重新获得 Intent 的服务的,直到用传递给 onStartCommand() 方法的 startId 参数调用stopSelf()为止。这里会使用 Intent 和 startId 作为队列完成工作。
START_NOT_STICKY:用于那些杀掉也没关系的服务。这适合那些管理周期性任务的服务,它们只是等待下一个时间窗口工作。
后台进程:该进程包含的组件没有与用户交互,用户也看不到 Service。在一般操作场景下,设备上的许多内存就是用在这上面的,使可以重新回到之前打开过的某个 activity 。
空进程:没有任何界面组件、服务组件,或触发器组件,只是出于缓存的目的而被保留(为了更加有效地使用内存而不是完全释放掉),只要 Android 需要可以随时杀掉它们。
多进程写法以及说明
1.不写android:process属性,则默认为当前进程。
2.指定属性android:process=”:test”
以":"开始,表示是要在当前的进程名前附加上当前的包名,表示该进程是本应用的私有进程,其他应用不可以和其跑
在同一个进程。
3.指定属性android:process=”.test”
不以":"开始,表示不需附加包名信息,是一个完全的命名。同时该进程是全局进程,其他应用可以通过ShareUID和其跑在同一个进程中。
多进程优点和缺点
优点:
(1)分担主进程的内存压力。当应用越做越大,内存越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。
(2)守护进程,使应用常驻后台,守护进程和主进程之间相互监视,有一方被杀就重新启动它。
例如:即时通讯或者社交应用。
典型用法是在启动一个不可见的轻量级私有进程,在后台收发消息,或者做一些耗时的事情,或者开机启动这个进程,然后做监听等。
缺点:
1.消耗用户的电量。
2.多占用了系统的空间,若所有应用都这样占用,系统内存很容易占满而导致卡顿。
3.应用程序架构会变得复杂,因为要处理多进程之间的通信。
4.Application的多次重建。(每个进程创建都会初始化一次)
5.静态成员的失效,SharedPreferences不支持多进程
6.文件共享问题,SQLite容易被锁
7.断点调试问题。(每个进程的堆栈信息是独立的)
解决方案
1.针对Application的多次重建:
在Application的onCreate中获取进程Id来判断不同进程,然后做不同的事情。
2.针对静态成员的失效:
使用Intent或者aidl等进程通讯方式传递内容,不要用静态或单例模式。
3.针对文件共享问题:
多进程情况下会出现两个进程在同一时刻访问同一个数据库文件的情况。这就可能造成资源的竞争访问,导致诸如数据库损坏、数据丢失等。在多线程的情况下我们有锁机制控制资源的共享,但是在多进程中比较难,虽然有文件锁、排队等机制,但是在Android里很难实现。解决办法就是多进程的时候不并发访问同一个文件,比如子进程涉及到操作数据库,就可以考虑调用主进程进行数据库的操作。
4.SQLite容易被锁的问题
由于每个进程可能会使用各自的SQLOpenHelper实例,如果两个进程同时对数据库操作,则会发生SQLiteDatabaseLockedException等异常。所以可以使用ContentProvider来实现或者使用其他存储方式。
5.针对断点调试问题:
改为同一进程:调试时去掉AndroidManifest.xml中android:process标签,这样保证调试状态下是在同一进程中,堆栈信息是连贯的。待调试完成后,再将标签复原。
什么时间使用多进程
1.常驻后台的应用(音乐类、跑步健身类、手机管家类、即时通讯类等),防止进程被杀。
2.容易OOM的模块(地图模块、大图浏览、自定义WebView等等)这些都是吃内存大户)可以采用多进程,况且如果java heap 申请内存失败,该进程崩溃并不影响主进程的使用。
3.由于多进程有很多弊端,所以具体视业务需求而定。