Android:安卓学习笔记之进程间通信方式(IPC)的简单理解和使用

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:安卓学习笔记之进程间通信方式(IPC)的简单理解和使用相关的知识,希望对你有一定的参考价值。

进程间通信方式

1、背景

在讲解Binder前,我们先了解一些Linux的基础知识

1.1、进程空间划分




IPC 即 Inter-Process Communication (进程间通信)。android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。

1.2、进程隔离 & 跨进程通信( IPC)

进程隔离

  • 为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的

跨进程通信( IPC )

  • 即进程间需进行数据交互、通信

跨进程通信的基本原理

Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。

优点

  • 安全性:每个进程都单独运行的,可以保证应用层对系统层的隔离。
  • 稳定性:如果某个进程崩溃了不会导致其他进程崩溃。
  • 内存分配:如果某个进程以及不需要了可以从内存中移除,并且回收相应的内存。

1.3、基础概念:序列化方式

Serializable & Parcelable 原理和区别

1.3.1、Serializable接口

Serializable是Java所提供的一个序列化接口,空接口。

  • serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的
  • serialVersionUID要和当前类的serialVersionUID相同才能正常的序列化。
  • 静态成员变量属于类不属于对象,所以不会参加序列化过程;
  • 其次用transient关键字标明的成员变量也不参加序列化过程。

重写如下两个方法可以重写系统默认的序列化和反序列化过程

private void writeObject(java.io.ObjectOutputStream out)throws IOException

private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException


1.3.2、SParcelable接口

Android中特有的序列化方式,效率相对Serializable更高,占用内存相对也更少,但使用起来稍微麻烦点。

public class User implements Parcelable 
    public int userId;
    public String userName;
    public boolean isMale;

    public Book book;

    public User() 
    

    public User(int userId, String userName, boolean isMale) 
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    

    public int describeContents() 
        return 0;//返回当前对象的内容描述,含有文件描述符返回1,否则0
    

    public void writeToParcel(Parcel out, int flags) //将当前对象写入序列号结构中
        out.writeInt(userId);
        out.writeString(userName);
        out.writeInt(isMale ? 1 : 0);
        out.writeParcelable(book, 0);
    

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() 
        public User createFromParcel(Parcel in) 
            return new User(in);
        

        public User[] newArray(int size) 
            return new User[size];
        
    ;

    private User(Parcel in) 
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    

    @Override
    public String toString() 
        return String.format(
                "User:userId:%s, userName:%s, isMale:%s, with child:%s",
                userId, userName, isMale, book);
    



  • 序列化功能由writeToParcel方法来完成,最终通过Parcel中的一系列write方法完成的。
  • 反序列化功能由CREATOR来完成,其内部标明了如何创建序列号对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。
  • 内容描述功能由describeContents方法来完成,几乎所有情况都返回0,只有当前对象存在文件描述符时,才返回1。

对比

  • Serializable是Java中的序列化接口,简单但开销大,序列化和反序列化需要大量的IO操作。
  • Parceable是Android中的序列化方式,使用起来麻烦,但是效率高。

2、通讯方式种类

进程间通信(IPC)方式

  • 使用Bundle
  • 使用文件共享
  • 使用Messenger
  • 使用AIDL
  • 使用COntentProvider
  • 使用Socket

3、使用Bundle

我们都知道Android中三大组件Activity,Service,Receiver都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间进行传输。

当我们在一个进程中启动另外一个进程的Activity、Service、Receiver时,我们就可以在Bundle中附加我们所需要传输给远程进程的信息并通过intent发送出去。这里注意,我们传输的数据必须能够被序列化。

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

