Android 进程之间的通信(IPC)

序:很多都是自己的个人理解,不一定非常准确,供大家参考学习

大家应该都用过进程间的通讯,那有没有想过一个问题,进程之间为什么要通信呢??

下面循序渐进的为大家解释这个问题。

一、什么是进程或者线程

一个进程是一个独立的功能模块,像windows中的.exe, android中的一个app。

线程是一个独立的子任务,是进程的子单元。

进程只是线程的执行场所。

二、一个进程间的多个线程需要特殊的通信机制吗?

进程就相当于一个大房子,线程相当于房子里的一个人。房子里的人需要分享一个东西,只要每次一个人使用就可以了,用完再让别人用。这里使用的就是“线程互斥”原理。因为一个房子里的东西,对房子里的所有人来说,都是可见的,可以直接使用的。

   对Java来说,可以简单理解为共享引用。

   对C或者C++来说,可以理解为共享指针。

三、进程之间通信的情况又如何??

进程间的通信,就不是一个进程间的多个线程通信那么简单了。这个操作系统的特性有关。为什么需要特别的进程通信机制。原因就是,操作系统中,进程间是相互不可见的。操作系统在逻辑上将每个进程隔离开了。

进程间的内存相互隔绝,操作系统这样做就是防止进程间的相互干扰。假如A进程在操作一个内存时,B进程无意中程序执行出错,改写了A的进程的内存,A进程就很可能崩溃,从而造成了破坏。这也是保护各个进程的安全的一个手段。同时,操作系统也是有各种进程组成的。系统的进程同样也会被其他进程进行破坏,从而让操作系统出现严重问题。这样的设计,操作系统很容易被病毒程序攻陷。所以操作系统为了安全起见,就采用隔绝的方式,既保护了用户程序安全,更是保护了操作系统本身,使操作系统变得很健壮。

也就是因为这个原因,将进程相互隔绝,以至于进程间是相互看不到的。所以就给进程间的通信带来了问题。相互看不见,如何通信呢?因为隔绝,才出现各种各样的通信机制。

比如内存映射文件,就是将一个文件打开,作为通信中介,然后将这文件作为内核对象,分配一个句柄,这个是公用的文件,而这个句柄,是系统全部进程都可以看到的,并且看到的都是同一个,就好比几个房子里的人都能看到广场的一个雕像。然后通过向系统请求,得到访问这个内核对象的句柄就可以操作了。操作完后,其他进程才可以操作,这个是“进程间的互斥”。而通过这种方式就可以更改公共的变量,达到通信的目的。而这个通信的过程就是内存映射文件模式。内核文件作为一个中介,让相互看不见的进程可以相互交换数据。

而管道,邮槽则也是通过消息信件机制,通过系统投递给进程的。进程只要接受这个信件即可,然后了解情况后再发送信件。系统成为了信使。这样也达到通信的机制。

这些也就解决了进程间相互不能通信的问题,也保证进程间相互隔绝后的安全。如果当初没有实现进程间的相互隔绝,也就用不上进程间的通信,就像一个进程间的多个线程之间的通信,是多么的容易。

四、为什么不用更简单,粗暴的方式,直接分享一个引用?

为了系统的安全、进程的独立性考虑,操作系统层把进程隔离开来。进程A把引用给进程B,进程B用不了。

五、掌握此知识点,对我们的开发工作有什么帮助?

系统framework层,到处都是使用进程通信的地方,当我们调用系统服务的时候,它就生存在其他的进程里。想深刻理解,需要掌握。

六、Android有几种通信方式

  • 使用Bundle的方式
    我们知道在Android中三大组件(Activity,Service,Receiver)都支持在Intent中传递Bundle数据,由于Bundle实现了Parceable接口,所以它可以很方便的在不同的进程之间进行传输。当我们在一个进程中启动另外一个进程的Activity,Service,Receiver时,我们就可以在Bundle中附加我们所需要传输给远程的进程的信息,并且通过Intent发送出去。这里注意:我们传输的数据必须基本数据类型或者能够被序列化。
1:基本数据类型(int, long, char, boolean, double等) 
2:String和CharSequence 
3:List:只支持ArrayList,并且里面的元素都能被AIDL支持 
4:Map:只支持HashMap,里面的每个元素能被AIDL支持 
5:Parcelable:所有实现Parcelable接口的对象

