自学内容网 自学内容网

【Android】IPC机制—Serializable、Parcelable、Binder用法

简介

IPC(Inter-Process Communication,进程间通信)是指多个进程之间相互传递数据或信号,以便协调完成某些任务的机制。

进程和线程是操作系统中两个重要的概念,主要用于管理和执行计算任务。它们具有不同的特点和作用。

  1. 进程 (Process)
  • 定义:进程是一个正在运行的程序的实例,是资源分配的基本单位。每个进程都包含程序代码、数据、资源(如文件、内存等)以及一些控制信息。
  • 独立性:进程之间是相互独立的,它们的内存空间相互隔离,拥有各自的内存地址空间和系统资源。因此,进程之间的通信相对复杂,需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等。
  • 开销:进程创建和切换的开销较大,因为它需要分配独立的资源和内存空间。
  • 并发性:操作系统可以调度多个进程同时运行,每个进程的执行顺序是独立的,进程之间的调度称为多任务处理。
  1. 线程 (Thread)
  • 定义:线程是进程中的一个执行单元,是处理器调度和执行的基本单位。一个进程可以包含多个线程,它们共享该进程的内存空间和资源。
  • 共享资源:同一个进程中的线程共享内存和系统资源,因此线程之间的通信更加高效和方便,能够直接访问同一进程内的数据。
  • 轻量级:线程的创建和切换开销较小,因为线程共享进程的资源,不需要分配额外的内存空间。
  • 并发性:在多线程模型中,一个进程可以同时运行多个线程,这样可以在一个进程内执行并行任务,从而提高程序的执行效率。

进程在PC和移动设备上指一个程序或者应用,线程是CPU调度的最小单元。

基础概念介绍

Serializable接口

Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。

使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:

    private static final long serialVersionUID = 8711368828010083044L;

serialVersionUID也并不是必须的,不声明serialVersionUID也同样可以实现序列化,但是会对反序列化过程产生影响。

下面是一个User类实现了Serializable接口:

public class User implements Serializable {
    private static final long serialVersionUID = 519067123721295773L;
    public int userId; 
    public String userName;
    public boolean isMale; 
} 

实现了序列化很简单,我们只需要采用ObjectOutputStream和ObjectInputStream就可以实现反序列化:

