Android多进程实现及常见问题
1、为什么需要多个进程?
默认情况下,一个Android应用中所有的组件都会运行在以包名为进程名的单个进程中,但是由于Android自身平台的一些限制或者多进程固有的一些好处,导致很多应用在实现的时候不得不选择多进程的实现方式:
1.1. Android系统对每一个应用进程的内存占用有限制,视具体设备的情况,我的测试机的单个应用的内存限制为128M,比较大了,早期的Android设备由于总的内存大小限制,对单个应用的内存限制的比较小24M或者更小。所以如果应用需要占用很大的内存,可以考虑将一些组件单独运行在独立的进程中,减小OOM和被系统kill的概率。
1.2. 由于每一个进程都是运行在独立的虚拟机中,所以子进程的崩溃不会影响到其它的进程
1.3. 主进程退出后,子进程仍然可以运行,对voip和推送服务类的应用,可以说是必备的
1.4. 还有一些另类的应用用多个进程之间相互监听,防止自己的服务被系统杀死,保证被杀死或者崩溃后能拉起服务
2、android多进程实现方式
说了多进程的那么多好处,在Android中如何实现呢?可通过两种设置process属性的方式实现多进程:
2.1. android:process=":processY" 实际的进程名是包名:processY,这种形式表示该进程为当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中
2.2. android:process="com.pj.sh.processY" 其它应用可以通过设置相同SharedUID且签名完全一致,可以和它跑在同一个进程
代码
<activity android:name=".multiprocess.ProcessXActivity" android:label="@string/title_activity_process_x"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".multiprocess.ProcessYActivity" android:label="@string/title_activity_process_y" android:process=":processY"></activity>
运行结果:
shell@ja3g:/ $ ps | grep com.pj.sh
u0_a191 7078 2604 1875580 61288 ffffffff 00000000 S com.pj.sh
u0_a191 13412 2604 1892492 59280 ffffffff 00000000 S com.pj.sh:processY
2.3. Application也可以设置android:process属性,来修改所有组件运行进程的名称
代码:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:process="application.test">
<activity
android:name=".multiprocess.ProcessXActivity"
android:label="@string/title_activity_process_x">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".multiprocess.ProcessYActivity"
android:label="@string/title_activity_process_y"
android:process=":processY"></activity>
</application>
运行结果:用第一种方式指定的进程的进程名称依然是包名:processY
shell@ja3g:/ $ ps | grep application.test
u0_a191 3142 2604 1877644 60508 ffffffff 00000000 S application.test
shell@ja3g:/ $ ps | grep com.pj.sh
u0_a191 4651 2604 1890428 59232 ffffffff 00000000 S com.pj.sh:processY
shell@ja3g:/ $ ps | grep application.test
u0_a191 3142 2604 1877644 60508 ffffffff 00000000 S application.test
shell@ja3g:/ $ ps | grep com.pj.sh
u0_a191 4651 2604 1890428 59232 ffffffff 00000000 S com.pj.sh:processY
3、多进程常见的问题
多进程是不是就这么简单?有没有其它单进程不会有的问题呢?答案是肯定的,而且经常很头疼。
3.1. 不同的进程会创建不同的Application实例,导致我们在Application中全局初始化资源的时候失效
解决方法:可以在Application的onCreate方法中判断当前运行的进程做不同的处理解决资源重复初始化的问题。
3.2. 单例和静态成员变量失效,不同的进程中获取的单例实例不是同一块内存,静态变量也是不同的副本
代码:
public class SingletonTest {
volatile private static SingletonTest mInstance;
private SingletonTest() {
}
public static SingletonTest getInstance() {
if (mInstance == null) {
synchronized (SingletonTest.class) {
if (mInstance == null) {
mInstance = new SingletonTest();
}
}
}
return mInstance;
}
}
public class ProcessXActivity extends Activity implements View.OnClickListener { private TextView mTXInstance; private Button mBtnGetInstance; private Button mBtnGo2ProcessY; private Button mBtnGetInstanceByThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_process_x); mTXInstance = (TextView) findViewById(R.id.id_tx_singleton); mBtnGetInstance = (Button) findViewById(R.id.id_btn_get_instance); mBtnGo2ProcessY = (Button) findViewById(R.id.id_btn_goto_processy); mBtnGetInstanceByThread = (Button) findViewById(R.id.id_btn_thread_get_instance); mBtnGetInstance.setOnClickListener(this); mBtnGo2ProcessY.setOnClickListener(this); mBtnGetInstanceByThread.setOnClickListener(this); } @Override public void onClick(View v) { switch(v.getId()) { case R.id.id_btn_get_instance: mTXInstance.setText("processX:" + SingletonTest.getInstance()); break; case R.id.id_btn_goto_processy: startActivity(new Intent(this, ProcessYActivity.class)); break; case R.id.id_btn_thread_get_instance: final String tmp = mTXInstance.getText().toString(); new Thread() { @Override public void run() { super.run(); final String tx = tmp + "\n" + "processX:" + Thread.currentThread().getId() + ":" + SingletonTest.getInstance(); runOnUiThread(new Runnable() { @Override public void run() { mTXInstance.setText(tx); } }); } }.start(); break; default: break; } } }
public class ProcessYActivity extends Activity implements View.OnClickListener{ private TextView mTXInstance; private Button mBtnGetInstance; private Button mBtnGo2ProcessX; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_process_y); mTXInstance = (TextView) findViewById(R.id.id_tx_singleton); mBtnGetInstance = (Button) findViewById(R.id.id_btn_get_instance); mBtnGo2ProcessX = (Button) findViewById(R.id.id_btn_goto_processx); mBtnGetInstance.setOnClickListener(this); mBtnGo2ProcessX.setOnClickListener(this); } @Override public void onClick(View v) { switch(v.getId()) { case R.id.id_btn_get_instance: mTXInstance.setText("processY:" + SingletonTest.getInstance()); break; case R.id.id_btn_goto_processx: startActivity(new Intent(this, ProcessXActivity.class)); break; default: break; } } }
运行结果:
3.3. 文件共享问题,线程同步机制失效,SharedPreferences可靠性下降,不支持多进程同时写,有一定的概率丢失数据
多个进程同时读写数据库等共享数据,造成资源竞争,导致数据库损坏或者数据丢失等情况
3.4. 断点调试
多进程给断点调试带来了很大的不便,一般在调试的时候会把多进程的配置先去掉,这样堆栈信息就是连贯的。
这么多的问题,使用多进程的时候一定要小心,特别是第2,3两个问题,笔者曾经犯过类似的错误,写了一个单例的数据库工具类,信心满满的做了线程同步机制,保证不会出现同时读写的情况,保证数据的一致,由于项目需要,后期把应用拆分成三个进程运行,没有考虑到之前写的单例数据库工具类,导致单例失效,数据的一致性也遭到破坏,整体提心吊胆,生怕出现问题,决定对这个问题进行深入研究,如何实现单例在多个进程之间共享?还没有验证,后期单独文章和进程间通讯一起分析这部分内容。