跨平台和本机方法在移动应用程序开发中的结合

仅针对一个移动平台发布应用程序是无关紧要的,您需要同时为iOS和Android开发两个版本。 在这里,您可以选择两种方式:为每个操作系统使用“本机”编程语言或使用跨平台框架。

在DD Planet上开发项目之一时,我依靠的是最后一个选择。 在本文中,我将讨论开发跨平台应用程序的经验,我们遇到的问题以及找到的解决方案。

开发跨平台移动应用程序的三种方法


首先,请考虑当您需要一次获得两个应用程序时使用什么方法:iOS和Android。

首先是时间和资源上最昂贵的:为每个平台开发一个单独的应用程序。 这种方法的复杂性在于每个操作系统都需要自己的方法:这既以开发进行中的语言表示(对于Android-Java或Kotlin,对于iOS-Objective-C或Swift),以及描述UI部分的方法应用程序(分别为axml和xib或情节提要文件)。

仅凭这一事实就使我们得出这样一个事实,对于这种方法,必须组成两个开发团队。 此外,您将必须为每个平台复制逻辑:与api交互以及业务逻辑。

图片

但是,如果使用的API数量增加,该怎么办?

图片

这就引出了一个问题:如何减少所需的人力资源? 无需为每个平台重复编写代码。 有足够的框架和技术可以解决此问题。

使用跨平台框架(例如Xamarin.Forms)可以用一种编程语言编写代码,并一次在一个地方描述数据逻辑和UI逻辑。 因此,不再需要使用两个开发团队。 编译项目的结果是,在输出中得到了两个本机应用程序。 这是第二种方法。

图片

我认为许多人都知道Xamarin是什么,或者至少听说过它,但是它如何工作? Xamarin基于.NET平台-Mono的开源实现。 Mono包括自己的C#编译器,运行时以及许多库,包括WinForms和ASP.Net的实现。

该项目的目标是允许用C#编写的程序在Windows以外的其他操作系统(Unix系统,Mac OS等)上运行。 本质上,Xamarin框架本身是一个类库,可为开发人员提供对平台SDK的访问以及针对它们的编译器。 反过来,Xamarin.Forms不仅允许您使用一种语言为两种平台编写代码,而且还可以使用XAML标记设计屏幕,这对于那些已经使用WPF应用程序的人来说是很熟悉的。 作为项目组装的结果,我们在所有平台上的外观几乎相同,因为在编译阶段,每个平台的所有XF控件都被转换为本机。

图片

仅当需要访问任何平台功能(例如,指纹扫描仪或电池电量)或有必要微调控制行为时,开发人员才被迫为每个平台编写代码。 在某些情况下,开发应用程序时,可能有必要编写与平台相关的代码,但是即使在这种情况下,也没有人禁止将平台功能带到接口并从一个公共项目中与之交互。

一种编程语言,很少的代码,等等。 听起来一切都很漂亮,但是Xamarin.Forms并不是万能的,它的所有美感都可以转化为现实的石头。 一旦出现内置XF控件不再满足其要求的情况,屏幕和控件的结构就会变得越来越复杂。 为了确保舒适地使用常见项目中的屏幕,您必须编写越来越多的自定义渲染。

这将继续到我们在开发应用程序时使用的第三种方法。

我们已经发现使用Xamarin Forms会使工作复杂化,而不是简化工作。 因此,为了实现在架构上复杂的屏幕,设计元素和控件与本机屏幕本质上不同,发现了一种折衷方案,并且有可能将第一种方法和第二种方法结合在一起。

我们具有相同的三个项目:一个普通的PCL项目,但没有Xamarin Forms,以及两个Xamarin Android和Xamarin iOS项目。 仍然有机会用一种语言编写所有内容,这是两个项目之间的通用逻辑,但是单个XAML标记没有任何限制。 UI组件由每个平台控制,并使用Android(本地AXML)和iOS(XIB)文件上的本地工具。 每个平台都有能力遵守其准则,因为Core和平台项目之间的连接仅在数据级别进行组织。

要组织这种关系,您可以使用MVVM设计模式及其在Xamarin中相当流行的实现-MVVMCross。 它的使用使您可以为每个屏幕保留一个通用的ViewModel,以描述作品的整个“业务逻辑”,并将其呈现委托给平台。 它还允许两个开发人员在同一个屏幕上工作(一个使用逻辑-另一个使用UI)并且彼此不干扰。 除了该模式的实现之外,我们还获得了足够多的工作工具:DI和IoC的实现。 为了将与平台的交互提升到通用代码级别,开发人员只需要声明一个接口并在平台上实现即可。 对于典型的事情,MvvmCross已经提供了一组自己的插件。 在团队中,我们使用Messenger插件在平台和通用代码之间交换消息,并使用该插件处理文件(从图库中选择图像等)。

我们解决了复杂设计和多层次导航的问题