下面看一个Demo例子:利用Bundle进行进程间通信

Intent intent = new Intent(MainActivity.this, TwoActivity.class);
 Bundle bundle = new Bundle();
bundle.putString("data", "测试数据"); intent.putExtras(bundle);
startActivity(intent);

注意:利用Bundle进行进程间通信是很容易的,大家应该注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它使用时有一定的局限性。

  • 使用文件共享的方式
    共享文件也是以后不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据,比如进程A把数据写入到文件File中,然后进程B就可以通过读取这个文件来获取这个数据。通过这种方式,除了可以交换简单的文本信息之外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。
    举个例子:
    在A进程中创建一个线程进行写数据:
new Thread(new Runnable() { 
  @Override 
public void run() { 
  User user = new User(1, "user", false); 
  File cachedFile = new File(CACHE_FILE_PATH); 
  ObjectOutputStream objectOutputStream = null; 
  try{ objectOutputStream = new ObjectOutputStream (
  new FileOutputStream(cachedFile));
   objectOutputStream.writeObject(user); 
  }catch(IOException e){
   e.printStackTrace(); 
  }finally{ 
  objectOutputStream.close(); } 
  }
 }).start();

在B进程中创建一个线程进行读取数据:

new Thread(new Runnable() {
 @Override public void run() { 
 User user = null;
 File cachedFile = new File(CACHE_FILE_PATH);
 if(cachedFile.exists()){ 
ObjectInputStream objectInputStream = null;
 try{ 
objectInputStream = new ObjectInputStream (new FileInputStream(cachedFile));
  user = objectInputStream.readObject(user); 
}catch(IOException e){ 
e.printStackTrace(); 
}finally{
 objectInputStream.close();
 } } try{ 
objectOutputStream = new ObjectOutputStream (new FileOutputStream(cachedFile)); objectOutputStream.writeObject(user);
 }catch(IOException e){ 
e.printStackTrace(); 
}finally{ 
objectOutputStream.close(); 
} } }).start();

通过文件共享的这种方式来共享数据对文件的格式是有具体要求的,比如可以是文本文件,也可以是XML文件,只要读写双方约定数据格式即可。这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,如读取的数据不完整或者读取的数据不是最新的。因此通过文件共享的方式适合在数据同步要求不高的进程间通信,并且要妥善处理并发读/写问题。

  • 使用Messenger的方式
    我们也可以通过Messenger来进行进程间通信,在Messenger中放入我们需要传递的数据,就可以轻松的实现进程之间数据传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,关于AIDL我在下面会介绍到。
    Messenger的使用方法也是比较简单的,实现一个Messenger有以下几步,分为服务器端和客服端:
    服务器进程:在A进程创建一个Service来处理其他进程的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind()中返回这个Messenger对象底层的Binder即可。
public class MessengerService extends Service{ 
private Handler MessengerHandler = new Handler(){ 
@Override 
public void handleMessage(Message msg) {
 //消息处理.......            
}; //创建服务端Messenger 
 private final Messenger mMessenger = new Messenger(MessengerHandler);
 @Override public IBinder onBind(Intent intent) {
 //向客户端返回Ibinder对象,客户端利用该对象访问服务端
  return mMessenger.getBinder();
 }
 @Override
 public void onCreate() {
 super.onCreate(); }
 }

客户端进程:在进程B中首先绑定远程进程Service,绑定成功后,根据Service返回的IBinder对象创建Messenger对象,并使用此对象发送消息,为了能收到Service端返回的消息,客户端也创建了一个自己的Messenger发送给Service端,Service端就可以通过客户端的Messenger向客户端发送消息了,具体的实现代码如下:

public class MessengerActivity extends Activity{ 
private ServiceConnection conn = new ServiceConnection(){ 
@Override
 public void onServiceConnected(ComponentName name, IBinder service) {
 //根据得到的IBinder对象创建
Messenger  mService = new Messenger(service);
 //通过得到的mService 可以进行通信 }
 }; 
//为了收到Service的回复,客户端需要创建一个接收消息的Messenger和Handler 
 private Handler MessengerHander = new Handler(){ 
@Override
 public void handleMessage(Message msg) { 
//消息处理
 } }; 
private Messenger mGetMessenger = new Messenger(MessengerHander);
 @Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_messenger); 
init(); 
} 
private void init() { 
intent = new Intent(MessengerActivity.this, MessengerService.class);
 indService(intent, conn, Context.BIND_AUTO_CREATE); 
}
 @Override
 protected void onDestroy(){ 
unbindService(conn); 
super.onDestroy(); 
} 
}