private void startWithIntent()
    Intent intent = new Intent();
    //制定要打开的程序的包名(必须写全包名,不然会报错)和地址(activity名)
    intent.setComponent(new ComponentName("PackageName", 
                        "PackageName.intentIpcActivity"));
    //通过budle传递数据,可以携带序列化数据
    Bundle bundle = new Bundle();
    bundle.putInt("intextra", 0);
    bundle.putString("stringextra", "测试数据");
    intent.putExtras(bundle);
    try
        startActivity(intent);
    catch(Exception e)
        ToastUtils.showMessage("没有找到对应文件");
    

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

4、使用文件共享

共享文件也是种不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据

  • 比如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();
        
    

  • 通过文件共享的这种方式来共享数据对文件的格式是没有具体要求的,比如可以是文本文件、也可以是XML文件,只要读写双方约定数据格式即可。
  • 这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,如读取的数据不完整或者读取的数据不是最新的。
  • 因此通过文件共享的方式适合在对数据同步要求不高的进程之间通信,并且要妥善处理并发读/写问题。
  • SharedPreferences是个特例,虽然也是文件的一种,但系统在内存中有一份SharedPreferences文件的缓存,因此在多线程模式下,系统的读/写就变得不可靠,高并发读写SharedPreferences有一定几率会丢失数据,因此不建议在多进程通信中使用SharedPreferences

5、使用Messenger

5.1、Messenger是什么?

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据。

  • 它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
  • 底层实现是AIDL,对AIDL进行了封装。Messenger 服务端是以串行的方式来处理客户端的请求的,不存在并发执行的情形。
  • 优势在于我们可以免于自己去定义.aidl文件,使用系统中提前定义好的Messenger.aidl

Messenger通常和Message、Handler一起使用Messenger中封装了Handler,通过Messenger.send(Message)最终也就是调用了Handler.sendMessage()

5.1.1、Messenger源码简单说明

Messenger 有两个构造函数:

  • 以 Handler 为参数
  • 以 Binder 为参数
private final IMessenger mTarget;
public Messenger(Handler target) 
    mTarget = target.getIMessenger();

public Messenger(IBinder target) 
    mTarget = IMessenger.Stub.asInterface(target);    //和前面的 AIDL 很相似吧

  • 1、Messenger构造方法一(创建clientMessenger)
    调用了Handler的getIMessenger
    public Messenger(Handler target) 
        mTarget = target.getIMessenger();
    

  • 2、Messenger构造方法二(得到serviceMessenger)
    public Messenger(IBinder target) 
        mTarget = IMessenger.Stub.asInterface(target);
    
  • 3、看下 Handler.getIMessenger() 源码:getIMessenger方法获取MessengerImpl
final IMessenger getIMessenger() 
    synchronized (mQueue) 
        if (mMessenger != null) 
            return mMessenger;
        
        mMessenger = new MessengerImpl();
        return mMessenger;
    

这个 IMessanger 应该也是个 AIDL 生成的类吧,看下源码,果然是:

