为啥 Android Bound Services 文档建议不能跨进程使用 IBinder?

Posted

技术标签:

【中文标题】为啥 Android Bound Services 文档建议不能跨进程使用 IBinder?【英文标题】:Why does Android Bound Services documentation suggest IBinder can't be used across processes?为什么 Android Bound Services 文档建议不能跨进程使用 IBinder? 【发布时间】:2017-07-17 11:28:20 【问题描述】:

来自https://developer.android.com/guide/components/bound-services.html

如果您的服务是您自己的应用程序私有的并且在与客户端相同的进程中运行(这很常见),您应该通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建您的接口。客户端收到 Binder 并可以使用它直接访问 Binder 实现或服务中可用的公共方法。

当您的服务只是您自己的应用程序的后台工作者时,这是首选技术。您不会以这种方式创建界面的唯一原因是您的服务被其他应用程序或跨单独进程使用。

如果您需要您的界面跨不同的进程工作,您可以使用 Messenger 为服务创建一个界面...

这似乎暗示您不会使用 Binder 进行进程间通信,但 IBinder documentation 则相反:

远程对象的基本接口,轻量级远程过程调用机制的核心部分,旨在在执行进程内和跨进程调用时获得高性能。该接口描述了与远程对象交互的抽象协议。不要直接实现这个接口,而是从Binder扩展而来。

关键的 IBinder API 是由 Binder.onTransact() 匹配的 transact()。这些方法允许您分别发送对 IBinder 对象的调用和接收传入 Binder 对象的调用。此事务 API 是同步的,因此在目标从 Binder.onTransact() 返回之前,对 transact() 的调用不会返回;这是调用本地进程中存在的对象时的预期行为,并且底层进程间通信 (IPC) 机制确保在跨进程时应用这些相同的语义。

还有Binder documentation:

但是,您可以直接从 Binder 派生以实现您自己的自定义 RPC 协议,或者直接实例化原始 Binder 对象以用作可以跨进程共享的令牌。

这个类只是一个基本的 IPC 原语...

许多其他页面也提到了 IPC 上下文中的活页夹。我误会了什么?

【问题讨论】:

【参考方案1】:

IBinder 提供远程对象的基本接口Binder 是 IBinder 的实现,它提供标准支持创建一个远程对象的本地实现。

当服务和客户端都在同一个进程中运行时,只需扩展Binder 并返回Binder 的子类的实例就足以通过绑定到它来与服务通信,因为Binder 是IBinder 的实现,它提供该标准支持创建远程对象的本地实现。

当服务在单独的进程中运行并且服务的客户端可以在任何其他进程中运行时,我们需要实现IBinder并提供支持远程实现远程对象的实现。这可以通过以下两种方式之一实现:

使用 AIDL

使用 AIDL,我们可以编写一个我们想要公开的接口。 aidl 工具然后可以解析这个文件并自动生成 Stub 和一些样板代码,这些代码对于 IPC 的所有应用程序都是通用的。然后我们可以扩展Interface.Stub 以提供服务端的实现。

客户可以在他们的项目中包含此 AIDL 文件,并且 IPC 代码由 aidl 工具自动生成。现在,客户端可以绑定到您的服务并使用 AIDL 文件中声明的接口进行通信。

使用Messenger

Messenger 是对 Handler 的引用,其他人可以使用它向它发送消息。这允许跨进程实现基于消息的通信,方法是在一个进程中创建一个指向 Handler 的 Messenger,并将该消息传递给另一个进程。该 IPC 在内部使用 AIDL 本身实现。 Here 是 IMessenger.aidl 接口,Messenger 在内部使用它来公开客户端可以用来与主机进程通信的合约。

总结

IBinder 可用于进程内和进程间通信。使用它进行进程间通信时,只需使用 AIDL 公开接口即可。

【讨论】:

我有点不同。 Messenger 所做的不仅仅是 AIDL 接口。证明:Messenger 可用于在不同进程中的活动之间进行通信。我无法用原始粘合剂实现这一点。【参考方案2】:

我这几天一直遇到 IPC 问题。但在 pskink 的慈善帮助下,我学到了很多东西。

我讨厌 AIDL。我讨厌这个词。为什么这个奇怪的新术语实际上只是一些实现 onTransact 并调用事务的 java 类。没有必要,而且比自己做更难实现!

所以调用AIDL接口:

打印.aidl

interface Print 

  void print(String s);

原谅我的助手代码。然而,AIDL 编译器将其编译为 java 文件。

打印.java

// note that AIDL takes care to use fully qualified names
public interface Print

     void print(String s);

     // This is used for local process calls
     public abstract static class Stub extends Binder
                                                               implements Print
     
          private Stub()
          
            attachInterface(this);
          

          public static Print asInterface(IBinder binder)
          
           return binder == null ? null :
                      binder instanceof Print.Stub ? new Print.Stub() :
                        /* else */           new Print.Stub.Proxy(binder);
          

          @Override public IBinder asBinder  return this; 

          @Override public boolean onTransact(int event,Parcel out,Parcel reply,int flags)
          
               switch(event)
               
                 case TRANSACTION_print: // print data.readString();
               
          

          // This is used for IPC
          private static class Proxy implements Print
          
               IBinder binder;

               @Override public void print(String s)
               
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                data.writeString(s);
                binder.transact(TRANSACTION_print,data,reply,0);
                reply.readException();
               
          
     

这几乎是 AIDL 生成的代码,从不向您展示。我不知道为什么它与 JIDL 或 PIEL 或 TBHJ 不同。但是我们不需要 AIDL 工具,上面的所有代码都可以简化!

process1/Print.java

public class Print extends Binder

     public void print(String s)  // print string 

          @Override public boolean onTransact(int event,Parcel out,Parcel reply,int flags)
          
               switch(event)
               
                 case TRANSACTION_print: // print data.readString();
               
          

处理2/打印

public class Print

     public void print(String s)
     
      Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                data.writeString(s);
                binder.transact(TRANSACTION_print,data,reply,0);
                reply.readException();
     

正如您所看到的那样简单。唯一遗漏的是使用本地函数与进程代理的决定。这很容易实现。

请注意,Binders 不适用于 IPC 句号...它们仅适用于绑定服务到客户端的通信。尝试在两个活动之间进行交易似乎有效。正如 pskink 指出的那样。

要在两个活动之间进行通信,您可以使用 Messenger api。据说 Messenger 只是使用活页夹,但我认为这是半真半假。正如我所说,使用活页夹在两个活动之间进行通信失败了!顺便说一句,Binder 是一个内核驱动程序。所以我猜 Messenger 使用的 binder 是一个特殊的内核 binder 驱动程序!

另一种通信方式是通过 ParcelFileDescriptor.createPipe()!!!!嗯,宝贝。 Android 支持管道。问题是,通过意图对它们进行编组是完全失败的,并且应用程序会严重崩溃。管道再次,似乎只用于服务到活动的通信。

最后。不能使用活页夹聊天真是太荒谬了。两个活动在不同的进程中。无法将可打包描述符放入意图中。你必须遵循设定在你身上的教义。更糟糕的是,糟糕的文档。

【讨论】:

以上是关于为啥 Android Bound Services 文档建议不能跨进程使用 IBinder?的主要内容,如果未能解决你的问题,请参考以下文章

Andorid总结 - Bound Services

为啥 C++ lower_bound() 允许返回与 val 等效的指针,而 upper_bound() 不允许

当我设置为仅编译播放服务广告时,为啥 Android Studio gradle 仍然在我的 apk 中包含用于 google play services 分析的库?

了解绑定服务文档

Android四大组件service之Bound Service

为啥我无法反编译 System.IdentityModel.Services.dll?