下面给出一张Messenger的工作原理图,以便于更好的理解Messenger:


3416944-96de340a332104cc.png

Messenger内部消息处理使用Handler实现的,所以它是以串行的方式处理客服端发送过来的消息的,如果有大量的消息发送给服务器端,服务器端只能一个一个处理,如果并发量大的话用Messenger就不合适了,而且Messenger的主要作用就是为了传递消息,很多时候我们需要跨进程调用服务器端的方法,这种需求Messenger就无法做到了。

  • **使用ContentProvider的方式 **
    ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作。ContentProvider分为系统的和自定义的,系统的(例如:联系人,图片等数据)。
    开发一个ContentProvider的步骤很简单:
    (1):定义自己的ContentProvider类,该类集成ContentProvider基类;
    (2):在AndroidMainfest.xml中注册这个ContentProvider,类似于Activity注册,注册时要给ContentProvider绑定一个域名;
    (3):当我们注册好这个ContentProvider后,其他应用就可以访问ContentProvider暴露出来的数据了。
    ContentProvider只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentProvider来操作ContentProvider所暴露出来的数据。Content提供了getContentResolver()方法来获取ContentProvider对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。
    使用ContentResolver操作数据的步骤也很简单:
    (1)调用Activity的getContentResolver()获取ContentResolver对象;
    (2)根据调用的ContentResolver的insert()、delete()、update()和query()方法操作数据库即可。

  • 使用广播接收者(Broadcast)的方式
    广播是一种被动跨进程通信方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就像电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
    BroadcastReceiver本质上是一个系统级的监听器,它专门监听各个程序发出的Broadcast,因此它拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcastReceivert总会被激发。我们知道,只要注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为是将自己感兴趣的IntentFilter注册到Android系统的AMS(ActivityManagerService)中,里面保存了一个IntentFilter列表。广播发送者将IntentFilter的action行为发送到AMS中,然后遍历AMS中的IntentFilter列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应的IntentFilter或者Service中---也就是说:会调用抽象方法onReceive()方法。其中AMS起到了中间桥梁的作用。
    程序启动BroadcastReceiver只需要两步:
    (1):创建需要启动的BroadcastReceivert的intent;
    (2):调用Context的sendBroadcast()或者sendOrderBroadcast()方法来启动指定的BroadcastReceivert。
    每当Broadcast事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发onReceiver()方法,onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。
    注意:onReceiver()方法中尽量不要做耗时操作,如果onReceiver()方法不能再10秒之内完成事件的处理,Android会认为该进程无响应,也就弹出我们熟悉的ANR对话框。如果我们需要在接收到广播消息后进行耗时的操作,我们可以考虑通过Intent启动一个Server来完成操作,不应该启动一个新线程来完成操作,因为BroadcastReceiver生命周期很短,可能新建线程还没有执行完,BroadcastReceivert已经销毁了,而如果BroadcastReceivert结束了,它所在的进程中虽然还有启动的新线程执行任务,可是由于该进程中已经没有任何组件,因此系统会在内存紧张的情况下回收该进程,这就导致BroadcastReceivert启动的子线程不能执行完成。

  • 使用Socket的方式
    Socaket也是实现进程间通信的一种方式,Socaket也称为“套接字”,网络通信中的概念,通过Socket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的嘛,但是Socaket主要还是应用在网络通信中。

  • **使用AIDL的方式 **
    AIDL(Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
    AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类架构,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
    1:定义一个AIDL接口。
    2:为远程服务(Service)实现对应Stub。
    3:将服务“暴露”给客户程序使用。

官方文档中对AIDL有这样一段介绍:
Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messenger,也能跨进程通信。可见AIDL是处理多线程、多客户端并发访问的。而Messenger是单线程处理。
AIDL很大的好处就是我们直接可以调用服务端进程所暴露出来的方法,下面简单介绍一下使用AIDL的使用方法:
服务端:
(1):创建aidl接口文件
AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型的,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是与接口相同的包中。

package com.example.android; 
interface IRemoteService { 
int getPid(); 
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString); 
}

