zl程序教程

您现在的位置是:首页 >  APP

当前栏目

Android进程间通信(三):Bundle、文件共享、Messenger

2023-02-18 16:34:02 时间

转载请以链接形式标明出处: 本文出自:103style的博客

《Android开发艺术探索》 学习记录

base on AndroidStudio 3.5.1


目录

  • 前言
  • Bundle方式
    • 能传哪些数据?
    • 传递的数据大小是否有限制?
  • 文件共享方式
    • 文件共享的局限性 以及 使用示例
    • SharedPreferences ?
  • Messenger方式
    • 使用示例 和 工作原理
  • 小结

前言

前面我们介绍了 进程间通信基础介绍通过AIDL介绍Binder的工作机制 ,不了解的可以先看下。

本文主要介绍进程间通信。

进程间通信的方式有很多:可以通过在 Intent 中附加 extras 来传递信息;可以通过文件共享数据;还可以采用 Binder 方式:MessengerAIDL ;另外 ContentProvider 天生就是支持跨进程通信的;以及通过网络通信 Socket 也是可以实现的。

接下来我们一一介绍。 由于篇幅原因使用 AIDLContentProviderSocket 的方式将单独介绍。


Bundle方式

我们知道四大组件中的 Activity、Service、Receiver 都是支持 Intent 中传递 Bundle 数据的,由于 Bundle 实现了 Pracelable 接口,所以它可以在不同进程间进行传输。

基于这一点我们可以在一个进程里 启动 另一个进程的 Activity、Service、Receiver,我们就可以在 Bundle 中添加对应的数据 通过 Intent 传递。 当然我们的数据必须能够被序列化,或者说可以通过 intent.putExtra() 传的,如下图:

public @NonNull Intent putExtra(String name, String value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putString(name, value);
    return this;
}

不过 Bundle传递数据也是由大小限制的

Intent intent = new Intent(MainActivity.this, TestActivity.class);
int[] test = new int[256 * 1024];//512 = 2097556  256 = 1048980
for (int i = 0; i < test.length; i++) {
    test[i] = 1;
}
intent.putExtra("test", test);
startActivity(intent);

当我们运行上面的代码就会看到以下的报错信息: JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 1048980)

测试发现当把 256 * 1024 修改为 126 * 1024 + 116 是可以传递通过的, 再大 就失败了。

官方的介绍:

“The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size.” 大概意思就是说,这个缓冲区 最大1MB ,并且这是该进程中所有正在进行中的传输对象所公用的

查看了Bundle源码,并结合stackOverFlow资料,发现有如下解释: ArrayMap内部是使用两个数组进行数据存储,一个数组记录key的hash值,另一个数组记录value值,内部使用二分法对key进行排序,并使用二分法进行添加、删除、查找数据,因此它只适合于小数据量操作,在数据量较大的情况下它的性能将会退化。而HashMap内部则是数组+链表的结构,在数据量较少的情况下,HashMap的Entry Array比ArrayMap占用更多的内存。 由于使用Bundle的场景大多数为小数据量,所以相比之下,使用ArrayMap保存数据在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用 以上摘自 https://www.jianshu.com/p/32c641d62ae3


文件共享方式

共享文件也是一个不错的方式,放个进程通过读写同一个文件来交换数据,如果 A进程 写,B进程 读。

不过文件共享方式也是由局限性的,比如并发读写问题,无法保证数据的正确性。所以我们要尽量避免并发读写操作。

SharedPreferences 是个特例,我们知道它是以 xml 键值对的形式保存在 /data/data/packagename/shared_prefs 下。 从本质上将,它也是一个文件,但是系统对它的读写有一定的缓存策略,即内存中也会有一份数据,因此在多进程模式下就变得不可靠了。

除了通过文件交换一些文本信息外,还以通过序列化一个对象到文件系统中,在另一个进程中恢复。 示例如下:

//进程A MainActivity
private void writeToFile() {
    new Thread(() -> {
        User user = new User(1, "103style", 25);
        try {
            File file = new File(getExternalCacheDir().getAbsoluteFile() + File.separator + "test.txt");
            if (!file.exists()) {
                file.createNewFile();
            }
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(user);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();
}
//进程B TestActivity
private void readFromFile() {
    new Thread(() -> {
        try {
            File file = new File(getExternalCacheDir().getAbsoluteFile() + File.separator + "test.txt");
            if (file.exists()) {
                return;
            }
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            User user = (User) ois.readObject();
            Log.e(TAG, user.toString());
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

Messenger方式

Messenger 可以翻译成 信使,通过它可以在不同进程中传递 带有传递数据的 Messenger 对象,就可以轻松实现进程间传递了。

Messenger 是一种轻量级的 IPC 方案,前面介绍 Binder 也说了,它的底层实现是 AIDL 。从以下的构造函数我们就可以看到 AIDL 的痕迹。

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

Messenger 的用法很简单,它对 AIDL 做了封装,是我们更简便的进行进程间通信。同时,由于它依次处理一个请求,因此服务端我们不用考虑线程同步问题,这是因为服务端不存在并发执行的情形。

实现 Messenger 的步骤

  • 服务端进程 首先我们在服务端创建一个 Service 来处理客户端的连接请求,同时创建 Handler 并通过他来创建一个 Messenger 对象,然后再 ServiceonBinder 中返回这个 Messenger 对象底层的 Binder 即可。
  • 客户端进程 客户端进程首先要绑定服务端的 Service, 绑定成功后用服务端返回的 Binder 对象创建一个 Messenger,然后通过这个 Messenger 就可以向服务端进程发消息了,消息类型为 Message 对象。 如果需要服务端能回应客户端,就和服务端一样,我们还需要创建一个 Handler 以及 一个新的 Messenger,并把这个 Messenger 对象通过 MessagereplyTo 参数传递给服务端,服务端就可以通过 replyTo 参数回应客户端了。

示例如下: 服务端:

//在AndroidManifest设置service的进程
<service
    android:name="test.MessengerService"
    android:process=":remote" />
//MessengerService.java
public class MessengerService extends Service {
    public static final int MSG_FROM_CLIENT = 0x001;
    public static final int MSG_FROM_SERVICE = 0x002;
    private static final String TAG = "MessengerService";
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_FROM_CLIENT:
                    Log.e(TAG, "get data form client, data =  " + msg.getData().get("msg"));
                    Messenger client = msg.replyTo;
                    Message replay = Message.obtain(null, MessengerService.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("msg", "Hi, Nice to meet you!");
                    replay.setData(bundle);
                    try {
                        client.send(replay);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}

客户端:

public class TestActivity extends AppCompatActivity {
    private static final String TAG = "TestActivity";
    private Messenger mService;
    private Messenger getReplyMessenger = new Messenger(new GetReplyHandler());
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "hello, 103style");
            msg.setData(bundle);
            //将客户端接受服务端信息的Messenger传递给服务端
            msg.replyTo = getReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
    private static class GetReplyHandler extends Handler {
        @Override
        public void dispatchMessage(Message msg) {
            switch (msg.what) {
                case MessengerService.MSG_FROM_SERVICE:
                    Log.e(TAG, "get data form service, data =  " + msg.getData().get("msg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}

运行程序,切换进程,可以看到控制台打印出以下日志:

//:remote
MessengerService: get data form client, data =  hello, 103style
//主进程
TestActivity: get data form service, data =  Hi, Nice to meet you!

以下是 Messenger 的工作原理图:


小结

  • 使用 Bundle 方式在 ActivityServiceReceiver 中通过 Intent 传递可以显示进程间通信,不过类型只包括 Bundle 支持的类型,而且传输的数据大小也有限制,每个进程的限制是 1M
  • 文件共享方式:只能 一个先写 另一个在读,并发读写不能保证数据的正确性,所以在 超过 Bundle 大小限制的时候可以采用这种方式。
  • Messenger 方式 是通过 Messenger、Service、Handler、Message 协作来实现进程间通信的,Messenger 本身也是系统为了方便上层调用而对 AIDL 的封装。

下一节介绍 通过 AIDL 实现进程间通信


如果觉得不错的话,请帮忙点个赞呗。

以上