Ionic跨平台移动应用开发教程(二)

在本系列的上一篇文章中, 安装和简单配置了 ionic 的开发环境,并构造了一个 ionic 应用的骨架。本文让读者感受 ionic 访问硬件的能力。这也是 ionic 与纯 Angular 开发主要不同之处。本文的示例参照了官网文档中的实例,但更简单些,并且解决了源实例中有些代码和当前最新环境不兼容的问题。

本文通过构建一个简单的相册功能,来演示如何使用 ionic 和 cordova 来扩展应用使用相机和本地存储(sqlite)的能力。本文参考了 ionic 的官方文档中的一个案例,只是更简化,并且修正了官方案例不能在 ionic 4 中运行的问题。如果想参考官方文档,可以直接访问: Your First Ionic App: Angular

安装 cordova

1
yarn global add cordova

系统显示

1
2
3
4
5
6
7
8
9
10
yarn global v1.16.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "win32" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "cordova@9.0.0" with binaries:
- cordova
Done in 21.71s.

引用 cordova.js 文件

修改 src/index.html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<title>Ionic App</title>

<base href="/" />

<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />

<link rel="icon" type="image/png" href="assets/icon/favicon.png" />

<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

<script src="cordova.js"></script>
</head>

<body>
<app-root></app-root>
</body>

</html>

增加相机插件

1
yarn add @ionic-native/camera

系统显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
yarn add v1.16.0
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "win32" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 46 new dependencies.
info Direct dependencies
├─ @ionic-native/camera@5.10.0
├─ cordova-android@8.0.0
├─ cordova-plugin-device@2.0.3
├─ cordova-plugin-ionic-keyboard@2.1.3
├─ cordova-plugin-ionic-webview@4.1.1
├─ cordova-plugin-splashscreen@5.0.3
├─ cordova-plugin-statusbar@2.4.3
└─ cordova-plugin-whitelist@1.3.4
info All dependencies
├─ @ionic-native/camera@5.10.0
├─ android-versions@1.4.0
├─ ansi@0.3.1
├─ base64-js@1.3.0
├─ big-integer@1.6.44
├─ bplist-parser@0.1.1
├─ cordova-android@8.0.0
├─ cordova-common@3.2.0
├─ cordova-plugin-device@2.0.3
├─ cordova-plugin-ionic-keyboard@2.1.3
├─ cordova-plugin-ionic-webview@4.1.1
├─ cordova-plugin-splashscreen@5.0.3
├─ cordova-plugin-statusbar@2.4.3
├─ cordova-plugin-whitelist@1.3.4
├─ cross-spawn@6.0.5
├─ dedent@0.7.0
├─ deep-equal@1.0.1
├─ define-properties@1.1.3
├─ defined@1.0.0
├─ elementtree@0.1.7
├─ endent@1.3.0
├─ es-abstract@1.13.0
├─ es-to-primitive@1.2.0
├─ fast-json-parse@1.0.3
├─ for-each@0.3.3
├─ fs-extra@8.1.0
├─ has-symbols@1.0.0
├─ has@1.0.3
├─ is-date-object@1.0.1
├─ is-regex@1.0.4
├─ is-symbol@1.0.2
├─ minimist@1.2.0
├─ object-inspect@1.6.0
├─ objectorarray@1.0.3
├─ plist@3.0.1
├─ properties-parser@0.3.1
├─ resolve@1.11.1
├─ resumer@0.0.0
├─ shelljs@0.5.3
├─ string.prototype.codepointat@0.2.1
├─ string.prototype.trim@1.1.2
├─ tape@4.11.0
├─ through@2.3.8
├─ underscore@1.9.1
├─ xmlbuilder@9.0.7
└─ xmldom@0.1.27
Done in 176.87s.
1
ionic cordova plugin add cordova-plugin-camera

系统提示

1
2
3
4
5
6
7
> cordova.cmd plugin add cordova-plugin-camera

