如何在没有Ionic的情况下使用Angular调用Cordova插件



我在Angular 6中构建了一个PWA,并打算使用Google Maps API进行地理定位。然而,我很快意识到,PWA中的地理定位服务只有在用户与UI交互以故意请求其位置时才能工作。然而,我的应用程序应该在用户开车四处走动时在后台跟踪用户的位置。因此,当他们锁定屏幕或转到另一个应用程序时,它仍然应该跟踪他们。记录在案,这是一个私人应用程序,用户完全知道他们正在被跟踪。因此,我使用Cordova将PWA转换为混合应用程序。到目前为止,一切都和我已经做的一样(模拟很好,等等),但我似乎不知道如何添加地理定位部分。我已经安装了这个插件,它似乎已经安装并可用。我看到的所有例子都使用Ionic(目前我不需要它),并且适用于用户单击按钮获取位置的场景,但我需要它,因为地理定位的东西在服务中,并且在他们登录后开始在后台运行。我似乎找不到能说明如何做到这一点的东西。以下是我想我应该做的:

(这还不完整,我只是把GitHub的示例代码粘贴在这里,当我知道它实际上被调用时,我打算用"stuff"填充它)

_services/geolocation.service.ts:

import { Injectable } from '@angular/core';
import { Component, ViewChild } from '@angular/core';
declare var cordova: any;
@Injectable()
export class GeolocationService {
startBackgroundGeolocation() {
console.log("Geolocation service called...");
cordova.plugins.BackgroundGeolocation.configure({
locationProvider: cordova.plugins.BackgroundGeolocation.ACTIVITY_PROVIDER,
desiredAccuracy: cordova.plugins.BackgroundGeolocation.HIGH_ACCURACY,
stationaryRadius: 50,
distanceFilter: 50,
notificationTitle: 'Background tracking',
notificationText: 'enabled',
debug: true,
interval: 10000,
fastestInterval: 5000,
activitiesInterval: 10000,
url: 'http://192.168.0.3:3000/location',
httpHeaders: {
'X-FOO': 'bar'
},
// customize post properties
postTemplate: {
lat: '@latitude',
lon: '@longitude',
foo: 'bar' // you can also add your own properties
}
});
cordova.plugins.BackgroundGeolocation.on('location', function(location) {
// handle your locations here
// to perform long running operation on iOS
// you need to create background task
cordova.plugins.BackgroundGeolocation.startTask(function(taskKey) {
// execute long running task
// eg. ajax post location
// IMPORTANT: task has to be ended by endTask
cordova.plugins.BackgroundGeolocation.endTask(taskKey);
});
});
cordova.plugins.BackgroundGeolocation.on('stationary', function(stationaryLocation) {
// handle stationary locations here
});
cordova.plugins.BackgroundGeolocation.on('error', function(error) {
console.log('[ERROR] cordova.plugins.BackgroundGeolocation error:', error.code, error.message);
});
cordova.plugins.BackgroundGeolocation.on('start', function() {
console.log('[INFO] cordova.plugins.BackgroundGeolocation service has been started');
});
cordova.plugins.BackgroundGeolocation.on('stop', function() {
console.log('[INFO] cordova.plugins.BackgroundGeolocation service has been stopped');
});
cordova.plugins.BackgroundGeolocation.on('authorization', function(status) {
console.log('[INFO] cordova.plugins.BackgroundGeolocation authorization status: ' + status);
if (status !== cordova.plugins.BackgroundGeolocation.AUTHORIZED) {
// we need to set delay or otherwise alert may not be shown
setTimeout(function() {
var showSettings = confirm('App requires location tracking permission. Would you like to open app settings?');
if (showSettings) {
return cordova.plugins.BackgroundGeolocation.showAppSettings();
}
}, 1000);
}
});
cordova.plugins.BackgroundGeolocation.on('background', function() {
console.log('[INFO] App is in background');
// you can also reconfigure service (changes will be applied immediately)
cordova.plugins.BackgroundGeolocation.configure({ debug: true });
});
cordova.plugins.BackgroundGeolocation.on('foreground', function() {
console.log('[INFO] App is in foreground');
cordova.plugins.BackgroundGeolocation.configure({ debug: false });
});
cordova.plugins.BackgroundGeolocation.on('abort_requested', function() {
console.log('[INFO] Server responded with 285 Updates Not Required');
cordova.plugins.BackgroundGeolocation.stop();
// Here we can decide whether we want stop the updates or not.
// If you've configured the server to return 285, then it means the server does not require further update.
// So the normal thing to do here would be to `cordova.plugins.BackgroundGeolocation.stop()`.
// But you might be counting on it to receive location updates in the UI, so you could just reconfigure and set `url` to null.
});
cordova.plugins.BackgroundGeolocation.on('http_authorization', () => {
console.log('[INFO] App needs to authorize the http requests');
});
cordova.plugins.BackgroundGeolocation.checkStatus(function(status) {
console.log('[INFO] cordova.plugins.BackgroundGeolocation service is running', status.isRunning);
console.log('[INFO] cordova.plugins.BackgroundGeolocation services enabled', status.locationServicesEnabled);
console.log('[INFO] cordova.plugins.BackgroundGeolocation auth status: ' + status.authorization);
// you don't need to check status before start (this is just the example)
if (!status.isRunning) {
cordova.plugins.BackgroundGeolocation.start(); //triggers start on start event
}
});
// you can also just start without checking for status
// cordova.plugins.BackgroundGeolocation.start();
// Don't forget to remove listeners at some point!
// cordova.plugins.BackgroundGeolocation.events.forEach(function(event) {
//   return cordova.plugins.BackgroundGeolocation.removeAllListeners(event);
// });
}
}

