混合式移动应用是一种带有自己浏览器的web应用,可以运行在任何移动平台上(Android、iOS、Windows等)。混合应用程序的最大优势是它有一个单一的代码库,而原生应用程序必须针对每个平台重新编写。
Apache科尔多瓦是一个用于开发混合应用程序的开源框架。为了使用本地功能(除了那些已经通过hmtl5提供的功能),应用程序可以使用插件。这些插件处理特定于平台的代码,可用于单个或多个平台。例如,插件可以访问GPS、加速计或相机。
SAP已经编写了自己的插件,命名为Kapsel,以处理登录和脱机OData之类的事情。这些为混合应用程序带来了企业特色。
直到最近,SAP推荐的方法是使用混合应用程序工具包(HAT)来构建混合应用程序。应用程序构建是在开发人员的PC或Mac上进行的。HAT以难以设置而闻名,这阻碍了SAP生态系统中混合应用程序的使用。
Fiori Mobile的核心是云构建服务,因此不需要安装HAT。我们可以从Web IDE触发构建。我们指定我们想要打包的(已经部署的)Fiori应用程序,几分钟后,我们可以下载.apk (Android)和.ipa (iOS)文件,准备安装到移动设备上。
云构建服务运行良好。它通常在8分钟内为两个平台构建,没有任何问题。由于技术问题而导致构建失败的情况只有几次。
这项技术的某些方面还不成熟。一个方面是离线OData特性。当使用Fiori Mobile时,事情与更传统的混合和本地应用程序不同。目的地就是一个例子,因为Fiori Mobile应用程序使用一个引用SAP Cloud Platform (CP)门户服务的生成目的地。
我的观点不是离线OData看起来不成熟,而是离线功能在与Fiori移动应用程序一起使用时可能会很棘手。我们仍在接受SAP的帮助,以优化我们应用程序的离线功能。这是一个遗憾,因为共享数据和处理增量请求,例如,是两个功能,证明了“中间件”的方法脱机SAP已经采取。
身份验证也是一个挑战。在之前的项目中,我使用了菲奥里客户与SAP Cloud Identity(现在正式SAP云平台身份认证).Fiori Client将用户名和密码存储在设备上,这样用户就不需要在每次会话超时时都输入它们。我们的Fiori手机应用还没有做到这一点。
如果我们使用SAP Fori Mobile Service首先构建移动应用程序,我们需要创建并发布应用程序将部署的站点。否则,如果使用HAT,则跳过此步骤并继续使用创建Fiori项目
去门户服务并创建新的站点目录
将显示一个弹出窗口。输入您的网站的名字,选择SAP Fiori发射台,单击“创建”
在此之后,您将被重定向到SAP Fiori配置驾驶舱。关闭选项卡,回到Portal Service的Admin Space。
现在我们需要发布这个网站,并设置为默认值。
一旦我们创建和发布cmbSDKTestSite,我们就可以创建我们的Fiori项目(如果我们使用Fiori移动服务来构建移动应用程序,否则如果我们使用HAT,我们不需要在Launchpad上发布网站,创建Fiori项目将是我们的第一步)。
再次转到服务选项卡,打开SAP Web IDE全栈服务,然后单击转到服务
一个新的标签将被打开。从这里,我们将开始创建我们的Fiori项目。
在SAP Web IDE工作区中,我们可以看到Fiori项目包含的所有文件。
开放Component.js文件。
Component.js是我们应用程序的第一个点,我们可以说它是一个索引,它封装了我们所有应用程序的细节,即视图名称,路由细节,主视图,应用程序类型(全屏或SplitApp),应用程序服务配置等。
在这里初始化函数,我们将配置我们的插件。设置我们需要的属性,设置回调函数,调用一些方法,等等。
sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/Device", "MyFirstFioriApp/MyFirstFioriApp/model/models"], function (UIComponent, Device, models) {"use strict";var oEventBus = sap.ui.getCore().getEventBus();var scannerIsInitialized = false;返回UIComponent.extend(“MyFirstFioriApp.MyFirstFioriApp。Component", {metadata: {manifest: "json"}, /** *该组件在应用启动时由UI5自动初始化,并调用init方法一次。* @public * @override */ init: function(){//调用基组件的init函数UIComponent.prototype.init。应用(这个参数);//启用路由this.getRouter().initialize();//设置设备模型this.setModel(models.createDeviceModel(), "device");if (!scannerIsInitialized) {scannerIsInitialized = true;window.readerConnected = 0; window.scannerActive = false; cmbScanner.addOnResume(function (result) { cmbScanner.setAvailabilityCallback((readerAvailability) => { if (readerAvailability === cmbScanner.CONSTANTS.AVAILABILITY_AVAILABLE) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "AVAILABLE" }); cmbScanner.connect((result) => {}); } else { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "NOT AVAILABLE" }); } }); if (Device.os.android) { cmbScanner.connect((result) => {}); } }); cmbScanner.addOnPause(function (result) { if (Device.os.android) { cmbScanner.disconnect(); } cmbScanner.setAvailabilityCallback(); }); cmbScanner.setPreviewContainerPositionAndSize(0, 0, 100, 50); cmbScanner.setConnectionStateDidChangeOfReaderCallback((connectionState) => { if (connectionState === cmbScanner.CONSTANTS.CONNECTION_STATE_CONNECTED) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "CONNECTED" }); if (window.readerConnected != connectionState) { cmbScanner.setSymbologyEnabled("SYMBOL.DATAMATRIX", true); cmbScanner.setSymbologyEnabled("SYMBOL.C128", true); cmbScanner.sendCommand("SET TRIGGER.TYPE 2"); } } else if (connectionState == cmbScanner.CONSTANTS.CONNECTION_STATE_DISCONNECTED) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "DISCONNECTED" }); } else if (connectionState == cmbScanner.CONSTANTS.CONNECTION_STATE_CONNECTING) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "CONNECTING" }); } else if (connectionState == cmbScanner.CONSTANTS.CONNECTION_STATE_DISCONNECTING) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "DISCONNECTING" }); } window.readerConnected = connectionState; }); cmbScanner.setAvailabilityCallback((readerAvailability) => { if (readerAvailability === cmbScanner.CONSTANTS.AVAILABILITY_AVAILABLE) { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "AVAILABLE" }); cmbScanner.connect((result) => {}); } else { oEventBus.publish("ScanView", "SetConnectionStatus", { statusText: "NOT AVAILABLE" }); } }); cmbScanner.setActiveStartScanningCallback((result) => { if (result === true) window.scannerActive = true; else window.scannerActive = false; }); cmbScanner.setPreviewOptions(cmbScanner.CONSTANTS.PREVIEW_OPTIONS.DEFAULTS | cmbScanner.CONSTANTS.PREVIEW_OPTIONS.HARDWARE_TRIGGER); cmbScanner.setCameraMode(cmbScanner.CONSTANTS.CAMERA_MODES.NO_AIMER); var sdkKEY = ""; if (Device.os.android) sdkKEY = this.getModel("i18n").getProperty("MX_MOBILE_LICENSE_ANDROID"); else sdkKEY = this.getModel("i18n").getProperty("MX_MOBILE_LICENSE_iOS"); cmbScanner.registerSDK(sdkKEY); cmbScanner.loadScanner(0, (result) => { cmbScanner.connect((result) => {}); }); oEventBus.subscribe("Component", "LoadScanner", this.loadScanner, this); } }, loadScanner: function (sChanel, sEvent, oData) { cmbScanner.disconnect((result) => { cmbScanner.loadScanner(oData.selectedDevice, (result) => { cmbScanner.connect((result) => {}); }); }); }, onExit: function () { oEventBus.unsubscribe("Component", "LoadScanner", this.loadScanner, this); cmbScanner.disconnect(); cmbScanner.setAvailabilityCallback(); } }); });
cmbScanner是一个表示插件的对象。有了这个对象,我们可以访问所有API方法和常量来自我们的插件。
这里我们只配置阅读器设备、处理连接、可用性等。我们将在其他视图中进行扫描并获得结果。如果我们想通知用户每一个连接状态的改变或其他信息的阅读器设备,我们将使用EventBussap对象。有了这个对象,我们可以发布函数,并从应用程序中的任何视图调用它们。在这个例子中,我们调用的EventBus改变了连接状态SetConnectionStatus执行扫描并设置标签文本以显示用户当前连接状态的视图中实现的函数。
现在打开ScanView.view.xml并添加以下代码:
controllerName = " MyFirstFioriApp.MyFirstFioriApp.controller < mvc:视图。ScanView sap.ui.core“xmlns: mvc =”。mvc" displayBlock="true" xmlns="sap. mvc" displayBlock="true"m sap.ui“xmlns:核心=”。>
你可以用CodeEditor或LayoutEditor设计视图。
在那之后ScanView.controller.js然后添加这段代码
sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) {"use strict";var oEventBus = sap.ui.getCore().getEventBus();var oModel = new sap.ui.model.json.JSONModel();oModel。setData({Devices: [{key: "0", text: "MX Device"}, {key: "1", text: "Mobile Camera"}]});返回Controller.extend(“MyFirstFioriApp.MyFirstFioriApp.controller。ScanView", {onInit: function () {this.getView().setModel(oModel);}, onAfterRendering: function(){窗口。scannerActive = false;switch (window.readerConnected) {case cmbScanner.CONSTANTS. {case cmbScanner.CONSTANTS.}CONNECTION_STATE_CONNECTED: this.getView () .byId(“lblStatus”). settext(“连接”);打破; case cmbScanner.CONSTANTS.CONNECTION_STATE_DISCONNECTED: this.getView().byId("lblStatus").setText("DISCONNECTED"); break; case cmbScanner.CONSTANTS.CONNECTION_STATE_CONNECTING: this.getView().byId("lblStatus").setText("CONNECTING"); break; case cmbScanner.CONSTANTS.CONNECTION_STATE_DISCONNECTING: this.getView().byId("lblStatus").setText("DISCONNECTING"); break default: this.getView().byId("lblStatus").setText("UNKNOWN"); break; } oEventBus.subscribe("ScanView", "SetConnectionStatus", this.setConnectionStatus, this); cmbScanner.setResultCallback((result) => { if(result && result.readResults && result.readResults.length > 0){ result.readResults.forEach((item, index) => { if (item.goodRead == true) { //Perform some action on barcode read //example: var verticalLayoutContainer = new sap.ui.layout.VerticalLayout(null, { width: "100%" }).addStyleClass("sapUiSmallMarginTop"); verticalLayoutContainer.addContent(new sap.m.Label({ text: item.symbologyString + ":", textAlign: "Begin", design: "Bold" }).addStyleClass("sapUiSmallMarginBegin")); verticalLayoutContainer.addContent(new sap.m.Text({ text: item.readString, textAlign: "Begin" }).addStyleClass("sapUiSmallMarginBegin")); this.getView().byId("flexBoxContainer").addItem(verticalLayoutContainer); } else{ //Perform some action when no barcode is read or just leave it empty } }); } }); }, btnScanPress: function () { if (window.readerConnected === cmbScanner.CONSTANTS.CONNECTION_STATE_CONNECTED) { if (window.scannerActive === true) { cmbScanner.stopScanning(); } else { cmbScanner.startScanning(); } } }, setConnectionStatus: function (sChanel, sEvent, oData) { this.getView().byId("lblStatus").setText(oData.statusText); }, activeDeviceChanged: function () { cmbScanner.disconnect((result) => { oEventBus.publish("Component", "LoadScanner", { selectedDevice: parseInt(this.getView().byId("selectActiveDevice").getSelectedKey()) }); }); }, onExit: function () { oEventBus.unsubscribe("ScanView", "SetConnectionStatus", this.setConnectionStatus, this); if (window.readerConnected === cmbScanner.CONSTANTS.CONNECTION_STATE_CONNECTED) { if (window.scannerActive === true) { cmbScanner.stopScanning(); } } cmbScanner.setResultCallback((result) => { return false; }); } }); });
在这个视图中,我们有一个Scan按钮startScanning () / stopScanning ()而且cmbScanner.setResultCallback处理成功的扫描结果。
在本节中,我们将解释如何构建Fiori移动应用程序(生成.ipa和.apk文件)。
1.使用帽子(混合应用工具箱)
2.使用Fiori移动服务
在我们开始使用Fiori移动服务构建我们的Fiori移动应用程序之前,我们需要在上面部署我们的项目SAP HANA云平台.
如果您计划使用cmbSDK使用智能手机或平板电脑(没有MX移动终端)进行移动扫描,SDK需要安装许可密钥。没有许可密钥,SDK仍将运行,尽管扫描结果将被模糊化(SDK将随机地将扫描结果中的字符替换为星号字符)。
请与您的康耐视销售代表联系,了解如何获取许可证密钥,包括可用于30天评估SDK的试用许可证。
获得许可密钥后,打开i18n。属性文件在您的项目上的SAP WEB IDE服务,并设置您获得的密钥。
然后回到Component.js文件和检查这段代码:
...var sdkKEY = "";if (Device.os.android) sdkKEY = this.getModel("i18n").getProperty("MX_MOBILE_LICENSE_ANDROID");else sdkKEY = this.getModel("i18n").getProperty("MX_MOBILE_LICENSE_iOS");cmbScanner.registerSDK (sdkKEY);...
你可以看到你从i18n读取了这个键。属性和调用cmbScanner.registerSDK方法注册SDK与您的许可密钥。