如前所述,当在屏幕上使用复杂的表示形式时,该框架不仅使生活变得复杂,而且会使生活更加复杂。 但是什么叫做复杂元素? 由于我主要从事iOS开发,因此将考虑该平台的示例。 例如,像输入字段这样的琐碎事物可以具有多个状态以及足够的逻辑以进行切换和可视化。

图片

在使用用户输入的过程中,此处开发了这样的输入控件。 他可以在输入字段上方举起他的名字,使用掩码,设置前缀,后缀,在按下CapsLock时通知,以两种方式验证信息:禁止输入和输出错误信息。 控件内部的逻辑大约需要1000条线。 而且,似乎:在输入字段的设计中有什么可能会变得复杂?

我们看到了一个简单的复杂控件示例。 屏幕呢?

图片

首先,我要澄清的是,在大多数情况下,一个应用程序屏幕就是一个类-UIViewController,它描述了它的行为。 在开发过程中,需要创建多级导航。 开发的应用程序的概念归结为管理您的房地产以及与邻居和市政组织进行交互。 因此,构建了三个导航级别:属性,显示级别(房屋,城市,区域)和内容类型。 所有切换都在一个屏幕中进行。

这样做是为了使用户无论身在何处都能理解自己所看到的内容。 为了组织这样的导航,应用程序的主屏幕不仅由一个控制器组成。 从视觉上讲,它可以分为3部分,但是任何人都可以尝试猜测这里使用了多少个控制器吗?

图片

十五个主控制器。 这只是为了内容。

这样的怪物生活在主屏幕上,感觉还不错。 一个屏幕有15个控制器,当然很多。 这会影响整个应用程序的速度,因此您需要以某种方式对其进行优化。

我们拒绝同步初始化:所有视图模型仅在必要时在后台初始化。 为了减少渲染时间,我们还为这些屏幕放弃了xib文件:绝对定位和数学运算总是比计算元素之间的依赖关系更快。

要跟踪这么多控制器,您需要了解:

  • 他们每个人处于什么状况?
  • 用户在哪里;
  • 他希望转移到另一个控制器时看到的内容。

为此,我编写了一个单独的导航处理器,用于存储有关用户的位置,用户正在查看的内容类型,导航历史记录等信息。 他控制初始化的顺序和必要性。

由于每个选项卡都是一个控制器滑块(以便在它们上创建滑动过渡),因此您需要了解:每个选项卡都可以处于自己的状态(例如,“新闻”在一个菜单上处于打开状态,而另一项在“正在表决”中)。 随后是相同的导航处理器。 即使将显示级别从家庭到区域更改,我们也将保持相同的内容类型。

我们实时控制数据流


处理应用程序中的大量数据,您需要实时组织所有部分中的相关信息交付。 要解决此问题,可以区分3种方法:

  1. 通过计时器或触发器访问API并在屏幕上重新请求相关内容;
  2. 与服务器建立永久连接并实时接收更改;
  3. 接收内容更改的推送。

每种方法都有其优点和缺点,因此最好同时使用这三种方法,仅选择每种方法的优点。 我们有条件地将应用程序内的内容分为几种类型:热,常规和服务。 这样做是为了确定事件与用户通知之间的可接受时间。 例如,我们希望在聊天消息发送给我们后立即查看-这是热门内容。 另一种选择:从邻居那里投票。 现在或一会儿见到他都没有区别,因为这是普通的内容。 应用程序内部的小通知(未读消息,命令等)是需要紧急传递但不会占用大量数据的服务内容。

原来:

  • 热门内容-与API的永久连接;
  • 正常内容-对API的http请求;
  • 系统内容-推送通知。

最有趣的是保持恒定的连接。 编写自己的客户端以使用Web套接字是一个千疮百孔的一步,因此您需要寻找其他解决方案。 结果,我们停止了SignalR库。 让我们看看它是什么。

ASP.Net SignalR是Microsoft提供的一个库,可实时简化客户端与服务器的交互,并提供客户端与服务器之间的双向通信。 该服务器包括用于管理连接,连接断开事件的成熟API,用于将连接的客户端组合为组,授权的机制。

SignalR可以使用websockets,LongPolling和http请求作为传输。 您可以强制指定传输类型或信任该库:如果可以使用websocket,它将通过websocket运行,如果无法实现,则它将失败直到找到可接受的传输。 考虑到计划在移动设备上使用它,事实证明这一事实非常实用。

总计,我们将获得什么好处:

  • 能够在客户端和服务器之间交换任何类型的消息;
  • 自动在Web套接字,长池和Http请求之间切换的机制;
  • 有关连接当前状态的信息;
  • 有机会将客户分组
  • 处理组中发送消息逻辑的实用方法;
  • 扩展服务器的能力。

当然,这并不能满足所有需求,但显然可以使生活更轻松。

