Android中进程间通信(IPC)方式总结

时间:2021-04-07 17:46:42

IPC为进程间通信或跨进程通信,是指两个进程进行进程间通信的过程。PC和移动设备上一个进程指的是一个程序或者一个应用,所以我们可以将进程间通信简单理解为不同应用之间的通信,当然这种说法并不严谨。

      Android中,为每一个应用程序都分配了一个独立的虚拟机,或者说每个进程都分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机互相访问数据需要借助其他手段。下面分别介绍一下在Android中进行实现IPC的方式。

 

1.使用Bundle

      我们知道在Android中三大组件(ActivityServiceReceiver)都支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以他可以方便的在不同的进程之间进行传输。当我们在一个进程中启动另外一个进程的ActivityServiceReceiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过Intent发送出去。这里注意:我们传输的数据必须能够被序列化。

      下面我们看一下利用Bundle进行进程间通信的例子:

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

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

 

2.使用文件共享

    共享文件也是以后总不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件FILEB进程可以通过读取这个文件来获取这个数据。通过这种方式,除了可以交换简单的文本信息以外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。

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


3.使用Messenger

    我们也可以通过Messenger来进行进程间通信,在Messenger中放入我们需要传递的数据,就可以轻松的实现进程之间数据传递了。Messenger是一种轻量级的IPC方案,他的底层实现是AIDL,关于AIDL我们在和面会介绍到。

    Messenger的使用方法也是比较简单的,实现一个Messenger有如下几步,分为服务端和客户端:

    服务端进程:A进程创建一个Service来处理其他进程的连接请求,同时创建一个Handler并通过他来创建一个Messenger对象,然后在ServiceonBind中返回这大Messneger对象底层的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

Android中进程间通信(IPC)方式总结

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


4.使用AIDL

    AIDL (Android Interface Definition Language)是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

    AIDLIPC的一个轻量级实现,用了对于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”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。

    AIDL很大的好处就是我们直接可以调用服务端进程所暴露出来的方法,下面简单介绍一下AIDL的使用:

服务端

    服务端首先要创建一个Service用来监听客户端的请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个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原理。


5.使用ContentProvider

    ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProviderContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过接口来操作接口内的数据,包括增、删、改、查等。ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。

开发一个ContentProvider的步骤很简单:

定义自己的ContentProvider类,该类继承ContentProvider基类;

AndroidManifest.xml中注册这个ContentProvider,类似于Activity注册,注册时要给ContentProvider绑定一个域名;

当我们注册好ContentProvider后,其他应用就可以访问ContentProvider暴露出来的数据了。

    ContentProvider只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentReslover来操作ContentProvider所暴露出来的数据。Context提供了getContentResolver()方法来获取ContentProvider对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。

    使用ContentResolver操作数据的步骤也很简单:

    调用ActivitygetContentResolver()获取ContentResolver对象

    根据调用的ContentResolverinsert()delete()update()、和query()方法操作数据库即可。

    代码就不贴出来了,这是Android四大组建之一,相信读者已经很熟悉了。

 

6.使用广播(Broadcast)

    广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。

    BroadcasReceivert本质上是一个系统级的监听器,他专门监听各程序发出的Broadcast,因此他拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcasReceivert总会被激发。我们知道,只有先注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为是将自己感兴趣的IntentFilter注册到Android系统的AMSActivityManagerService)中,里面保存了一个IntentFilter列表。广播发送者将自己的IntentFilter action行为发送到AMS中,然后遍历AMS中的IntentFilter列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应IntentFilterActivity或者Service-----也就是会调用抽象方法onReceive()方法。其中AMS起到了中间桥梁作用。
    程序启动BroadcasReceivert只需要两步:

    1)创建需要启动的BroadcasReceivertIntent

    2)调用ContextsendBroadcast()sendOrderBroadcast()方法来启动指定的BroadcasReceivert

 

    每当Broadcast事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发onReceiver()方法,onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。

    注意:onReceiver()方法中尽量不要做耗时操作,如果onReceiver()方法不能在10秒之内完成事件的处理,Android会认为改程序无响应,也就弹出我们熟悉的ANR对话框。如果我们需要在接收到广播消息后进行一些耗时的操作,我们可以考虑通过Intent启动一个Server来完成操作,不应该启动一个新线程来完成操作,因为BroadcastReceiver生命周期很短,可能新建线程还没执行完,BroadcastReceiver已经销毁了,而如果BroadcastReceiver结束了,他所在的进程中虽然还有启动的新线程执行任务,可是由于该进程中已经没有任何组件,因此系统会在内存紧张的情况下回收该进程,这就导致BroadcastReceiver启动的子线程不能执行完成。

 

7.使用Socket

    Socaket也是实现进程间通信的一种方式,Socaket也成为“套接字”,是网络通信中的概念,通过Socaket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的么。但是Socaket主要还是应用于网络通信,感兴趣的读者可以自行了解一下。