缘起

  公司遗留了一个 Android 项目要负责一下,赶紧打算下载 Android Studio 搭建环境,回头一把啦代码大部分后缀.ts,这不是 typescript 格式?再看目录,整个一 web 项目类似,细看,原来是 Cordova 项目而且还用上了 Ionic。一言难尽… 然后,开始 Cordova 和 Ionic 之旅。

什么是 Cordova 和 Ionic

Cordova

  ordova提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头、麦克风等。
  Cordova还提供了一组统一的JavaScript类库,以及为这些类库所用的设备相关的原生后台代码。
  Cordova支持如下移动操作系统:iOS, Android,ubuntu phone os, Blackberry, Windows Phone, Palm WebOS, Bada 和 Symbian。
  Cordova是PhoneGap贡献给Apache后的开源项目,是从PhoneGap中抽出的核心代码,是驱动PhoneGap的核心引擎。你可以把它们的关系想象成类似于Webkit和Google Chrome的关系。

Ionic

  ionic是一个用来开发混合手机应用的,开源的,免费的代码库。可以优化html、css和js的性能,构建高效的应用程序,而且还可以用于构建Sass和AngularJS的优化。ionic会是一个可以信赖的框架。
  ionic是一个专注于用WEB开发技术,基于HTML5创建类似于手机平台原生应用的一个开发框架。绑定了AngularJS和Sass。这个框架的目的是从web的角度开发手机应用,基于PhoneGap(Cordova)的编译平台,可以实现编译成各个平台的应用程序。
  ionic的开发添加android和ios环境。
  ionic提供很多css组件和javascript Ui库。
  ionic可以支持定制android和ios的插件,也支持服务端REST的敏捷开发。

总的来说就是一套用 Javascript 脚本语言来开发 Native app的环境。

安装环境

基本依赖环境

  • node.js
    下载 安装 Node.JS,在 命令行中执行:

    1
    npm -v

    确定 node.js 安装路径添加到了环境变量 Path 中

  • JDK
    下载 安装JDK,在 命令行中执行:

    1
    Java -version

    确定 JDK 安装路径添加到了环境变量 Path 中并设置了 JDK 相关变量:比如:JAVA_HOME 等等。
    备注:
    java -version 命令行参数是一个横线加单词全拼,很变态的。

  • Android sdk
    下载 Android sdk 安装后,把 sdk根目录、sdk\platform-tools目录、sdk\tools目录设置到环境变量Path中。

    1
    adb version
  • Gradle
    下载 Gradle 安装后,把 Gradle 目录先bin目录设置到环境变量Path中:

    1
    gradle --help
  • ionic cordova
    安装 ionic cordova:

    1
    npm install -g ionic cordova

    验证:

    1
    2
    cordova  -v
    ionic -v

基本流程

安装一个ionic 项目

1
ionic start myApp tabs

运行myapp 项目

1
2
cd myapp
ionic serve --lab

添加android平台 必须在myapp下面

1
2
3
ionic platform add android
//ionic 新版本命令为
ionic cordova platform add android

生成android apk 必须在myapp下面

1
2
3
ionic build android
//
ionic cordova build android

在android模拟器或真机中模拟

1
2
ionic emulate android  
ionic run android

打包与签名

打包

1
ionic cordova build android --prod --release

zipalign

如果要私用 zipalign 做优化,应该在签名前优化,否则签名无效

1
zipalign -v 4 ./app/platforms/android/build/outputs/apk/android-release-unsigned.apk android-signed-release.apk

签名

1
2
3
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/app.keystore ./platforms/android/build/outputs/apk/android-release-unsigned.apk aliasname


