IPC是Inter-Process Communication的简写,意为进程间通信或者跨进程间通信,是指两个进程间进行数据交换的过程。在说道进程间通信,首先,我们要理解什么是进程?什么线程?进程跟线程的关系又是什么?搞清楚这些东西,对我们来说,是很有必要的,当然了,这个进程与线程的关系,你们肯定是知道的,但是我还是要在这里啰嗦两句,哈哈,不要喷我啊。首先来说,什么是线程?线程其实就是程序中单独执行的流控制。那么什么又是多线程呢?那么他呢其实就是单个程序中,可以同时运行多个不同线程执行不同的任务。多线的作用就是最大限度的使用CPU资源。而进程呢?一般指的是一个执行单元。比如:一个程序或者在Android上来说,它是一个应用。进程可以有多个线程,所以说,进程与线程是包含与被包含的关系。
那么我们为什么要知道这个多进程呢?多进程对我们来说又有什么好处呢?这个多进程的使用场景很多,我们知道在Android早些时候的版本,每个应用分配的内存大小是16MB,有时候,我为了解决这个应用内存大小的问题,我就采用了多进程来解决这个内存不足的问题。当然,还有很多使用场景,比如说还有我们比较常见的跨进程通讯,ContentProvider对吧,他也是一种跨进程通信。好了,既然说到了多进程,那么我们下面就来说说如何开启多进程?当然开启多进程很简单,我们给四大组件定义一个process属性,就好了。但是,开启多进程对我们来说真的很简单吗?答案是否定的,如果多进程应用的不好,那就犹如黄河之水泛滥一发不可收拾。如果控制的不好,将会给你的程序带来灾难性的后果。下面我们来看个例子,然后来分析分析,在开启多进程之后会带来那些问题。如下:
我们在清单文件里配置,如下:
好,现在我们运行一下程序,看看:
在上面的清单文件中,我们会发现,我们SencondActivity与ThirdActivity所声明的process方式是不一样的?一个是“ :remote”,一个是加上全部包名,“com.example.ipcdemo.remote”,那么他们之间有什么区别呢?当然是有区别的,对于,ThirdActivity中的声明方式,它是一个i额完整的声明方式,并且它不会附加包名信息,而SencondActivity则会附加包名信息。如果是以“:”开头的进程属于当前应用的私有进程,其他应用组件不可以和它跑到一个进程里,如果不是以“:”开头的进程属于全局的进程,其他的应用可以通过ShareUID可以和它跑到同一进程当中,我们知道Android系统会为每个应用分配一个唯一的UID,相同应用的UID的应用才能共享数据。但是这里要讲明的是,如果两个应用通过SharedUID跑到同一个进程中需要这两个应用的SharedUID相同并且签名也要相同才可以。当然,在这种情况下,他们可以互相访问对方的私有数据,比如:data目录,组件信息等,不管它们是否是跑在同一个进程当中,还有就是它们如果跑在同一个进程当中的话,除了能访问data目下、组件信息,还可以共享内存信息。
之前我们说道,开启多进程,会出现各种奇奇怪怪的问题,那么下面我们就来看一下这个例子,比如我们创建一个UserManger类,里面声明userId,int类型的:
/**
* Author : Created by rookie
*/
public class UserManager {
public static int sUserId = 1;
}
接下来,我们在MainActivity里,修改这个值,然后在打印,接着在SecondActivity里打印一下userId的值,看看他是不是我们之前在MainActivity里面改过来的数据。代码如下:
//MainActivity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UserManager.sUserId = 2;
Log.d(TAG,"sUserId = "+UserManager.sUserId);
Intent intent = new Intent(this,SencondActivity.class);
startActivity(intent);
}
}
//SencondActivity:
public class SencondActivity extends AppCompatActivity {
private static final String TAG = "SencondActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sencond);
Log.d(TAG, "sUserId = " + UserManager.sUserId);
Intent intent = new Intent(this,ThirdActivity.class);
startActivity(intent);
}
}
又上图可知,他们打印出来的值居然不一样,我们明明在MainActivity里改了,可是值在SedondActivity里打印出来并没有发生改变,这又是为什么呢?我们带着这个问题,来分析解答一下:
出现上述问题的原因很简单,那就是Android为我们每个应用都分配一个独立的虚拟机,或者你也可以这么去理解,Android系统为我们每个进程都分配了一个独立的虚拟机,不同的虚拟机将导致分配的内存空间也不一样,这也就是,在开启多进程的时候,每个进程对应一个独立的虚拟机,并且在访问同一个类的对象时,会产生多个副本。就拿这个例子来说吧,SedondActivity和ThirdActivity这两个进程中都会存在一个UserManger类,并且这两个类还是互不干扰的,那么你在前者的进程中修改值,在后一个进程中是不会收到影响的。这只是其中一种,那么我们在使用多进程的时候,他可能还会带来以下几个问题:
1>静态成员和单例模式完全失效。
2>线程同步机制完全失效。
3>SPreferences的可靠性下降。
4>Application会创建多次。
第一、第二个问题,类似,既然都不在同一个内存块了,无论你是锁对象还是所类都将无法保证线程同步,因为不同进程锁的对象不同。第三个问题,它不支持两个进程同时进行读写操作,否则会导致数据一定机率的丢失,好,到了第四个问题,我们创建一个Applictaion的子类来测试一下,代码如下:
public class MyApplication extends Application {
private static final String TAG = "MyApplication";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"进程名字 : "+ Utils.getProcessName(getApplicationContext(),android.os.Process.myPid()));
}
}
工具类:
public class Utils {
public static String getProcessName(Context context,int processId){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for(ActivityManager.RunningAppProcessInfo appProcess : am.getRunningAppProcesses()){
if(appProcess.pid == processId){
return appProcess.processName;
}
}
return null;
}
}
运行效果:
从上面的结果,我们可以证实,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,对吧,这也无形给我们日常开发中带来非常大的困扰,这篇我们说了,在使用多进程时所带来的问题,既然有问题,我们不能视若无睹,当然要想办法去解决,当然,Android系统,为我们提供很多跨进程的通信方式,虽然不能直接共享内存,但是交换数据还是没问题的。那么通信方式有哪些呢?这里可以预告一下,我们将会在下一篇讲到。比如:Intent来传递数据,共享文件和SharedPreferences,基于Binder的Messager和AIDL以及Socket,当然还有内容提供者(ContentProvider),但是,为了让我们更好的理解IPC的通信方式,我们要熟悉一些基础概念,比如:Serializable和Parcelable接口,以及Binder概念。我们将会在下一篇讲到这些基础概念,以及一种IPC机制通信方式。未完待续......