如何在跨平台应用程序中处理基于 UI 的导航?

Posted

技术标签:

【中文标题】如何在跨平台应用程序中处理基于 UI 的导航?【英文标题】:How to handle UI based Navigation in Cross Platform Apps? 【发布时间】:2015-07-27 20:15:53 【问题描述】:

假设您有一个跨平台应用程序。该应用程序在 androidios 上运行。您在两个平台上的共享语言是 Java。通常,您会用 Java 编写业务逻辑,而所有 UI 特定部分都用 Java(适用于 Android)和 Objective-C(适用于 iOS)。

通常,当您在跨平台、跨语言应用程序中实现 MVP pattern 时,您将拥有 Java 中的模型和演示者,并为您的视图提供演示者已知的 Java 接口。这样,您的共享 Java 演示者可以与您在平台特定部分上使用的任何视图实现进行通信。

假设我们想要编写一个带有 Java 部分的 iOS 应用程序,该部分以后可以与同一个 Android 应用程序共享。这是设计的图形表示:

左侧是 Java 部分。在 Java 中,您编写模型、控制器以及视图接口。您使用依赖注入进行所有接线。然后可以使用J2objc 将Java 代码转换为Objective-C。

右侧是 Objective-C 部分。在这里,您的UIViewController 可以实现翻译成ObjectiveC 协议的Java 接口。

问题:

我苦苦思索的是视图之间的导航是如何发生的。假设您在 UIViewControllerA 上,并且您点击了一个按钮,该按钮应将您带到 UIViewControllerB。你会怎么做?

案例一:

您将按钮点击报告给 UIViewControllerA 的 Java ControllerA (1),Java ControllerA 调用链接到 UIViewControllerB (3) 的 Java ControllerB (2)。然后你有一个问题,你不知道从 Java 控制器端如何将 UIViewControllerB 插入到 Objective-C 视图层次结构中。您无法从 Java 端处理它,因为您只能访问 View 接口。

案例 2:

您可以转换到 UIViewControllerB,无论它是模态的还是使用 UINavigationController 或其他 (1)。然后,首先您需要绑定到 Java ControllerB (2) 的 UIViewControllerB 的正确实例。否则 UIViewControllerB 无法与 Java ControllerB (2,3) 交互。当您拥有正确的实例时,您需要告诉 Java ControllerB 视图 (UIViewControllerB) 已显示。

我仍在努力解决如何处理不同控制器之间的导航问题。

如何对不同Controller之间的导航进行建模并适当处理跨平台View的变化?

【问题讨论】:

我认为 Spring MVC 是一个很好的方法。他们有一个执行上下文来存储整个请求处理的数据,下一个要执行的视图/动作按名称(字符串)映射并在运行时决定。这样,在 Android Java 应用程序中,您可以有一个控制器将点击请求的处理委托给 ObjC。它返回要执行的下一个视图/动作的名称,并且控制器会做出正确的反应。 能否更具体一些并展示一些抽象的示例代码? 参见此处tutorialspoint.com/spring/spring_mvc_form_handling_example.htm StudentController 对 Context 进行任何操作(它使用 @ModelAttribute 从其中获取一个 bean,但整个上下文只是名称-值对的映射。然后以 @ 响应987654329@ 对“结果”的引用一无所知。应该是 StudentController 实际上是在其上运行的真实控制器的代表,根据 @987654331 决定下一步做什么@value.result可以是一个jsp页面的名字,另一个action... @eduyayo SpringMVC 是一个用于 Web 的服务器客户端框架。这与我的问题无关。这对我有什么帮助? 您问的是“一种方式”...这是一种方式...我没有说您必须通过嵌入 tomcat 对其进行编程。这是一种方式……你明白吗? 【参考方案1】:

简答:

我们是这样做的:

对于简单的“正常”内容(例如打开设备摄像头的按钮或打开另一个Activity/UIViewController,操作背后没有任何逻辑)-ActivityA 直接打开ActivityBActivityB 现在负责在需要时与应用共享逻辑层进行通信。 对于更复杂或与逻辑相关的任何事情,我们使用 2 个选项:
    ActivityA 调用一些 UseCase 的方法,该方法返回 enumpublic static final int 并相应地采取一些行动-或- 说UseCase 可以调用我们之前注册的ScreenHandler 的方法,它知道如何使用一些提供的参数从应用程序的任何位置打开公共Activities

长答案:

我是一家公司的首席开发人员,该公司使用 java 库用于应用程序的模型、逻辑和业务规则,这两个移动平台(Android 和 iOS)都使用 j2objc 实现。

我的设计原则直接来自 Bob 叔叔和 SOLID,我真的不喜欢在设计包含组件间通信的整个应用程序时使用 MVP 或 MVC,因为这样你就开始将每个 Activity 与 1 和只有 1 Controller 联系起来这有时是可以的,但大多数时候你最终会得到一个控制器的上帝对象,它的变化往往与View 一样多。这可能会导致严重的代码异味。

我最喜欢的处理方式(也是我认为最干净的方式)是将所有内容分解为 UseCases,每个处理应用程序中的 1 个“情况”。当然,您可以拥有一个 Controller 来处理其中几个 UseCases,但它所知道的只是如何委派给这些 UseCases,仅此而已。

此外,我看不到将Activity 的每个操作链接到位于逻辑层中的Controller 的原因,如果此操作是简单的“带我到地图屏幕”或类似的任何操作种类。 Activity 的角色应该是处理它所拥有的 Views,并且作为生活在应用程序生命周期中的唯一“智能”事物,我认为它没有理由不能调用下一个活动本身的开始.

此外,Activity/UIViewController 生命周期太复杂且彼此之间太不同,无法由通用 java 库处理。这是我认为是“细节”而不是真正的“业务规则”的东西,每个平台都需要实现和担心,从而使java lib中的代码更加稳固,不易改变。

再次重申,我的目标是让应用程序的每个组件都尽可能符合 SRP(单一职责原则),这意味着将尽可能少的东西链接在一起。

一个简单的“正常”东西的例子:

(所有例子都是虚构的)

ActivityAllUsers 显示模型对象项的列表。这些项目来自调用AllUsersInteractor - 在后台线程中的UseCase controller(这反过来也由java lib处理,并在请求完成时调度到主线程)。用户单击此列表中的一项。在这个例子中,ActivityAllUsers 已经有模型对象,所以打开ActivityUserDetail 是一个直接调用这个数据模型对象的包(或其他机制)。如果需要进一步的操作,新活动ActivityUserDetail 负责创建和使用正确的UseCases

复杂逻辑调用示例:

ActivityUserDetail 有一个标题为“加为好友”的按钮,单击该按钮时会调用ActivityUserDetail 中的回调方法onAddFriendClicked

public void onAddFriendClicked()   
  AddUserFriendInteractor addUserFriend = new AddUserFriendInteractor();
  int result = addUserFriend.add(this.user);
  switch(result)
    case AddUserFriendInteractor.ADDED:
      start some animation or whatever
      break;
    case AddUserFriendInteractor.REMOVED:
      start some animation2 or whatever
      break;
    case AddUserFriendInteractor.ERROR:
      show a toast to the user
      break;
    case AddUserFriendInteractor.LOGIN_REQUIRED:
      start the log in screen with callback to here again
      break;

  

更复杂的调用示例

Android 上的 BroadcastReceiver 或 iOS 上的 AppDelegate 会收到推送通知。这将发送到位于 java lib 逻辑层中的NotificationHandler。在App.onCreate() 中构造一次的NotificationHandler 构造函数中,它需要一个ScreenHandler interface,您在两个平台上都实现了它。解析这条推送通知,在ScreenHandler中调用正确的方法打开正确的Activity

底线是:尽可能让View 保持愚蠢,让Activity 足够聪明以处理自己的生命周期和处理自己的视图,并与自己的controllers 通信(复数! ),其他所有内容都应该在 java lib 中编写(希望测试优先;))。