//序列化过程
        User user = new User(0, "jake", true);
        try {
            ObjectOutputStream out = null;
            out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
            out.writeObject(user);
            out.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //反序列化过程
        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream( new FileInputStream("cache.txt"));
            User newUser = (User) in.readObject(); in.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。

serialVersionUID是用来辅助序列化和反序列化过程的。在进行序列化的时候,系统会把当前的类的serialVersionUID写入序列化文件中,在反序列化的时候会去检测文件中的serialVersionUID,看是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,可以被反序列化;否则就说明类发生了某些变化,无法正常进行反序列化,所以一般需要我们手动指定serialVersionUID的值(1L),也可以让Eclipse根据当前类结构自动生成它的hash值。

如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

以下两点需要特别提一下:首先静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。

Parcelable接口

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化功能由writeToToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的,反序列化通过 CREATOR 完成,CREATOR 内部定义了如何从 Parcel 中创建序列化对象和数组,并通过一系列 Parcelread 方法实现反序列化。反序列化功能由CREATATOR来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程.内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。

public class User implements Parcelable {
    private int id;
    private String name;

    // 构造函数
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // 从 Parcel 反序列化
    protected User(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    // 序列化到 Parcel
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    @Override
    public int describeContents() {
        return 0; // 无特殊内容
    }

    // CREATOR 用于反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

对比

属性ParcelableSerializable
效率高效(推荐在 Android 使用)较低(依赖反射)
实现难度复杂,需要手动实现序列化和反序列化方法简单,只需实现接口
内存开销较低较高
可移植性Android 专用Java 标准,跨平台支持
使用场景Android 开发中的数据传递(Intent 等)数据持久化或网络传输

推荐使用 Parcelable

  • 如果在 Android 中开发,特别是需要频繁传递数据(如 IntentBundleAIDL)。

选择 Serializable

  • 如果需要将对象保存到文件或通过网络传输,且不在意性能。
  • 在需要跨平台或非 Android 环境下使用时。

Binder

  • Binder 是 Android 系统中进程间通信(IPC,Inter-Process Communication)的核心机制。
  • 它是一种高效的、基于 C/S 模式的 IPC 实现,允许一个进程向另一个进程发送数据。
  • Binder 被设计为 Android 的底层通信框架,几乎所有的系统服务(如 ActivityManager、WindowManager)都通过它来实现交互。

Binder 的用法

在 Android 中,Binder 通常用于实现进程间通信(IPC)。其使用方式可以分为直接使用 Binder 和通过 AIDL(Android Interface Definition Language)间接使用。以下是具体的用法和步骤:

1. 直接使用 Binder

直接使用 Binder 类实现简单的进程间通信。

服务端

服务端创建一个继承自 Binder 的类,并实现通信逻辑。

  • 服务端代码
public class MyBinder extends Binder {
    public String getMessage() {
        return "Hello from MyBinder!";
    }
}

public class MyService extends Service {
    private final MyBinder binder = new MyBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return binder; // 返回 Binder 实例
    }
}

客户端

客户端通过 ServiceConnection 获取 Binder 实例,并调用服务端方法。

  • 客户端代码
public class MainActivity extends AppCompatActivity {
    private MyBinder myBinder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyBinder) service; // 获取 Binder 实例
            String message = myBinder.getMessage();
            Log.d("MainActivity", message); // 输出 "Hello from MyBinder!"
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myBinder = null;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
    }
}

2. 使用 AIDL 实现复杂通信

AIDL 是基于 Binder 的一种跨进程通信工具,适合需要传递复杂数据的场景。

定义 AIDL 接口

创建一个 .aidl 文件,用于定义服务接口。例如:

// IMyAidlInterface.aidl
package com.example.myapp;

interface IMyAidlInterface {
    String getMessage();
}

编译后,Android Studio 会自动生成对应的 Java 接口。

服务端

实现 AIDL 接口,并通过 onBind 方法返回 Binder

  • 服务端代码
public class MyAidlService extends Service {
    private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
        @Override
        public String getMessage() throws RemoteException {
            return "Hello from AIDL Service!";
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

客户端

在客户端绑定服务后,通过 AIDL 接口调用服务端方法。

  • 客户端代码
public class MainActivity extends AppCompatActivity {
    private IMyAidlInterface myAidlService;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlService = IMyAidlInterface.Stub.asInterface(service); // 获取 AIDL 接口
            try {
                String message = myAidlService.getMessage();
                Log.d("MainActivity", message); // 输出 "Hello from AIDL Service!"
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myAidlService = null;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MyAidlService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
    }
}

Android中的IPC方式

使用Bundle

四大组件中的三大组件(Activity 、Service 、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。

我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。

Bundle 的基本用法

1. 创建与设置数据

通过 putXxx 方法存入数据:

Bundle bundle = new Bundle();
bundle.putString("key_string", "Hello Bundle!");
bundle.putInt("key_int", 123);
bundle.putBoolean("key_boolean", true);

2. 传递数据

  • 在 Activity 之间传递

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtras(bundle);
    startActivity(intent);
    
  • 在 Fragment 之间传递

    Fragment fragment = new ExampleFragment();
    fragment.setArguments(bundle);
    
  • 传递给 Service

    Intent intent = new Intent(this, MyService.class);
    intent.putExtras(bundle);
    startService(intent);
    

3. 获取数据

通过 getXxx 方法取出数据:

// 从 Intent 中获取
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
    String stringData = bundle.getString("key_string");
    int intData = bundle.getInt("key_int");
    boolean booleanData = bundle.getBoolean("key_boolean");
}

// 从 Fragment 中获取
Bundle args = getArguments();
if (args != null) {
    String stringData = args.getString("key_string");
}

Bundle 支持的数据类型

  • 基本类型Stringintbooleanfloatdouble 等。
  • 数组:如 String[]int[] 等。
  • 集合类型ArrayList<String>ArrayList<Integer> 等。
  • ParcelableSerializable 对象。

使用文件共享

共享文件是两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。

在 Android 中,文件共享指的是通过文件系统或者 ContentProvider 在不同应用之间共享文件或数据。以下是两种常见的文件共享方式:

  1. 通过文件系统共享
  2. 通过 ContentProvider 共享

1. 通过文件系统共享

Android 提供了通过外部存储(例如 SD 卡)来共享文件的方式。这个方法适用于没有特定数据格式要求的共享,用户可以直接存取共享文件。

步骤 1:将文件保存到外部存储

首先,应用需要将文件保存在共享的外部存储中。

FileOutputStream fos = null;
try {
    File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "shared_file.txt");
    fos = new FileOutputStream(file);
    fos.write("This is a shared file.".getBytes());
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fos != null) fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

步骤 2:申请权限

如果你要访问外部存储,必须在 AndroidManifest 中申请必要的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

步骤 3:共享文件

可以通过 Intent 和文件路径来共享文件,允许其他应用访问这个文件。

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "shared_file.txt");