(2):向客户端暴露接口

public class DDService extends Service {
 @Override public void onCreate() { 
super.onCreate(); 
System.out.println("DDService onCreate........"
 + "Thread: " + Thread.currentThread().getName()); } 
@Override
 public IBinder onBind(Intent arg0) { 
 System.out.println("DDService onBind");
 return mBinder; 
} 
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
 public int getPid(){
 System.out.println("Thread: " + Thread.currentThread() .getName()); 
 System.out.println("DDService getPid "); 
 return Process.myPid();
 } 

public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) { 
System.out.println("Thread: " + Thread.currentThread().getName()); 
System.out.println("basicTypes aDouble: " + aDouble +" anInt: "
 + anInt+" aBoolean " + aBoolean +" aString " + aString); 
} 
}; 
}

这样我们的服务器端就完成了,把服务器端运行到手机上,等一会可以看一下打印信息。重点看“线程名”。

客户端:

客户端所做的事情就要简单很多了,首先需要绑定服务器端Service,绑定成功后将服务器端返回的Binder对象转成AIDL接口所属的类型,接着皆可以调用AIDL中的方法了。

public class MainActivity extends Activity { 
private IRemoteService remoteService; 
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 } 
ServiceConnection conn = new ServiceConnection() { 
@Override
 public void onServiceDisconnected(ComponentName name) { 
} 
@Override
 public void onServiceConnected(ComponentName name, IBinder service) {
 remoteService = IRemoteService.Stub.asInterface(service);
 try { int pid = remoteService.getPid();
 int currentPid = Process.myPid(); 
System.out.println("currentPID: " + currentPid +"  remotePID: " + pid); remoteService.basicTypes(12, 1223, true, 12.2f, 12.3, "我们的爱,我明白"); 
} catch (RemoteException e) { 
e.printStackTrace();
} System.out.println("bind success! " + remoteService.toString()); } };
/**
     * 监听按钮点击
     * @param view
     */
public void buttonClick(View view) { 
System.out.println("begin bindService"); 
Intent intent = new Intent("duanqing.test.aidl"); 
bindService(intent, conn, Context.BIND_AUTO_CREATE); 
} 
@Override 
protected void onDestroy() { 
super.onDestroy(); unbindService(conn); 
}
}

这样就实现了AIDL进行进程间通信了,是不是也很简单,不过这个看似简单,其实底层Android为我们做了很多的事情,核心就是Binder,感兴趣的读者可以学习一下Binder原理。

//重点记录

1. 系统生成****.java 文件的位置:

如果是Eclips 的工程,会在gen目录下生成该文件,

如果是系统工程里的,会在out/target/common/obj/JAVA_LIBRARIES/...生成该文件

备注:有些自动化的东西,提高了编程的速度,同时也带来了负面影响:给深入理解带来障碍。

2.接口继承:

是不需要重写父亲接口的函数的

public interface TestInterface extendsandroid.os.IInterface {

}

//接口可以继承接口,类可以继承类或者实现接口
//java中抽象类当实现某个接口类时,可以不用实现接口中的方法??因为它是抽象的,实现类去实现这些接口就好。

3.关于stub:

stub 意思是存根,stubclass (存根类)
句子:To do this , you need a stub class of the remoteobject.
要做到这点,你需要一个远程类的存根类。

4.返回同一对象or拷贝??

A:如果绑定的是同一个app中的Service,返回的是同一个引用
Activity 和Service 中是一个存根对象 afb6f16,一个app 内,内存是可以共享的,符合预期估算

B:如果绑定的是另一个app中的service,返回的就不是同一个对象了,是拷贝对象b89ac31(拷贝并不准确,其实是一个代理)
BinderProxy you see.

5. Binder 和 BinderProxy

都位于Binder.java 中 ,是并行类。

6. 代理是一个位于内核空间的对象吗??

7. 内核可以访问进程A和进程B的所有数据

     这样内核才有了做为中转的前提条件。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335

推荐阅读更多精彩内容