进程与线程
IPC(Inter-Process Communication)指的是进程间通信,指的是两个进程之间交换数据的过程。在学习IPC之前我们得先了解一下什么是进程,什么是线程。
进程是应用程序的实例,是操作系统进行资源分配和调度的最小单元,每个进程都代表着应用的一个实例
线程是程序执行的最小单元,线程本身是不占有资源的(除了维持本身的资源除外),线程与进程贡献资源。
一个进程最少包括一个线程(UI线程),但是如果在UI线程中执行大量耗时的操作的话,那么就会造成UI无响应。当然这是不可取的。
采用多进程的好处
虽然使用了多进程以后在数据通信方面变的比较繁杂并且可能会遇到各种各样的问题,但是多进程也有自己的好处。众所周知Android的每个应用程序可以调用的内存是有限制的,但是如果分配的内存不够我们的应用程序的话,那么我们就可以通过多进程的方式来获取更多的内存资源。
还有如果我们的应用程序如果需要一些独立的模块的话,也需要采用多进程。
Dalvik 虚拟机
Android系统为每个进程都单独的分配了一个 Dalvik
不同的虚拟机在内存有不同的内存空间。在不同的进程之间访问相同的类的对象,会创建不同的 副本
。这些副本之间相互独立,互不干涉。这也是为何如果我们想在多进程的模式在两个不同的进程之间通过内存来共享数据,显然是不会成功的。如果想要在进程间通信就必须要用到IPC技术。
开启多进程
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="top.littledavid.studyipc">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:process=":remote" />
<activity
android:name=".ThirdActivity"
android:process="top.littledavid.studyipc.remote" />
</application>
</manifest>
四大组件都可以运行在不同的进程中,通过 process
属性在 manifest
文件中为四大组件指定进程即可。
调用Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.startActivity(android.content.Intent(this, SecondActivity::class.java))
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
startActivity(Intent(this, ThirdActivity::class.java))
}
}
在上面的三个Activity中MainActivity没有指定进程,那么MainActivity采用的就是默认进程,默认进程的命名就是程序的 包名
,如果想要修改默认进程,请给 Application
节点通过 process
属性指定进程。 SecondActivity的进程是 top.littledavid.studyipc:remote
,ThirdActivity的进程是 top.littledavid.studyipc.remote
,当在运行不同的Activity的时候就会创建不同的进程。通过ADB命名或者DDMS都可以查看已经处于运行状态的进程。
adb shell ps ##查看所有进程
### 下面的进程是我们程序的进程
u0_a84 4022 1377 1416356 49952 SyS_epoll_ 00000000 S top.littledavid.studyipc
u0_a84 4039 1377 1415804 49424 SyS_epoll_ 00000000 S top.littledavid.studyipc:remote
u0_a84 4056 1377 1418396 50172 SyS_epoll_ 00000000 S top.littledavid.studyipc.remote
上面就是我们开启的进程。其中 top.littledavid.studyipc
是我们的默认进程,而其余的两个则使我们新创建的进程。其中除了默认的进程之外,剩下的两个进程在声明的时候也不相同,一个是 :remote
一个是 <包名>.remote
。其中 :
有两个作用
- 最明显的一个: 省略包名,是一种简写的方式 ?
- 表示当前进程是一个私有的进程,不同通过共享
UID
的方式让多个应用共享进程
共享进程
在上面我们提到了共享进程这一概念,众所周知Android是基于Linux系统的,Android系统在权限设置上有一下特点:
- Android是一种多用户的Linux系统,Android的每一个应用程序都是一个不同的User
- 默认情况下,系统会用每个应用分配一个唯一的Linux用户Id,系统为文件设置权限,只有该用户才能够访问
- 每一个进程都有有自己的虚拟机,因此应用与应用的运行都是相互隔离的。
通过这种多用户的方式限制了每个应用只能访问与自己相关的组件,而不能访问不相关的组件,通过可以安排两个应用共享一个Linux的用户ID,在这种情况下,他们可以互相访问彼此的文件组件等信息。
验证进程间没有共享内存空间
两个Activity MainActivity
和 SecondActivity
,SecondActivity处于另外的一个进程。通过修改一个静态的变量来验证一下我们的理论。
//将会在两个Activity中修改i变量,来验证
object ValueHolder {
public var i = 0
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ValueHolder.i = 1
this.startActivity(android.content.Intent(this, SecondActivity::class.java))
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.e("TAG", ValueHolder.i.toString())
//输出 0
}
}
在上面的代码中,我们猛一看可能会认为应该输出 1
,但是不要忘了,SecondActivity是在另一个进程中的即在两个不同的虚拟机中,当在不同的虚拟机中访问同一个对象的时候,会产生不同的副本,这些副本之间互不影响,这也就证实了我们上面的理论。
多进程模式造成的影响
因为不同的进程会运行与不同的虚拟机导致内存不共享,会产生以下的影响:
- 静态成员和单例模式完全失效
- 线程同步完全失效
- SharedPreference 可靠性下降
- Application对象会多次创建
①和②造成的原因是一样的,因为不是同一块内存了所以线程的同步锁也就不起作用了
③是因为SharedPreference的底层是读写XMl文件,同时并发写入同一个文件可能会早成数据的丢失。
④都知道Application类代表的是应用程序的实例,所以的原因也很简单,因为系统会为每一个进程都会建立一个虚拟机,这一过程也是创建应用程序的实例并启动的过程,所以这也是为何Application会被多次创建。