Uri uri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给予其他应用读取文件权限
startActivity(Intent.createChooser(intent, "Share file"));

步骤 4:配置 FileProvider

如果共享文件时使用 FileProvider,需要在 AndroidManifest.xml 中配置:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.myapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true"
    android:permission="android.permission.MANAGE_DOCUMENTS">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

res/xml/file_paths.xml 中配置文件的共享路径:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="shared_files"
        path="Documents/" />
</paths>

2. 通过 ContentProvider 共享

ContentProvider 是 Android 提供的用于在应用之间共享数据的机制。它允许你将数据(如文件、数据库等)暴露给其他应用。

步骤 1:定义 ContentProvider

首先,定义一个 ContentProvider,用于暴露文件。

public class FileContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 实现插入数据的方法
        return null;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        // 实现查询数据的方法
        return null;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.item/vnd.com.example.myapp.sharedfile";
    }
}

步骤 2:配置 ContentProvider

AndroidManifest.xml 中注册 ContentProvider

<provider
    android:name=".FileContentProvider"
    android:authorities="com.example.myapp.provider"
    android:exported="true"
    android:permission="android.permission.READ_EXTERNAL_STORAGE" />

步骤 3:共享文件

你可以通过 ContentProvider 来共享文件:

Uri fileUri = Uri.parse("content://com.example.myapp.provider/shared_file");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(fileUri, "text/plain");
startActivity(intent);

步骤 4:访问共享文件

其他应用可以通过 ContentProvider 访问共享的文件:

Uri fileUri = Uri.parse("content://com.example.myapp.provider/shared_file");
InputStream inputStream = getContentResolver().openInputStream(fileUri);
// 读取文件

使用Messenger

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。

下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。

public Messenger(Handler target) { 
    mTarget = target.getIMessenger(); 
} 
public Messenger(IBinder target) { 
    mTarget = IMessenger.Stub.asInterface(target); 
}

Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。

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

1. 服务端进程

首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。

2. 客户端进程

客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。

在 Android 中,Messenger 是一种用于在应用之间传递消息的机制,基于 HandlerMessage 类的实现。它允许不同的应用或组件之间进行跨进程通信 (IPC),类似于使用 Binder 实现的通信,但它提供了更高层次的 API。Messenger 适用于需要传递消息而不是复杂数据对象的场景。

Messenger 的工作原理

Messenger 基于 Handler,允许发送和接收消息。消息通过 Message 对象传递,可以包含数据或信息。Messenger 可以在同一进程内或跨进程间使用。

使用 Messenger 进行通信

1. 服务端:创建 Messenger 服务

