本篇博客的框架
IPC(Inter-Process Communication) 进程间通讯,是指两个不同进程之间数据交换的进程。
在明确其之前,需要先弄懂几个概念:
在Android程序中,1般情况下1个程序就是1个进程(在无特别的代码实现下),UI线程即主线程。如果有耗时操作,则会致使主线程堵死。而在Android中主线程负责UI,和用户交互,如果堵死肯定影响用户的使用度。所以Android要求不能再主线程中有耗时操作,这时候就要将耗时操作放在子线程中。
在Android 中,每个利用可以使用的内存大小有限制,早起的1些版本在16M左右,不同的装备有不同的大小。可以通过量进程获得多分内存空间。
Android中开启多进程只有1种方法,便是给4大组件(Activity
,Receiver
,ContentProvider
,Service
)指定android
属性,初次以外没有其他方法。请注意,不能指定某1个线程或实体类指定其所运行的进程
:process
通过jni调用底层去开启多进程也是1种方法,但属于特殊情况,不进行斟酌;
首先编写3个Activity
,并在清单文件中做以下注册:
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.ipc.SecondActivity"
android:process=":remote" />
<activity
android:name="com.example.ipc.ThirdActivity"
android:process=".remote" />
对MainActivity
不进行指定,则默许为当前进程。
对SecondActivity
指定属性android:process=":remote"
。
对ThirdActivity
指定属性android:process=".remote"
。
注意SencodActivity
和ThirdActivity
的进程参数不同。
把3个页面都打开,通过DDMS可以看到3个进程的开启
启动了3个进程:分别是
com.example.ipc
:默许的程序进程。和包名相同。com.example.ipc:remote
:SecondActivity
所在的进程。.remote
:ThirdActivity
所在的进程。那末2和3 ,进程名不同有甚么区分吗;
ShareUID
和其跑在同1个进程中。通过如上方式,很简单的变开启了多进程,但是,如果仅仅这样的话,会有大问题。
看下面1个例子:
添加1个公有的类,添加静态字段:
/**
* 类似平常开发中的工具类等
* @author MH
*
*/
public class PublicContant {
public static int m = 1;
}
在MainActivity
中Log1下并修改字段
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("info", PublicContant.m+"");
PublicContant.m++;
}
在SecondActivity
中Log1下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Log.i("info", PublicContant.m+"");
}
根据上面的逻辑,Log信息应当是1,和2 。但是呢,不是这样的。
两个都是1,我靠。。先不问缘由,看结果就知道是错的。多进程这么不靠谱,肿么回事??
Android 为每个进程都分配1个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就致使在不同虚拟机中访问同1个类对象会产生多个副本。
对当前来讲,进程com.example.ipc
和com.example.ipc:remote
都存在1个PublicContant
类,并且这两个类是相互不干扰的,1个进程中修改了该值的对象,对其他进程中的该值不会造成任何影响。
运行在同1个进程中的组件是属于同1个虚拟机和同1个
Application
的。同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application
的。
根据如上所述,多进程所酿成的问题分为以下几个方面:
SharedPreference
的可靠性降落 sharedPreference
的底层实现是通过读写XML文件,两个进程去读写,并发明显是可能出现问题的。Application
会屡次创建在了解多进程通讯之前,我们需要了解两个基础的概念,序列化和反序列化。
序列化和反序列的用处:
在Android
中实现序列化的方式有两种,Serializable
和Parcelable
。
Serializable
是Java提供的1个序列化接口,他是1个空接口,是类实现该接口便可实现序列化。
/**
* Serializable 序列化对象
* @author MH
*
*/
public class Book implements Serializable {
/**
* 序列化和反序列的关键
*/
private static final long serialVersionUID = 1L;
public int bookId;
public String bookName;
}
在实现Serializable
时候,编译器会提示,让我们添加serialVersionUID
字段,该字段是1个关键的字段,后面会说。
相应的实现好了,那末如何写入和读取呢?
public void writeSerializable() {
try {
// 构造对象
Book book = new Book();
// 构造序列化输出字节流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xxx.txt"));
// 序列化对象
oos.writeObject(book);
// 关闭流
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void readSerializable() {
try {
// 创建序列化读取字节流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"xxx.txt"));
// 反序列化(读取)对象
Book book = (Book) ois.readObject();
// 关闭流
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
在序列化时,如果我们序列化对象以后,改变了我们的类结构(添加或改变字段),乃至是修改了字段的类型,修改了类名,那末我们能反序列化成功吗。
那末关键就在于serialVersionUID
字段。
如果我们不指定的话。在序列化时,会计算当前类结构的hash值并将该值赋给serialVersionUID
,当反序列时,会比对该值是不是相同,如果不相同,则没法序列化成功。
我们也能够手动指定,手动指定的好处是在类结构产生变化时,能够最大程度的反序列,固然条件是只是删除或添加了字段,如果是变量类型产生了变化,则仍然没法反序列成功。
serialVersionUID 的工作机制:序列化时系统会把当前类的
serialVersionUID
写入序列化文件中,当反序列化时候系统会去检测文件中的serialVersionUID
,看它是不是和当前类的serialVersionUID
1致,如果1致说明序列化类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比产生了某些变化。所以,我们最好指定serialVersionUID
,避免他自定生成。
Parcelable
是Android中独有的1种序列化方式,在intent
传值时,通常使用该方式。
该方式实现序列化,仍然实现Parcelable
,然后实现1些该接口的方法。
/**
* Parcelable 对象的使用方式
* @author MH
*
*/
public class Book implements Parcelable {
public int bookId;
public String bookName;
@Override
public int describeContents() {
// 返回当前对象的内容描写。几近所有情况下都是返回0
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// 将当前对象写入到序列化结构中
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
//从序列化后的对象中创建原始的值
Book book = new Book();
book.bookId = source.readInt();
book.bookName = source.readString();
return book;
}
@Override
public Book[] newArray(int size) {
//创建指定长度的原始对象数组
return new Book[size];
}
};
}
Parcelable
实现两个方法,创建1个字段:
describeContents()
:返回当前对象的内容描写。几近所有情况下都是返回0。public void writeToParcel(Parcel dest, int flags)
:// 将当前对象写入到序列化结构中Parcelable.Creator
字段,该对象需要实现两个方法: public Book createFromParcel(Parcel source)
:从序列化后的对象中创建原始的值。public Book[] newArray(int size)
:创建指定长度的原始对象数组Serializable
是Java中的序列化接口,其使用起来简单但是开消较大,序列化和反序列化需要大量的I/O操作。Parcelable
是Android中的序列化方式,更适用于Android的平台上,他的缺点是使用起来略微麻烦,但是效力很高。Parcelable
合适进程间的通讯,运行期。Serializable
合适文件存储即网络传输。Android中的4大组件中,其中有3大组件(Activity
,Service
,Receiver
)都支持Intent
中传递Bundle
数据,如果看其源码,会发现其也是实现了Parcelable
接口,所以其能够在不同进程中传输。
固然在传输的进程中,其所传输的数据必须支持序列化。比如基本数据类型,字符串,Parcelable
的实现类,Serializable
的实现类。由于该方法非常经常使用,不在多说。
文件同享: 将对象序列化以后保存到文件中,在通过反序列,将对象从文件中读取。
在MainActvity
中写写入对象
/**
* 写入序列化对象
*/
public void wirte() {
Book book = new Book();
book.bookId = 1;
book.bookName = "si";
try {
// 构造序列化输出字节流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(PATH));
// 序列化对象
oos.writeObject(book);
// 关闭流
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(book);
}
在SecondActivity
中,读取文件(反序列化)
public void read() {
Book book = null;
try {
// 创建序列化读取字节流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
MainActivity.PATH));
// 反序列化(读取)对象
book = (Book) ois.readObject();
// 关闭流
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(book);
}
LOG 结果
06-28 09:20:47.916: com.example.ipc(进程名) I/System.out(12399): Book [bookId=1, bookName=si]
06-28 09:20:53.376: com.example.ipc:remote(进程名) I/System.out(12866): Book [bookId=1, bookName=si]
分属不同的进程成功的获得到了同享的数据。
通过同享文件这类方式来同享数据对文件的格式是没有具体的要求的。比如可以是文件,也能够是Xml,JSON 等。只要读写双方约定1定的格式便可。
同文件同享方式也存在着很大的局限性。即并发读/ 写的问题。读/写会造成数据不是最新。同写很明显会出现毛病。
文件同享合适在对数据同步要求不高的进程之间进行通讯。并且要妥善处理并发读写的问题。
SharedPreference 底层文件的方式。不合适在多进程中同享数据。
Messenger
可以翻译为信使,通过该对象,可以在不同的进程中传递Message
对象。注意,两个单词不同。
下面就通过服务端(Service)和客户端(Activity)的方式进行演示。
客户端向服务端发送消息,可分为以下几步。
服务端
Service
Handler
对象,实现handlerMessage
方法。Handler
对象构造Messenger
信使对象。Service
的onBind()
返回信使中的Binder
对象。客户端
Actvity
ServiceConnection
,监听绑定服务的回调。onServiceConnected()
方法的参数,构造客户端Messenger
对象Messenger
向服务端发送消息。实现服务端
/**
* Messenger 的使用 服务端
* @author MH
*
*/
public class MessengerService extends Service {
/**
* 构建handler 对象
*/
public static Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
// 接受客户端发送的消息
String msgClient = msg.getData().getString("msg");
Log.i("messenger","接收到客户真个消息--"+msgClient);
};
};
// 通过handler 构建Mesenger 对象
private final Messenger messenger = new Messenger(handler);
@Override
public IBinder onBind(Intent intent) {
// 返回binder 对象
return messenger.getBinder();
}
}
注册服务别忘了 ,同时对服务修改其进程。
实现客户端
/**
* Messenger 的使用 客户端
*
* @author MH
*
*/
public class MessengerActivity extends AppCompatActivity {
/**
* Messenger 对象
*/
private Messenger mService;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// IBinder 对象
// 通过服务端返回的Binder 对象 构造Messenger
mService = new Messenger(service);
Log.i("messenger", "客户端以获得服务端Messenger对象");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
// 启动服务
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
/**
* 布局文件中添加了1个按钮,点击该按钮的处理方法
* @param view
*/
public void send(View view) {
try {
// 向服务端发送消息
Message message = Message.obtain();
Bundle data = new Bundle();
data.putString("msg", "lalala");
message.setData(data);
// 发送消息
mService.send(message);
Log.i("messenger","向服务端发送了消息");
} catch (Exception e) {
e.printStackTrace();
}
}
}
看1下结果:
注释很清楚,不在多说,依照流程实现便可。 其中有1点需要注意:
我们是通过Message
作为媒介去携带数据的。但是,Message
的obj 并没有实现序列化(实现Serializable
或Parcelable
),也就是其不能保存数据。必须使用message.setData()
方法去传入1个Bundle
对象,Bundle
中保存需要传入的数据。
传递时使用的是Messenger.send(Message)
方法。
服务端向客户端发送了消息,那末服务端向客户端发送消息也类似:
关键点: 客户端向服务端发送消息是,通过msg.replyTo
将客户端Messenger
对象传给服务端。
客户端代码进行修改:
Handler
和Messenger
对象。send()
方法。
/**
* 构建handler 对象
*/
public static Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
// 接受服务端发送的消息
String msgService = msg.getData().getString("msg");
Log.i("messenger","接收到服务真个消息--"+msgService);
};
};
// 通过handler 构建Mesenger 对象
private final Messenger messengerClient = new Messenger(handler);
/**
* 布局文件中添加了1个按钮,点击该按钮的处理方法
* @param view
*/
public void send(View view) {
try {
// 向服务端发送消息
Message message = Message.obtain();
Bundle data = new Bundle();
data.putString("msg", "lalala");
message.setData(data);
// ----- 传入Messenger 对象
message.replyTo = messengerClient;
// 发送消息
mService.send(message);
Log.i("messenger","向服务端发送了消息");
} catch (Exception e) {
e.printStackTrace();
}
}
服务端代码修改
/**
* 构建handler 对象
*/
public static Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// 接受客户端发送的消息
String msgClient = msg.getData().getString("msg");
Log.i("messenger", "接收到客户真个消息--" + msgClient);
// 获得客户端Messenger 对象
Messenger messengetClient = msg.replyTo;
// 向客户端发送消息
Message message = Message.obtain();
Bundle data = new Bundle();
data.putString("msg", "ccccc");
message.setData(data);
try {
// 发送消息
messengetClient.send(message);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
};
结果不在演示了。
AIDL是1种接口定义语言,用于束缚两个进程间的通讯规则,供编译器生成代码,实现Android装备上的两个进程间通讯(IPC)。
进程之间的通讯信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。
AIDL
的关键便是Binder
,关于Binder
,后面的博客会分析。在这里之将如何使用它。
由于需要服务端和客户端共用aidl
文件,所以最好单独建1个包,合适拷贝到客户端。
服务端:
com.example.ipc.aidl
BookAidl.java
,该对象需要作为传输。所以需要实现Parcelable
。public class BookAidl implements Parcelable {
public int bookId;
public String bookName;
public BookAidl() {
super();
}
public BookAidl(int bookId, String bookName) {
super();
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Parcelable.Creator<BookAidl> CREATOR = new Creator<BookAidl>() {
@Override
public BookAidl[] newArray(int size) {
return new BookAidl[size];
}
@Override
public BookAidl createFromParcel(Parcel source) {
BookAidl book = new BookAidl();
book.bookId = source.readInt();
book.bookName = source.readString();
return book;
}
};
@Override
public String toString() {
return "BookAidl [bookId=" + bookId + ", bookName=" + bookName + "]";
}
}
Parcelable
在前面已说过,不在多说。
.aidl
文件。由于需要用到BookAidl
对象,所以需要先声明。创建BookAidl.aidl
文件,并手动添加
package com.example.ipc.aidl;
parcelable BookAidl;
创建IBookManager.aidl
文件,接口文件,面向客户端调用
package com.example.ipc.aidl;
import com.example.ipc.aidl.BookAidl;
interface IBookManager{
List<BookAidl> getBookList();
void addBook(in BookAidl book);
}
写完以后clean
1下工程,以后会在gen
目录下生成对应的java文件。此java中的具体含义后面会解释,在此不做多述。
Service
类。public class BookService extends Service {
/**
* 支持线程同步,由于其存在多个客户端同时连接的情况
*/
private CopyOnWriteArrayList<BookAidl> list = new CopyOnWriteArrayList<>();
/**
* 构造 aidl中声明的接口的Stub对象,并实现所声明的方法
*/
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<BookAidl> getBookList() throws RemoteException {
return list;
}
@Override
public void addBook(BookAidl book) throws RemoteException {
list.add(book);
Log.i("aidl", "服务端添加了1本书"+book.toString());
}
};
@Override
public void onCreate() {
super.onCreate();
//加点书
list.add(new BookAidl(1, "java"));
list.add(new BookAidl(2, "android"));
}
@Override
public IBinder onBind(Intent intent) {
// 返回给客户真个Binder对象
return mBinder;
}
}
在Service
中,主要干了两件事情:
Binder
对象通过onBinder
返回给客户端。为了省事,在这里不在另起1个工程了,直接将Service
在另外一个进程中运行。
<service
android:name="com.example.ipc.BookService"
android:process=":remote" />
开始编写客户端
由于在同1个工程中,不需要拷贝aidl
包中的文件。如果不在同1个工程,需要拷贝。
public class BookActivity extends AppCompatActivity{
/**
* 接口对象
*/
private IBookManager mService;
/**
* 绑定服务的回调
*/
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获得到书籍管理的对象
mService = IBookManager.Stub.asInterface(service);
Log.i("aidl", "连接到服务端,获得IBookManager的对象");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
// 启动服务
Intent intent = new Intent(this,BookService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
/**
* 获得服务端书籍列表
* @param view
*/
public void getBookList(View view){
try {
Log.i("aidl","客户端查询书籍"+mService.getBookList().toString());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 添加书籍
*/
public void add(View view){
try {
// 调用服务端添加书籍
mService.addBook(new BookAidl(3,"ios"));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户真个代码和之前的Messenger
很类似:
IBinder service
通过IBookManager.Stub.asInterface()
转化为借口对象。效果
总结来讲可分为以下几步
服务端:
客户端
作为android 4大组件之1,虽然用的地方不是太多。但是其确切是多进程通讯的1种方式。例如,获得通讯录信息,这明显跨利用了,肯定是多进程通讯啊。
其底层实现和Messenger
1样,都是通过Binder
,后面会专门分析Binder
对象。
ContentProvider
很多介绍,在这不在多提。
Socket
也称为“套接字”,是网络通讯中的概念,它分为流式套接字和用户数据报套接字,分别对应于网络传输中的传输控制层的TCP和UDP。
该方面使用的是JAVA 方面的知识。该举例只是说明1个思路。不做细致的实现。
服务端
public class SocketService extends Service {
/**
* 连接的状态
*/
private boolean isConnState = true;
@Override
public void onCreate() {
super.onCreate();
// 启动TCP 服务
new Thread(new TCPServer()).start();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
// 结束TCP 服务
isConnState = false;
super.onDestroy();
}
/**
* 服务端TCP 服务,相当于服务器,接受Socket 连接
* @author MH
*
*/
class TCPServer implements Runnable{
@Override
public void run() {
try {
// 监听本地的12345 端口
ServerSocket ss = new ServerSocket(12345);
while(isConnState){
// 获得客户真个Socket 对象
Socket socket = ss.accept();
// 获得输入流 ---
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 通过输入流读取客户真个消息
//String line = br.readLine();
// 输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 通过输出流向客户端发送消息
//bw.write("....");
// 关闭连接
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服务启动时,在onCreate
方法中启动了TCPServer
,该线程时刻接受客户真个要求。
客户端
public void conn(){
try {
// 指定ip和端口
Socket s = new Socket("localhost", 12345);
// ----- 和服务端类似
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//String line = br.readLine();
// 输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//bw.write("....");
// 关闭连接
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
关于Socket
,在此只是1个简单的示范,具体的使用博大精深。知道能实现便可。
Bundle
:4大组件间的进程间通讯方式,简单易用,但传输的数据类型受限。Messenger
: 数据通过Message
传输,只能传输Bundle
支持的类型ContentProvider
:android 系统提供的。简单易用。但使用受限,只能根据特定规则访问数据。AIDL
:功能强大,支持实时通讯,但使用略微复杂。Socket
:网络数据交换的经常使用方式。不推荐使用。在实现多进程通讯时,其中Messenger
,ContentProvider
,AIDL
的底层实现都是Binder
,很有必要对其进行继续分析。
该博客中的代码以同享到github。 https://github.com/AlexSmille/Android-IPC-Example
上一篇 C++中enum的使用
下一篇 部分SWAP 内存知识