使用这些方法,我们的应用目前在 java lib 中运行了大约 60-70% 的代码,下一次更新有望将其提高到 70-80%。

【讨论】:

【参考方案2】:

我建议您使用某种插槽机制。与其他 MVP 框架使用的类似。

定义:插槽是视图的一部分,可以插入其他视图。

在您的演示者中,您可以根据需要定义任意数量的插槽:

GenericSlot slot1 = new GenericSlot();
GenericSlot slot2 = new GenericSlot();
GenericSlot slot3 = new GenericSlot();

这些插槽必须在演示者的视图中具有引用。你可以实现一个

setInSlot(Object slot, View v);

方法。如果您在视图中实现setInSlot,则视图可以决定如何包含它。

看看插槽是如何实现的here。

【讨论】:

【参考方案3】:

在共享我称之为“核心”(用 Java 编写的应用程序的)的跨平台开发中,我倾向于将所有权授予 UI查看下一个显示。这使您的应用程序更加灵活,可以根据需要适应环境(在 iOS 上使用 UINavigationController,在 Android 上使用 Fragments 以及在 Web 界面上使用动态内容的单个页面)。

您的controllers 不应绑定到视图,而是履行特定角色(用于登录/注销的 accountController,用于显示和编辑配方的 recipeController 等)。

您的controllers 将使用interfaces 而不是views。然后您可以使用Factory design pattern 在 端(您的Java 代码)实例化您的controllers,并在UI 端实例化viewsview factory 引用了您的controller factory,并使用它为请求的视图提供了一些实现特定接口的控制器。

示例: 点击“登录”按钮后,homeViewControllerViewControllerFactory 询问loginViewController。该工厂依次向 ControllerFactory 请求实现accountHandling 接口的控制器。然后它实例化一个新的loginViewController,给它刚刚收到的控制器,并将新实例化的视图控制器返回给homeViewControllerhomeViewController 然后将新的视图控制器呈现给用户。

由于您的“核心”与环境无关,并且仅包含您的 和业务逻辑,因此它应该保持稳定且不易被修改。

您可以查看this simplified demo project I made,它说明了这种设置(减去接口)。

【讨论】:

以上是关于如何在跨平台应用程序中处理基于 UI 的导航?的主要内容,如果未能解决你的问题,请参考以下文章

LTUI v2.4 发布, 一个基于lua的跨平台字符终端UI界面库

LTUI v2.2 发布, 一个基于lua的跨平台字符终端UI界面库

LTUI v1.7 发布, 一个基于lua的跨平台字符终端UI界面库

LTUI v2.4 发布, 一个基于lua的跨平台字符终端UI界面库

LTUI v1.1, 一个基于lua的跨平台字符终端UI界面库

LTUI v2.2 发布, 一个基于lua的跨平台字符终端UI界面库