如何在跨平台应用程序中处理基于 UI 的导航?
Posted
技术标签:
【中文标题】如何在跨平台应用程序中处理基于 UI 的导航?【英文标题】:How to handle UI based Navigation in Cross Platform Apps? 【发布时间】:2015-07-27 20:15:53 【问题描述】:假设您有一个跨平台应用程序。该应用程序在 android 和 ios 上运行。您在两个平台上的共享语言是 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
直接打开ActivityB
。 ActivityB
现在负责在需要时与应用共享逻辑层进行通信。
对于更复杂或与逻辑相关的任何事情,我们使用 2 个选项:
ActivityA
调用一些 UseCase
的方法,该方法返回 enum
或 public 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 端实例化views
。 view factory
引用了您的域 的controller factory
,并使用它为请求的视图提供了一些实现特定接口的控制器。
示例:
点击“登录”按钮后,homeViewController
向 ViewControllerFactory 询问loginViewController
。该工厂依次向 ControllerFactory 请求实现accountHandling
接口的控制器。然后它实例化一个新的loginViewController
,给它刚刚收到的控制器,并将新实例化的视图控制器返回给homeViewController
。 homeViewController
然后将新的视图控制器呈现给用户。
由于您的“核心”与环境无关,并且仅包含您的 域 和业务逻辑,因此它应该保持稳定且不易被修改。
您可以查看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界面库