Android应用内多进程的介绍
正常情况下,一个apk启动后只会运行在一个进程中,其进程名为AndroidManifest.xml文件中指定的应用包名,所有的基本组件都会在这个进程中运行。但是如果需要将某些组件(如Service、Activity等)运行在单独的进程中,就需要用到android:process属性了。我们可以为android的基础组件指定process属性来指定它们运行在指定进程中。
- AndroidMantifest.xml中的activity、service、receiver和provider均支持android:process属性。
- 设置该属性可以使每个组件均在各自的进程中运行,或者使一些组件共享一个进程。
- AndroidMantifest.xml中的application元素也支持android:process属性,可以修改应用程序的默认进程名(默认值为包名)。
好处
1、我们知道Android系统对每个应用进程的内存占用是有限制的,而且占用内存越大的进程,通常被系统杀死的可能性越大。让一个组件运行在单独的进程中,可以减少主进程所占用的内存,降低被系统杀死的概率。
2、如果子进程因为某种原因崩溃了,不会直接导致主程序的崩溃,可以降低我们程序的崩溃率。
3、即使主进程退出了,我们的子进程仍然可以继续工作,假设子进程是推送服务,在主进程退出的情况下,仍然能够保证用户可以收到推送消息。
Android应用内多进程的实现
只需要添加 android:process 的属性即可,属性名可以自定义,相同的属性名的进程在同一个里面运行。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.processtest"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<application
android:name="com.example.processtest.MyApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".ProcessTestActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".ProcessTestService"
android:process=":remote">
</service>
</application>
</manifest>
1 . 如果android:process的值以冒号开头的话,那么该进程就是私有进程,其他应用的组件不可以和它跑在同一个进程中。
<service android:name=".ProcessTestService" android:process=":secondProcess"/>
2 . 以小写字母开头(如com.secondProcess),那么就是公有进程,android:process值一定要有个点号,,表示运行在一个以这个名字命名的全局进程中,其他应用通过设置相同的ShareUID可以和它跑在同一个进程。
<service android:name=".LocalService" android:process="com.secondProcess"/>
ShareUserId,在Android里面每个app都有一个唯一的linux user ID,则这样权限就被设置成该应用程序的文件只对该用户可见,只对该应用程序自身可见,而我们可以使他们对其他的应用程序可见,这会使我们用到SharedUserId,也就是让两个apk使用相同的userID,这样它们就可以看到对方的文件。为了节省资源,具有相同ID的apk也可以在相同的linux进程中进行(注意,并不是一定要在一个进程里面运行),共享一个虚拟机。
ShareUserId的作用,数据共享、调用其他程序资源。
多进程的问题
1.Application的多次重建
Manifest文件,定义了两个类:ProcessTestActivity和ProcessTestService,我们只是在Activity的onCreate方法中直接启动了该Service,同时,我们自定义了自己的Application类。代码如下:
public class MyApplication extends Application {
public static final String TAG = "viclee";
@Override
public void onCreate() {
super.onCreate();
int pid = android.os.Process.myPid();
Log.d(TAG, "MyApplication onCreate");
Log.d(TAG, "MyApplication pid is " + pid);
}
}
public class ProcessTestActivity extends Activity {
public final static String TAG = "viclee";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_process_test);
Log.i(TAG, "ProcessTestActivity onCreate");
this.startService(new Intent(this, ProcessTestService.class));
}
}
public class ProcessTestService extends Service {
public static final String TAG = "viclee";
@Override
public void onCreate() {
Log.i(TAG, "ProcessTestService onCreate");
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
我们发现MyApplication的onCreate方法调用了两次,分别是在启动ProcessTestActivity和ProcessTestService的时候,而且我们发现打印出来的pid也不相同。由于通常会在Application的onCreate方法中做一些全局的初始化操作,它被初始化多次是完全没有必要的。出现这种情况,是由于即使是通过指定process属性启动新进程的情况下,系统也会新建一个独立的虚拟机,自然需要重新初始化一遍Application。那么怎么来解决这个问题呢?
下面给出解决方案:
思路:判断是否为主进程,只有主进程的时候才执行下面的操作
String processName = this.getProcessName();
//判断进程名,保证只有主进程运行
if (!TextUtils.isEmpty(processName) &&processName.equals(this.getPackageName())) {
//在这里进行主进程初始化逻辑操作
Log.i(">>>>>>","oncreate");
}
public static String getProcessName() {
try {
File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
String processName = mBufferedReader.readLine().trim();
mBufferedReader.close();
return processName;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
2. 静态成员的失效
设置了process属性后,产生了两个隔离的内存空间,一个内存空间里值的修改并不会影响到另外一个内存空间。所以就算是静态成员也是两个不同的成员值。
3、文件共享问题。
多进程情况下会出现两个进程在同一时刻访问同一个数据库文件的情况。这就可能造成资源的竞争访问,导致诸如数据库损坏、数据丢失等。在多线程的情况下我们有锁机制控制资源的共享,但是在多进程中比较难,虽然有文件锁、排队等机制,但是在Android里很难实现。解决办法就是多进程的时候不并发访问同一个文件,比如子进程涉及到操作数据库,就可以考虑调用主进程进行数据库的操作。
4、断点调试问题。
调试就是跟踪程序运行过程中的堆栈信息,由于每个进程都有自己独立的内存空间和各自的堆栈,无法实现在不同的进程间调试。不过可以通过下面的方式实现:调试时去掉AndroidManifest.xml中android:process标签,这样保证调试状态下是在同一进程中,堆栈信息是连贯的。待调试完成后,再将标签复原。