在服务端创建一个 Messenger 实例,通过它接收和处理消息。

步骤 1:创建 Service 并处理消息

首先,创建一个 Service 类,继承自 Service,并在其中定义一个 Handler 来处理消息:

public class MyService extends Service {
    // 创建一个 Handler 来处理消息
    private final Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    // 处理消息
                    String message = msg.getData().getString("key");
                    Log.d("MyService", "Received message: " + message);
                    break;
                default:
                    return false;
            }
            return true;
        }
    });

    // 创建 Messenger 实例
    private final Messenger mMessenger = new Messenger(mHandler);

    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder(); // 返回 Messenger 的 Binder
    }
}

MyService 中,我们创建了一个 Handler 来处理接收到的消息,并通过 Messenger 发送给客户端。onBind 方法返回 MessengerBinder,客户端可以通过该 Binder 与服务端通信。

步骤 2:在 AndroidManifest.xml 中注册服务

<service android:name=".MyService" android:exported="true" />
2. 客户端:发送消息到服务端

客户端可以通过绑定服务与 Messenger 进行通信。

步骤 1:绑定到服务并发送消息

在客户端,创建一个 Messenger 实例,通过它向服务端发送消息。

public class MainActivity extends AppCompatActivity {
    private Messenger mServiceMessenger;
    private boolean mBound = false;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获取服务端的 Messenger
            mServiceMessenger = new Messenger(service);
            mBound = true;
            sendMessageToService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mServiceMessenger = null;
            mBound = false;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定服务
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            // 解除绑定服务
            unbindService(mConnection);
            mBound = false;
        }
    }

    // 发送消息到服务
    private void sendMessageToService() {
        if (mBound) {
            Message msg = Message.obtain(null, 1);
            Bundle bundle = new Bundle();
            bundle.putString("key", "Hello, Service!");
            msg.setData(bundle);
            try {
                mServiceMessenger.send(msg);  // 发送消息
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

MainActivity 中,首先通过 bindService() 绑定到 MyService,并在 onServiceConnected 回调中获取服务端的 Messenger。然后使用 sendMessageToService() 方法向服务发送消息。消息通过 Message 对象传递,可以携带数据。

3. 发送带有回调的消息

Messenger 还支持异步回调,可以让服务端在处理完消息后返回结果给客户端。

步骤 1:服务端处理并回复消息

在服务端的 Handler 中,你可以使用 replyTo 字段来设置回复的 Messenger,以便客户端接收返回的消息。

private final Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                String message = msg.getData().getString("key");
                Log.d("MyService", "Received message: " + message);
                
                // 处理完后发送回执消息给客户端
                Messenger replyMessenger = msg.replyTo;
                Message replyMessage = Message.obtain(null, 2);
                Bundle bundle = new Bundle();
                bundle.putString("response", "Hello, Client!");
                replyMessage.setData(bundle);
                
                try {
                    replyMessenger.send(replyMessage);  // 回复客户端
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                return false;
        }
        return true;
    }
});

步骤 2:客户端接收服务端返回的消息

客户端需要在 Handler 中处理服务端的回复消息。

private final Handler mClientHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 2:
                String response = msg.getData().getString("response");
                Log.d("MainActivity", "Received response: " + response);
                break;
            default:
                return false;
        }
        return true;
    }
});

private void sendMessageToService() {
    if (mBound) {
        Message msg = Message.obtain(null, 1);
        msg.replyTo = new Messenger(mClientHandler);  // 设置回调 Handler
        Bundle bundle = new Bundle();
        bundle.putString("key", "Hello, Service!");
        msg.setData(bundle);
        try {
            mServiceMessenger.send(msg);  // 发送消息
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,客户端在发送消息时,使用 msg.replyTo 设置了一个回调 Messenger,这样服务端可以通过这个 Messenger 来回复客户端。


已经到底啦!!


原文地址:https://blog.csdn.net/2301_79977698/article/details/143839170

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!