一些奇奇怪怪的错误

  1. Gradle无法下载各种google的依赖包;比如: Could not resolve com.android.tools.build:gradle:3.0.0
  • 原因:
    老问题,无法访问googole的仓库
  • 解决方案:
    修改所有 build.gradle、xxx-build.gradle 文件中,maven中url 为 google的改为aliyun的镜像:
    1
    2
    3
    4
    5
    6
    7
    8
      repositories {        
    maven {
    url "http://maven.aliyun.com/nexus/content/groups/public"
    //url "https://maven.google.com"
    }
    mavenCentral()
    jcenter()
    }
  1. ERROR in node_modules/@ionic/storage/storage.d.ts:113:9 - error TS1086: An accessor cannot be declar
  • 原因:
    版本问题;
  • 解决方案:
    • 查看了node-module中是否 有storage.d.ts这个声明文件,确定是有的
    • 查看版本的号,猜测版本号可能和其他包冲突
    • 去相关官网上查看相关的版本号
    • 先卸载,npm uninstall @ionic/storage,再安装指定版本npm i @ionic/storage@2.2.0
  1. cordova-android-support-gradle-release 库 xxxx-cordova-android-support-gradle-release.gradle 文件中 def ANDROID_SUPPORT_VERSION = “24.+” 导致错误 Could not find any version that matches com.android.support:support-v4:24.+.
  • 原因:
    android-support-gradle-release 版本问题
  • 解决方案:
    设置ANDROID_SUPPORT_VERSION版本为支持的版本;失败后,会提示支持的版本
    1
    cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=27.0.0
    备注:
    网上所有 “在在SDK Manager中安装一下 Android Support Repository” 都是针对老版本的 as在3.0以下,新版本中 SDK Manager 已经没有 extra 部分。
  1. 找不到ic_launcher: setSmallIcon(R.mipmap.ic_launcher) 找不到
  • 原因:
    新版本的 Android Studio 中区分了mipmap 和 drawable 目录,mipmap仅仅用于应用启动图标,可以根据不同分辨率进行优化。其他的图标资源,还是要放到drawable文件夹中。本项目中启动图标在drawable目录,所以找不到图标。
  • 解决方案:
    手动修改为:setSmallIcon(R.mipmap.icon)
  1. Android SDK license 问题:You have not accepted the license agreements of the following SDK components
  • 原因:
    新安装或者其他原因 SDK 使用需要授权
  • 解决方案:
    Android-sdk的目录下的tools下的bin下,按住shift,鼠标右键选择:在此处打开命令窗口输入:
    1
    sdkmanager --licenses
    后面的按照提示不断选择 y然后回车
  1. 安装node-sass正确姿势
    windows下面安装node-sass,如下:
  • 可在项目目录下临时安装指定node-sass为镜像淘宝优先方案
1
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
  • 或者修改镜像
1
2
3
4
5
6
7
8
9
# npm命令
npm config get registry
# yarn命令
yarn config get registry
//修改为淘宝镜像
# npm命令
npm config set registry http://registry.npm.taobao.org/
# yarn命令
yarn config set registry http://registry.npm.taobao.org/

此时,正常情况再安装node-sass都可以成功,如果安装还报错,则进入下面第二步。安装编译windows平台编译环境

  • 安装windows平台编译环境(需要在管理员权限下安装)
1
2
npm install -g node-gyp
npm install --global --production windows-build-tools

以上三步,基本保证node-sass安装成功!

  1. 问题: Deviceready has not fired after 5 second
    现象为不能加载资源,造成白板显示,在浏览器 debug 工具中显示:Deviceready has not fired after 5 second;修改cordova.js引入位置,如下:
1
2
3
4
5
6
//......
<!-- cordova.js required for cordova apps (remove if not needed) -->
<script src="cordova.js"></script>
</body>

</html>

调试web代码

为了方便调试web代码,可以通过:

1
cordova serve android

来启动服务,用户可以通过http://localhost:8000/android/www/index.html来请求android平台下的页面,也就是相当于手机访问到的首页,如此一来方便调试了。这样以来,就可以使用浏览器的调试工具调试 web 代码了。

具体项目使用步骤

  1. 全局安装 cordova ionic
1
2
3
npm install -g cordova ionic
//本项目建议使用特定版本的 ionic 和 cordova安装
npm install -g ionic@4.10.2 cordova@8.1.2
  1. 进入到项目目录
    cd x:\dir\project
  2. 更新依赖项
1
2
3
4
5
npm install
//或者
npm update
//或者使用 淘宝镜像
npm install --registry=https://registry.npm.taobao.org
  1. 添加平台,以 Android为例
    ionic cordova platform add android

  2. 设置 ANDROID_SUPPORT_VERSION 为新版本,最低支持为26.0.0:
    cordova plugin add cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=27.0.0

  3. 手动修改: platforms\android\app\src\main\java\com\chenyu\GaoDeLocation\SerialLocation.java:236行
    setSmallIcon(R.mipmap.icon)

  4. 手动修改所有*build.gradle文件中:

1
2
3
4
5
/*maven {
url "https://maven.google.com"
}*/
//注释掉所有上面的仓库地址 然后添加:
google()
  1. 编译:
    ionic cordova build android --prod --release
  2. 如果显示build 详情:
    ionic cordova build android --prod --release --verbose
  3. 经过漫长的下载依赖、编译,最终生成安装包,在以下路径
    x:projectpathplatforms\android\app\build\outputs\apk\release\app-release-unsigned.apk
  1. 签名
    jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/app.keystore ./platforms/android/build/outputs/apk/android-release-unsigned.apk aliasname

常用 Cordova API

退出app功能

  • 如果 ionic v4 则没有退出 App 功能使用第三方:
1
2
npm i cordova-plugin- app-exit
cordova plugin add cordova-plugin- app-exit

然后可以使用:

1
navigator['app'].exitApp(); 
  • navigator.app.exitApp()
  • ionic.Platform.exitApp()
  • 示例
