在 SDK 中授予 Activity UI 的控制权

Grant control of Activity UI in SDK

目前我们正在制作一个包含 Activity 的 Android 库。我们想要控制 activity 的确切流程和状态,但是让实现库的用户控制 UI 的样子。同时,我们希望公开最少的内部 classes.

SDK 用户可以决定视图的放置位置、大小、颜色;我们决定在 onClicks 上发生什么并提供 TextViews 的文本。

activity 使用模型-视图-意图模式,因此我们想要公开不可变状态。如果没有可调整的 UI,Activity 及其所有 class 都是内部的。有了可调整的 UI,还有很多 class 需要制作 public。这增加了破坏更新更改的风险并暴露了我们的逻辑。

为了暴露UI,想到了几种解决方案:

哪种解决方案最好?除了我们想到的方法,还有其他方法吗?

The SDK user may decide where views are placed, sizes, colors; we decide on what happens on onClicks and provide the texts of TextViews.

我是这样想的:

  • 您的消费者收到一个 State、一个 ViewGroup 和一个 Actions 对象,稍后我将对此进行解释。
  • 基于 State,消费者需要创建多个视图并将它们按照自己的意愿放置在提供的 ViewGroup 中。
  • 消费者还需要使用某些 Actions.register*Button 方法注册上述视图。
  • 以上逻辑在一个回调方法中执行。一旦上述方法完成,您的 SDK 将验证正确性(所有必需的操作都分配有一个可点击的视图)并继续。

现在,如何将这个回调方法传给sour SDK?

1.

It would be the nicest to pass a function in the Intent

这实际上相对容易实现(并且,IMO,一些严重的缺点)。

在您的 SDK 中创建静态 Map<Key, Callback> sCallbacks。当您的消费者使用您的 API 注册回调时,您将为它生成一个查找键并将其存储在地图中。您可以将密钥作为 Intent 额外传递。打开您的 SDK activity 后,它可以使用其意图中的密钥查找回调。

密钥可以是 StringUUID 或任何符合您需要并可以放在意图中的东西。

优点:

  • 具有欺骗性易于实施和使用。
  • 消费者只需一个文件即可使用您的 SDK。所有的调用代码都在一个地方。

缺点:

  • 您不负责创建回调的位置。消费者可能会在 Activity 中将其创建为匿名 class,这会导致内存泄漏。
  • 当进程终止时,您将丢失回调映射。如果应用程序在您的 SDK 中被终止 activity 您需要在应用程序重新启动时优雅地处理它。
  • 变量不在多个进程之间共享。如果你的SDKactivity和调用代码不在同一个进程,你的SDKactivity将找不到回调。请记住,消费者可以自由更改其活动的流程,甚至可以覆盖您自己的 activity 流程。

呼叫点看起来像 startSdk(context) { state, parent, actions -> /* ... */ }

这是迄今为止最适合消费者的方法,但一旦您离开典型的消费者设置区域,缺点就会开始显现。

2.

Make the Activity open, so sdk users can subclass it.

正如您所解释的,如果您不做出妥协,这是不可能的。

优点: ?

缺点:

  • 消费者需要在 AndroidManifest.xml 中注册他们的子 class 并取消注册您的原始 SDK activity。我假设清单合并已启用。这一个痛苦,因为我经常忘记看这里。
  • 消费者可以轻松访问您的activity并且可以随意破坏它。
  • 除非您的文档是原始的,否则消费者将很难弄清楚要在您的 activity.
  • 中覆盖什么

呼叫点看起来像 startSdk<MySdkActivity>(context)

作为消费者,我真的不明白这个选项的好处。我失去了#1 的好处,在 return 中一无所获。作为一名开发人员,我不能认可这种 'let the consumer deal with it' 态度。他们会破坏东西,你会收到错误报告,你最终将不得不处理它。

3.

在这里,我将尝试扩展评论中首先提到的想法。

回调将是您的 SDK 定义的抽象 class。它将按如下方式使用:

  • 消费者扩展此 class 并定义回调主体。 class 需要有一个空的构造函数并且是静态的。通常它会在自己的文件中定义。
  • class 名称在意图中传递给您的 SDK activity。
  • 您的 SDK activity 以反射方式创建回调实例。

回调class可以有几种方法,一种可以设置菜单,一种可以只设置视图层次结构,一种可以获取整个activity作为参数。消费者会选择他们需要的那个。同样,如果有多个选项,则需要详细记录。

优点:

  • 您将回调与调用站点(调用您的 SDK 的站点)分开。这很好,因为回调是在 你的 activity 内部执行的,与调用代码完全分开。
  • 因此你不能泄漏调用 activity。
  • 消费者不需要触摸AndroidManifest.xml。这很棒,因为注册回调与 Android 无关。 manifest主要是系统交互的东西。
  • 回调很容易在进程死亡后重新创建。它没有构造函数参数并且是无状态的。
  • 它适用于多个进程。如果有任何机会,消费者需要跨流程进行沟通,他们将负责如何实现这一点。您的 SDK 并没有像案例 #2 那样使它变得不可能。
  • 您可以保留 classes internal

缺点:

  • 您必须捆绑混淆规则以保留每个 class 的空构造函数扩展抽象回调 class。您还需要保留他们的 class 姓名。

我假设您将隐藏作为实现细节传递的意图,因此入口点可能类似于 startSdk<MyCallback>(context)

我喜欢这个,因为它将所有可能的责任从消费者转移到您,即 SDK 开发人员。你让消费者很难使用 API 错误。您保护消费者免受潜在错误的影响。

现在回到第一段。只要消费者可以获得上下文 (ViewGroup.getContext()),他们就能够访问 activity 和应用程序(在该进程中)。如果调用 activity 和您的 SDK activity 都在同一个进程中,消费者甚至可以访问他们准备好的 Dagger 组件。但他们不会以意想不到的方式覆盖您的 activity 方法。