然后从app.component:

import { Component, OnInit } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
transition,
trigger,
query,
style,
animate,
group,
animateChild
} from '@angular/animations';
import { GeolocationService } from './_services/geolocation.service';
declare const device;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('routeAnimation', [
transition('* => *', [
query(
':enter',
[style({ opacity: 0 })],
{ optional: true }
),
query(
':leave',
[style({ opacity: 1 }), animate('0.3s', style({ opacity: 0 }))],
{ optional: true }
),
query(
':enter',
[style({ opacity: 0 }), animate('0.3s', style({ opacity: 1 }))],
{ optional: true }
)
])
])
]
})
export class AppComponent implements OnInit{
title = 'HLD Phlebotomist App';
constructor(private geolocationService: GeolocationService) { }
ngOnInit() { 
document.addEventListener("deviceready", function() {
this.geolocationService.startBackgroundGeolocation();
alert(device.platform);
}, false); 
} 
}

然而,当我在android模拟器中运行此程序时,我会得到"Uncaught TypeError:无法读取未定义的属性'startBackgroundGeolocation'"。不知道为什么。有人能帮我理解这里的结构吗?我想我的问题是我不完全理解如何"调用"Cordova插件。

我之所以在这里写这篇文章,是因为我很难在一个地方找到这些答案,并从各种博客文章和stackoverflow文章中拼凑出来,所有这些似乎都不完整。

(1)非常重要的引导

持续的错误和头痛的根源是,在某些情况下,Angular和核心应用程序将在移动设备(包括android和iOS)准备好提供对相机等系统资源的访问之前启动。这是我一直在做的事情,直到最后在我们的应用程序中添加SSO作为第一步,这让我在超过75%的加载场景中遇到了这个问题

这里的解决方案非常直接。在angular应用程序的主界面中,添加一个javascript文档。addEventListener,等待cordova说设备在引导angular之前准备好。

document.addEventListener('deviceready', bootstrap, false);

实际上,我们又向前迈进了一步,添加了一个条件块,只有当我们在环境变量中打开cordova脚本标记时,该块才会注入它。这使得在我们的测试环境中使用ng-server测试非cordova功能变得更加容易。

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e));
const bootstrapCordova = () => {
/** Dynamically load cordova JS **/
console.log('bootstrapped cordova');
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'cordova.js';
document.head.appendChild(script);
if (script['readyState']) {
// IE 2018 and earlier (?). They're migrating to using Chromium under the hood, so this may change
script['onreadystatechange'] = () => {
if (script['readyState'] === "loaded" || script['readyState'] === "complete") {
document.addEventListener('deviceready', bootstrap, false);
}
};
} else {
// All other browsers
document.addEventListener('deviceready', bootstrap, false);
}
};
environment.cordova ? bootstrapCordova() : bootstrap();