You have been opted out of telemetry. To change this, run: cordova telemetry on.
Installing "cordova-plugin-camera" for android
Subproject Path: CordovaLib
Subproject Path: app
Adding cordova-plugin-camera to package.json

在 config.xml 文件中增加以下内容:

1
2
3
4
<!-- Required for iOS 10: Camera permission prompt -->
<edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
<string>Used to take pictures</string>
</edit-config>

添加本地存储插件

1
ionic cordova plugin add cordova-sqlite-storage

系统显示

1
2
3
4
5
6
7
8
> cordova.cmd plugin add cordova-sqlite-storage

You have been opted out of telemetry. To change this, run: cordova telemetry on.
Installing "cordova-sqlite-storage" for android
installing external dependencies via npm
for package name: cordova-sqlite-storage
npm install of external dependencies ok
Adding cordova-sqlite-storage to package.json
1
yarn add @ionic/storage

系统显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
yarn add v1.16.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "win32" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 5 new dependencies.
info Direct dependencies
├─ @ionic/storage@2.2.0
└─ cordova-sqlite-storage@3.2.1
info All dependencies
├─ @ionic/storage@2.2.0
├─ cordova-sqlite-storage-dependencies@2.0.1
├─ cordova-sqlite-storage@3.2.1
├─ localforage-cordovasqlitedriver@1.7.0
└─ localforage@1.7.1
Done in 34.61s.

修改App模块,引入插件

打开 app.module.ts 文件,引入 Camera 和 IonicStorageModule 模块, 修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { Camera } from '@ionic-native/camera/ngx';
import { IonicStorageModule } from '@ionic/storage';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, IonicStorageModule.forRoot()],
providers: [
StatusBar,
SplashScreen,
Camera,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}

生成服务类 (Service)

1
ionic g service services/Photo

系统提示

1
2
3
4
> ng.cmd generate service services/Photo
CREATE src/app/services/photo.service.spec.ts (328 bytes)
CREATE src/app/services/photo.service.ts (134 bytes)
[OK] Generated service!

打开 services/photo.service.ts 文件,引入对 Camera 和 Storage。

1
2
import { Camera, CameraOptions } from '@ionic-native/camera/ngx';
import { Storage } from '@ionic/storage';

同时修改构造函数,注入 Camera, Storage

1
constructor(private camera: Camera, private storage: Storage) { }

在服务类中添加一个新的 Photo 类,用来存在图片数据。定义如下:

1
2
3
class Photo {
data: any;
}

然后在服务类中定义一个 Photo 类的数组,用来存储全部的图片。

1
public photos: Photo[] = [];

增加一个 takePicture 方法,在方法中,启动相机精选拍照并将照片放入 photos 数组中,最后存在本地存在中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
takePicture() {
const options: CameraOptions = {
quality: 100,
destinationType: this.camera.DestinationType.DATA_URL,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE
}

this.camera.getPicture(options).then((imageData) => {
this.photos.unshift({
data: 'data:image/jpeg;base64,' + imageData
});

this.storage.set('photos', this.photos);

}, (err) => {
// Handle error
console.log("Camera issue:" + err);
});
}

为了在程序启动时加载已经存在本地的照片,增加一个 loadSaved方法

1
2
3
4
5
loadSaved() {
this.storage.get('photos').then((photos) => {
this.photos = photos || [];
});
}

完整的服务类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { Injectable } from '@angular/core';
import { Camera, CameraOptions } from '@ionic-native/camera/ngx';
import { Storage } from '@ionic/storage';


class Photo {
data: any;
}

