在 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,想到了几种解决方案:
在 activity 上有一个调用 onCreate()
的静态回调,因此可以设置 setContentView()
,并在每次状态更改时调用 render(state: State)
.我们的 classes 按照我们的喜好进行了屏蔽,但是为此使用静态是有问题的。
打开Activity,让sdk用户可以subclass它。这意味着 activity 使用的每个 class 都必须从内部更改为 public.The classes 实际上应该是内部的,将通过使用 ProGuard 混淆它们来隐藏.
最好在 Intent
中传递一个函数,据我所知这是不可能的。
传一个POJO,我们定义了背景颜色等参数,对sdk用户限制最大,不在考虑范围内。
哪种解决方案最好?除了我们想到的方法,还有其他方法吗?
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 后,它可以使用其意图中的密钥查找回调。
密钥可以是 String
或 UUID
或任何符合您需要并可以放在意图中的东西。
优点:
- 它具有欺骗性易于实施和使用。
- 消费者只需一个文件即可使用您的 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 方法。
目前我们正在制作一个包含 Activity
的 Android 库。我们想要控制 activity 的确切流程和状态,但是让实现库的用户控制 UI 的样子。同时,我们希望公开最少的内部 classes.
SDK 用户可以决定视图的放置位置、大小、颜色;我们决定在 onClicks 上发生什么并提供 TextViews 的文本。
activity 使用模型-视图-意图模式,因此我们想要公开不可变状态。如果没有可调整的 UI,Activity 及其所有 class 都是内部的。有了可调整的 UI,还有很多 class 需要制作 public。这增加了破坏更新更改的风险并暴露了我们的逻辑。
为了暴露UI,想到了几种解决方案:
在 activity 上有一个调用
onCreate()
的静态回调,因此可以设置setContentView()
,并在每次状态更改时调用render(state: State)
.我们的 classes 按照我们的喜好进行了屏蔽,但是为此使用静态是有问题的。打开Activity,让sdk用户可以subclass它。这意味着 activity 使用的每个 class 都必须从内部更改为 public.The classes 实际上应该是内部的,将通过使用 ProGuard 混淆它们来隐藏.
最好在
Intent
中传递一个函数,据我所知这是不可能的。传一个POJO,我们定义了背景颜色等参数,对sdk用户限制最大,不在考虑范围内。
哪种解决方案最好?除了我们想到的方法,还有其他方法吗?
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 后,它可以使用其意图中的密钥查找回调。
密钥可以是 String
或 UUID
或任何符合您需要并可以放在意图中的东西。
优点:
- 它具有欺骗性易于实施和使用。
- 消费者只需一个文件即可使用您的 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 方法。