(2)识别打字

由于cordova及其插件并不是Angular的一部分,因此我们需要将cordova打字员添加到我们的Angular项目中。虽然可以在不使用"窗口"函数的情况下以特定的方式编写代码,但我们发现打字解决了许多问题很重要。值得庆幸的是,有人为几乎每一个核心的cordova插件贡献了打字脚本。我们尝试在类型文件中安装这些打字员,但发现angular在编译时会抱怨有类型但没有库。为了记录在案,我们觉得以下方法不应该奏效,但它们是唯一可以复制的步骤,可以一次又一次地停止抱怨。

首先,我们在cordova的一个单独的文件空间中维护我们的angular应用程序,然后在运行ng build--prod时将应用程序写入www文件夹。这意味着我们的cordova应用程序和angular应用程序都有一个独特的包.json来管理npm依赖关系。为了访问angular中的cordova打字,我们需要将cordova和我们正在使用的所有插件添加到angular项目的package.json中。

"cordova": "latest",
"cordova-plugin-camera": "latest",
"cordova-plugin-inappbrowser": "latest"

第二,一旦你安装了这些依赖项,我们需要为我们的项目生成一个打字员文件。值得注意的是,npm中不赞成使用"typeings"包,而赞成使用"@types"。然而,我们无法弄清楚如何使用"@types"来实现我们想要的最终结果,所以目前我们正在使用"打字员">

npm install –g typings
typings search cordova
typings install dt~cordova --global --save

这将在您运行命令的地方生成一个打字员文件夹,因此我们建议您在angular项目的根目录中执行此操作。完成后,将types文件添加到tsconfig.app.json中,就可以开始编码了。

"types": [
"./typings/globals/cordova"
]

第三级文件级导入

在我们的研究中,我们看到了许多在代码级别实现打字员的方法,但只有一种在我们的项目中有效。根据逻辑,我们应该能够做一个简单的事情:

import {*} from 'typings/globals/cordova'

虽然我们的ide对这种ng构建很满意,但prod却不满意。相反,我们以以下方式引用打字员文件:

/// <reference path="../../../../typings/globals/cordova/index.d.ts" />

一旦我们做到了这一点,我们就能够从IDE中的完全智能感知中受益。

export class SplashComponent implements AfterViewInit, OnDestroy {
private browser: InAppBrowser;
private cordova: Cordova;
constructor(private route: Router) {
this.cordova = window['cordova']
this.browser = this.cordova.InAppBrowser;
}
ngAfterViewInit(){
this.currentBrowser = this.browser.open('https://www.google.com', '_blank', 'location=yes');
}
}

(3)建筑cordova

如果您在其他地方没有见过它,请知道,处理您在cordova中遇到的任何遗留问题的最佳方法是删除您的平台并重新添加它。

cordova platform rm android
cordova platform add android

使用此工作流程,我们能够消除我们在应用程序中遇到的几乎100%的运行时错误,并且应用程序的性能飙升。

希望如果你有这些问题,你会发现这篇文章很有帮助,因为它是我希望存在的所有东西。

注意事项:处理该代码的"窗口"元素的正确Angular方法是使用注入令牌。我没有把它包括在这里,因为这是一篇很长的帖子。我稍后可能会更新它以包含此内容。

这里的问题是Cordova CLI正在提取插件的2.x版本,但文档是针对3.x的。在2.x中,插件文件是backgroundGeolocation.js,而3.x将其更改为backgroundGeolocation.js,并添加了一个BackgroundGeolocal.d.ts文件来支持TypeScript实现。由于它是区分大小写的,所以它被错误地命名并失败了。将@latest添加到我的cordova plugin add <plugin-name>命令中,得到了正确的版本,它就开始工作了。

最新更新