@Injectable({
providedIn: 'root'
})
export class PhotoService {

public photos: Photo[] = [];

constructor(private camera: Camera, private storage: Storage) { }

takePicture() {
const options: CameraOptions = {
quality: 100,
destinationType: this.camera.DestinationType.DATA_URL,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE
}

this.camera.getPicture(options).then((imageData) => {
this.photos.unshift({
data: 'data:image/jpeg;base64,' + imageData
});

this.storage.set('photos', this.photos);

}, (err) => {
// Handle error
console.log("Camera issue:" + err);
});
}

loadSaved() {
this.storage.get('photos').then((photos) => {
this.photos = photos || [];
});
}

}

修改界面组件

打开 src/app/home/home.page.ts 文件, 引入 PhotoService

1
import { PhotoService } from '../services/photo.service';

在构造函数中注入该服务

1
constructor(public photoService: PhotoService) {}

增加 ngOnInit 方法,以便在程序启动时执行一些初始化的动作

1
2
3
ngOnInit() {
this.photoService.loadSaved();
}

在这里,我们通过 ngOnInit 加载已经存在本地的图片。

完整的 home.page.ts 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component } from '@angular/core';

import { PhotoService } from '../services/photo.service';

@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {

constructor(public photoService: PhotoService) {}

ngOnInit() {
this.photoService.loadSaved();
}

}

打开 home.page.html 文件,将原有代码替换为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<ion-grid>
<ion-row>
<ion-col size="6" *ngFor="let photo of photoService.photos">
<img [src]="photo.data"/>
</ion-col>
</ion-row>
</ion-grid>

<ion-fab vertical="bottom" horizontal="center" slot="fixed">
<ion-fab-button (click)="photoService.takePicture()">
<ion-icon name="camera"></ion-icon>
</ion-fab-button>
</ion-fab>

加入Android支持

运行:

1
ionic cordova prepare android

运行和测试程序

完成以上修改后,运行以下命令运行程序:

1
ionic serve --devapp

系统提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ionic serve --devapp
> ng.cmd run app:ionic-cordova-serve --host=0.0.0.0 --port=8100 --cordova-mock --consolelogs --consolelogs-port=53703
[ng] WARNING: This is a simple server for use in testing or debugging Angular applications
[ng] locally. It hasn't been reviewed for security issues.
[ng] Binding this server to an open connection can result in compromising your application or
[ng] computer. Using a different host than the one passed to the "--host" flag might result in
[ng] websocket connection issues. You might need to use "--disableHostCheck" if that's the
[ng] case.
[INFO] Waiting for connectivity with ng...

[INFO] Development server running!

Local: http://localhost:8100
External: http://192.168.219.1:8100, http://10.0.75.1:8100, http://192.168.65.1:8100, http://192.168.40.1:8100,
http://192.168.3.53:8100
DevApp: todo-ionic-app@8100 on DESKTOP-DUER5RE

Use Ctrl+C to quit this process

[INFO] Browser window opened to http://localhost:8100!

并自动打开浏览器对应用进行访问。在浏览器中,可以看到开发的界面,但是当你按下拍照的按钮时,得到的是一个错误: “Native: tried calling Camera.getPicture, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator. Camera issue:cordova_not_available”, 说明在应用中用到的本地功能在浏览器中是不可用的。这时我们就需要用到 ionic 的测试工具了: Ionic DevApp。 “Ionic DevApp”是一个手机上运行的 App, 支持 iOS 和 Android 平台,可以到 App Store, Google Play 进行下载。

在手机上安装 Ionic DevApp之后,确保开发用的电脑和手机在同一个局域网中, 打开应用,稍等一会儿, Ionic DevApp 会自动发现在同一个局域网中运行的 App, 选择 App ,就会进入到该 App 的界面。在我们的例子中,这个时候再选择相机,就能打开系统的相机进行拍照,显示,存储了。

下一步

下一篇文章中,我们将练习在真机上运行程序。

本文标题:Ionic跨平台移动应用开发教程(二)

文章作者:梅老师

发布时间:2019年07月24日 - 11:07

最后更新:2020年09月16日 - 08:09

原始链接:https://www.mls-tech.info/app/ionic/ionic-app-getting-start-2/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。