1
2
3
4
5
6
document.addEventListener("deviceready", function() {
console.log("deviceready");
document.addEventListener("backbutton", function() {
navigator.app.exitApp(); //退出app
}, false);
false);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function showConfirm() {
var confirmPopup = $ionicPopup.confirm({
title: '<strong>退出应用?</strong>',
template: '你确定要退出应用吗?',
okText: '退出',
cancelText: '取消'
})
confirmPopup.then(function (res) {
if (res) {
ionic.Platform.exitApp();
} else {
// Don't close
}
});
}
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
//ionic v3
import { AlertController } from 'ionic-angular';

export class MyPage {

constructor(public alertCtrl: AlertController) { }

showPrompt() {
const prompt = this.alertCtrl.create({
title: 'Login',
message: "Enter a name for this new album you're so keen on adding",
inputs: [
{
name: 'title',
placeholder: 'Title'
},
],
buttons: [
{
text: 'Cancel',
handler: data => {
console.log('Cancel clicked');
}
},
{
text: 'Save',
handler: data => {
console.log('Saved clicked');
}
}
]
});
prompt.present();
}
}

ionic doc v4 ion-alert

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import { Component } from '@angular/core';
import { AlertController } from '@ionic/angular';

@Component({
selector: 'alert-example',
templateUrl: 'alert-example.html',
styleUrls: ['./alert-example.css'],
})
export class AlertExample {

constructor(public alertController: AlertController) {}

async presentAlert() {
const alert = await this.alertController.create({
header: 'Alert',
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK']
});

await alert.present();
}

async presentAlertMultipleButtons() {
const alert = await this.alertController.create({
header: 'Alert',
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['Cancel', 'Open Modal', 'Delete']
});

await alert.present();
}

async presentAlertConfirm() {
const alert = await this.alertController.create({
header: 'Confirm!',
message: 'Message <strong>text</strong>!!!',
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
handler: (blah) => {
console.log('Confirm Cancel: blah');
}
}, {
text: 'Okay',
handler: () => {
console.log('Confirm Okay');
}
}
]
});

await alert.present();
}

async presentAlertPrompt() {
const alert = await this.alertController.create({
header: 'Prompt!',
inputs: [
{
name: 'name1',
type: 'text',
placeholder: 'Placeholder 1'
},
{
name: 'name2',
type: 'text',
id: 'name2-id',
value: 'hello',
placeholder: 'Placeholder 2'
},
{
name: 'name3',
value: 'http://ionicframework.com',
type: 'url',
placeholder: 'Favorite site ever'
},
// input date with min & max
{
name: 'name4',
type: 'date',
min: '2017-03-01',
max: '2018-01-12'
},
// input date without min nor max
{
name: 'name5',
type: 'date'
},
{
name: 'name6',
type: 'number',
min: -5,
max: 10
},
{
name: 'name7',
type: 'number'
}
],
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
handler: () => {
console.log('Confirm Cancel');
}
}, {
text: 'Ok',
handler: () => {
console.log('Confirm Ok');
}
}
]
});

await alert.present();
}

async presentAlertRadio() {
const alert = await this.alertController.create({
header: 'Radio',
inputs: [
{
name: 'radio1',
type: 'radio',
label: 'Radio 1',
value: 'value1',
checked: true
},
{
name: 'radio2',
type: 'radio',
label: 'Radio 2',
value: 'value2'
},
{
name: 'radio3',
type: 'radio',
label: 'Radio 3',
value: 'value3'
},
{
name: 'radio4',
type: 'radio',
label: 'Radio 4',
value: 'value4'
},
{
name: 'radio5',
type: 'radio',
label: 'Radio 5',
value: 'value5'
},
{
name: 'radio6',
type: 'radio',
label: 'Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 Radio 6 ',
value: 'value6'
}
],
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
handler: () => {
console.log('Confirm Cancel');
}
}, {
text: 'Ok',
handler: () => {
console.log('Confirm Ok');
}
}
]
});

await alert.present();
}

async presentAlertCheckbox() {
const alert = await this.alertController.create({
header: 'Checkbox',
inputs: [
{
name: 'checkbox1',
type: 'checkbox',
label: 'Checkbox 1',
value: 'value1',
checked: true
},

{
name: 'checkbox2',
type: 'checkbox',
label: 'Checkbox 2',
value: 'value2'
},

{
name: 'checkbox3',
type: 'checkbox',
label: 'Checkbox 3',
value: 'value3'
},

{
name: 'checkbox4',
type: 'checkbox',
label: 'Checkbox 4',
value: 'value4'
},

{
name: 'checkbox5',
type: 'checkbox',
label: 'Checkbox 5',
value: 'value5'
},

{
name: 'checkbox6',
type: 'checkbox',
label: 'Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6 Checkbox 6',
value: 'value6'
}
],
buttons: [
{
text: 'Cancel',
role: 'cancel',
cssClass: 'secondary',
handler: () => {
console.log('Confirm Cancel');
}
}, {
text: 'Ok',
handler: () => {
console.log('Confirm Ok');
}
}
]
});

await alert.present();
}

}