在项目内部,SignalR库上使用了包装器,这进一步简化了该包装器的工作,即:

  • 监视连接状态,根据指定条件并在发生中断时重新连接;
  • 能够快速替换或重新打开连接,以异步方式杀死旧的连接并将其交给垃圾收集器以进行撕毁-事实证明,连接方法的工作速度比关闭方法(Dispose或Stop)快十倍,这是关闭它的唯一方法;
  • 组织发送消息的队列,以便重新连接或重新打开连接不会中断发送;
  • 如果发生不可预见的错误,则将控制权转移给适当的代表。

这些包装程序(我们称它们为客户端)中的每个包装程序都与缓存系统协同工作,并且在断开连接的情况下,仅可以请求这段时间内可能丢失的数据。 “每种”是因为几种活性化合物可以同时保留。 在应用程序内部有一个完整的Messenger,并使用一个单独的客户端为其提供服务。

第二个客户端负责接收通知。 就像我已经说过的那样,通常的内容类型是通过http-requests获取的,将来它的更新将落在该客户端上,该客户端报告其中的所有重要更改(例如,投票已从一种状态转移到另一种状态,发布新新闻)。

可视化应用程序中的数据


图片

获取数据是一回事,显示是另一回事。 实时数据更新有其自身的困难。 至少,您需要决定如何向用户展示这些更新。 在应用程序中,我们使用三种类型的通知:

  1. 通知未读内容;
  2. 自动更新屏幕上的数据;
  3. 内容报价。

显示某处有新内容的最常见和最普通的方法是突出显示部分图标。 因此,几乎所有图标都具有将未读内容通知程序显示为红点的功能。 更有趣的是自动更新。

仅当新内容未重新排列屏幕且不更改控件大小时,才可能自动更新数据。 例如,在调查屏幕上:有关选票的信息只会更改进度条的值和百分比。 此类更改不会导致任何大小调整;可以立即应用它们而不会出现问题。

当您需要向列表中添加新内容时会遇到困难。 实际上,应用程序中的所有列表都是ScrollView,并且具有几个特征:窗口大小,内容大小和滚动位置。 它们都有一个静态开始(屏幕顶部的坐标为0; 0),并且可以向下扩展。 在列表下方添加新内容,最后没有任何问题,列表将保持不变。 但是新内容应该出现在顶部,这是图片:

图片

在3个元素上,我们将在2个元素上-滚动将弹起。 并且由于新内容会不断到达,因此用户将无法正常滚动。 您可能会说:为什么不计算新内容的大小并向下滚动到该值? 是的,可以做到。 但是随后您必须手动控制滚动位置,并且如果此时用户向任何方向滚动,其操作都会被中断。 这就是为什么未经用户同意不能实时更新这些屏幕的原因。

在这种情况下,最好的解决方案是通知用户,当他滚动提要时,有人发布了新内容。 在我们的设计中,它看起来像屏幕角落的红色圆圈。 通过单击它,用户向我们表示有条件的同意,将其返回到屏幕顶部并显示新内容。

通过这种方法,我们当然避免了“杂乱”内容的问题,但是仍然必须解决它们。 即,在聊天屏幕上,由于在与屏幕进行通信和交互期间,必须在不同的地方显示新内容。

聊天列表和常规列表之间的区别在于,新鲜内容位于屏幕底部。 由于这是一个“尾巴”,因此您可以在那里轻松添加内容。 用户在这里花费了90%的时间,这意味着您需要在接收和发送消息时不断保持滚动位置并向下滚动。 在实时对话中,必须经常执行此类操作。

第二点:向上滚动时加载历史记录。 就在加载故事时,我们发现自己处于一种需要将消息置于审阅级别之上的情况(这会带来偏差),从而使滚动平滑且连续。 并且正如我们已经知道的,为了不打扰用户,不可能手动控制滚动位置。

我们找到了解决方案:我们将其移交了。 屏幕翻转立即解决了两个问题:

  1. 列表的尾部在顶部,因此我们可以无缝添加故事,而不会干扰用户滚动;
  2. 最后一条消息始终位于列表的顶部,我们不需要滚动屏幕。

图片

该解决方案还有助于加快渲染速度,并通过滚动控制消除了不必要的操作。

说到性能。 在屏幕的第一个版本中,滚动消息时检测到明显的缩水。由于“金钱”中的内容杂乱无章-文本,文件,照片-您必须不断重新计算单元格的大小,添加和删除金钱中的元素。因此,需要优化气泡。我们所做的与主屏幕相同,只是用绝对定位部分渲染了面团。

在iOS中使用列表时,在绘制单元格之前,您需要知道其高度。因此,在将新消息添加到列表之前,您需要准备所有必要的信息以在单独的流中显示,计算单元格的高度,处理用户数据,并且只有在我们找到并缓存了所需的所有内容之后,才将单元格添加到列表中。

结果,我们获得了平滑的滚动并且没有过载的UI流。

总结一下:


  • 跨平台开发节省了时间和金钱;
  • , , ;
  • , ;
  • ;
  • SignalR – - ;
  • ;
  • , , ;
  • , SignalR-, , , , .

Source: https://habr.com/ru/post/zh-CN430892/


All Articles