public interface IMessenger extends android.os.IInterface 
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements
            android.os.IMessenger 
        private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";

        public Stub() 
            this.attachInterface(this, DESCRIPTOR);
        

        public static android.os.IMessenger asInterface(...

        public android.os.IBinder asBinder() 
            return this;
        

        @Override
        public boolean onTransact(int code, android.os.Parcel data,
                android.os.Parcel reply, int flags)
                throws android.os.RemoteException ...

        private static class Proxy implements android.os.IMessenger ...

    public void send(android.os.Message msg)
            throws android.os.RemoteException;

IMessenger 是 AIDL 生成的跨进程接口,里面定义了一个发送消息的方法:

    public void send(android.os.Message msg)
            throws android.os.RemoteException;

HandlerMessengerImpl 实现了这个send方法,就是使用 Handler 将消息发出去:

public void send(Message message) throws RemoteException 
    mTarget.send(message);

所以,在Handler中有如下代码

  • getIMessenger方法获取MessengerImpl
  • MessengerImpl又继承自IMessenger.Stub,并且实现了send方法,send方法最终调用的是Handler.sendMessage

总结

  • 1、Messenger 中持有一个 IMessenger 的引用,在构造函数中可以通过 Handler 或者 Binder的形式获得最终的 IMessenger 实现,然后调用它的 send() 方法。
  • 2、Messenger 其实就是 AIDL 的简化版,它把接口都封装好,我们只需在一个进程创建一个 Handler 传递给 MessengerMessenger 帮我们把消息跨进程传递到另一个进程,我们在另一个进程的 Handler 在处理消息就可以了。

5.2、使用步骤

实现一个Messenger有如下几步,分为服务端和客户端:

5.2.1、服务端:

1、创建一个Service来处理客户端的连接请求

2、创建一个Handler并通过它来创建一个Messager对象

3、在ServiceonBind中返回这个Messager对象底层的Binder即可

5.2.2、客户端

1、绑定这个服务端的Server

2、用服务端返回的IBinder对象创建一个Messager对象,通过这个Messager对象向服务端发送Message消息

3、若需要服务端回应客户端,需要创建一个Handler并创建一个新的Messager,并通过MessareplyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端

5.3、使用案例


1、AndroidManifest.xml添加

        <service
            android:name=".MyService"
            android:process=":wangrui"></service>

2、activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_marginTop="50dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_ipc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="IPC连接"/>

    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_gravity="center"
        android:text="IPC通信"/>

</LinearLayout>

3、MyBean.java

public class MyBean implements Parcelable 
    private String name;

    public MyBean()

    

    protected MyBean(Parcel in) 
        name = in.readString();
    

    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeString(name);
    

    @Override
    public int describeContents() 
        return 0;
    

    public static final Creator<MyBean> CREATOR = new Creator<MyBean>() 
        @Override
        public MyBean createFromParcel(Parcel in) 
            return new MyBean(in);
        

        @Override
        public MyBean[] newArray(int size) 
            return new MyBean[size];
        
    ;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

4、MyService.java

1、创建一个Service来处理客户端的连接请求

2、创建一个Handler并通过它来创建一个Messager对象

3、在Service的onBind中返回这个Messager对象底层的Binder即可

public class MyService extends Service 

    private Handler handler = new Handler(Looper.getMainLooper())
        @Override
        public void handleMessage(@NonNull Message msg) 
            super.handleMessage(msg);
            //客户端→服务端
            Bundle bundle = msg.getData();
            bundle.setClassLoader(MyBean.class.getClassLoader());
            MyBean myBean = bundle.getParcelable("message");
            Toast.makeText(MyService.this,myBean.getName(),Toast.LENGTH_SHORT).show();

            //服务端→客户端
            try 
                Messenger clientMessenger = msg.replyTo;
                myBean = new MyBean();
                myBean.setName("皮卡丘对王睿使用了十万伏特");
                bundle = new Bundle();
                bundle.putParcelable("message",myBean);
                Message message = new Message();
                message.setData(bundle);
                message.replyTo = clientMessenger;
                clientMessenger.send(message);
             catch (RemoteException e) 
                e.printStackTrace();
            
        
    ;

    private Messenger messenger = new Messenger(handler);

    public MyService() 
    

    @Override
    public IBinder onBind(Intent intent) 
        return messenger.getBinder();
    


5、MainActivity.java

1、绑定这个服务端的Server

2、用服务端返回的IBinder对象创建一个Messager对象

3、回应客户端,需要创建一个Handler并创建一个新的Messager,并通过Messa的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端

public class MainActivity extends AppCompatActivity 
    private Button btnIPC;
    private Button btnSend;
    private Messenger messengerProxy;
    private Handler handler = new Handler(Looper.getMainLooper())
        @Override
        public void handleMessage(@NonNull Message msg) 
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            bundle.setClassLoader(MyBeanAndroid:安卓学习笔记之Binder 机制的简单理解和使用

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android进程间的通信之Messenger

Android进程间的通信之Messenger

学习笔记 Android 使用AIDL实现进程间通信

学习笔记 Android 使用AIDL实现进程间通信