程序人生 A log of my life

Cordova

和React Native类似,Cordova是跨平台应用开发的三驾马车之一。

准备工作

先安装node,安装之后设置一下npm,在用户目录下创建.npmrc,增加下面的一行,以加快npm的速度。

registry =http://registry.npm.taobao.org

再安装和创建cordova/ionic工程就会快些了

android

npm install -g cordova@8.1.2
cordova create hello
cd hello
cordova platform add android
cordova run android

之所以用8.1.2版本的cordova,是因为9.0有一个bugr, 某些版本的cordova,需要windows自己单独安装gradle,可以直接在path里设置一个指向已安装的gradle即可。

下面cordova run的过程中又可能卡死,因为又去下载东西了,参考android一文中修改gradle-wrapper.proerties,也可以尝试用Android Studio去打开这个工程了,打开的时候选择项目下的platform\android这个目录即可。如果不想直接运行,可以用cordova build android来编译出apk。

一旦编译成功,相关的组件版本版本锁定, 在新的机器上(或者clean之后)一定要用cordova prepare来恢复相同版本的组件,而不能再用cordova platform add,否则组件版本会被升级。

iOS

先安装组件:

brew install node@8
npm install -g cordova
npm install -g ios-deploy
npm install -g cordova-icon
npm install -g cordova-splash
cordova platform add ios

后面的@8是版本号,可根据情况替换。cordova-icon和cordova-splash是用来根据一个资源自动生成iOS需要的一堆icon,因为cordova不能自动做这个动作,cordova是按照iOS要求需要写多个icon和多个spalsh,这毕竟太麻烦了,所以可以在config.xml里不要写icon和spalsh,靠cordova-icon和cordova-splash来做自动生成:

cordova-icon --icon=../src/assets/icon.png

在真正编译之前,要解决签名和证书问题,启动xcode8,设置好自动前面的team,退出xcode就可以做编译了

cordova run ios --device

注意:编译的时候如果遇到问题xcode-select: error: tool ‘xcodebuild’ requires Xcode…,修复方法为:

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

iOS调试

iOS调试需要macOS,最简单的方法是打开电脑和手机端safari的调试开关,然后可以可以通过Safari调试,但我用下来感觉不是很好用,切换到chrome的方法如下:

brew update  #防止下面安装chrome时出现checksum错误
brew cask install --no-quarantine google-chrome #后面的no-quarantine解决brew的一个bug
brew unlink libimobiledevice ios-webkit-debug-proxy
brew uninstall --force libimobiledevice ios-webkit-debug-proxy
brew install --HEAD libimobiledevice
brew install --HEAD ios-webkit-debug-proxy
npm install remotedebug-ios-webkit-adapter -g
remotedebug_ios_webkit_adapter --port=9000 #手机上启动cordova debug应用后,执行这个命令

然后就可以启动chrome://inspect调试了,如果看不到被调试webview,尝试两个方法:

  • chrome的inpect页面中,需要config下network target,把localhost:9000加上
  • 先启动应用,在启动remotedebug_ios_webkit_adapter
  • 如果还不行,重启计算机

远程调试

config.xml里控制加载的入口文件index.html如果修改为远程地址,就可以远程加载webpack dev server调试了,好处自然是hot reload,但是需要注意的是缺省cordova的加载超时较短,为20秒,通常需要在config.xml里android部分下,增加超时的配置,否则很容易启动时出现The connection to the server was unsuccessful错误。

<preference name="loadUrlTimeoutValue" value="300000" />

cordova 版本

cordova自己有个版本,这个版本其实关系不大,相当于是cordova cli的版本,可以尽量用较高版本即可。而cordova在增加platform的时候,后面是可以加版本号的,比如add android时,可以用 add android@6.3.0,这个6.3.0实际上是cordova-android的版本号,这个就非常重要的,决定了得到android工程的一切细节,所以这个cordova-android版本最好锁定,不要轻易修改。

官方文档有一张表格,描述cordova-android版本和android版本对应关系。除了和android版本的对应,不同cordova版本见还有一些很大的差异:

  • cordova-android 6.3 是最后一个支持crosswalk的版本,后面的版本因为gradle的原因,crosswalk支持都有问题。
  • cordova-android 6.4 是第一个支持android studio 3(主要是gradle的变化不兼容)的版本, 并且项目结构和6.3有很大变化

使用Node模块

在Cordova工程中使用node模块并不简单,npm install之后,模块被安装到根下面的node_modules下, 需要手工复制到www下,否则无法引用,另一个问题是大部分nodejs module是服务器端模块,并不能直接用在cordova下使用,和在浏览器里无法使用node模块一个道理。

一个方法是通过wzrd.in转换出来,wzrd.in实际上就是通过Browserify来做到的,但无需手工安装Browserify和执行命令行了,并且wzrd.in提供了CDN服务,让你可以直接在html中引用而无需提前下载转换后的文件。

好在现代的前端工程都是使用打包工具从src目录直接打包得到dist,这时通常把src目录放在cordova/www外,得到的dist就是cordova/www,这样也解决了node模块问题,参考下quasar。

兼容性

考虑到不同手机的webview版本差异较大,需要找版本较低的手机来做验证,很多用到浏览器新特性的模块可能无法是旧设备上运行,比如:

  • animate.css 在老手机上很多动画显示不出来,替代方法可以考虑animejs
  • 有些语言特性,比如let of在老版本webview上没有提供,要小心。
  • TextDecoder在低版本浏览器(Android 5.1)不正常,需要使用npm模块

web限制

和原生app相比,使用webview作app有一些明显的限制(webview必须加的限制),比如:

  • CORS 尽管在cordova app运行时,以file协议加载,不会有cors限制,但是在做远程调试(http加载远端代码)还是受CORS限制,我通常的方法是在桌面浏览器中(使用禁止CORS的chrome)调试这部分代码,或者使用webpack提供的proxy也可以。
  • HTTP头的某些参数在webview下禁止修改,比如Referer,如果一定有这个需求,只能通过插件来调用移动端原生的http api,绕过webview来解决。

cordova 插件

可以为cordova项目安装插件

cordova plugin add https://github.com/don/cordova-plugin-hello.git

这样的话,plugins目录下就会多出通过git下载的插件了,并且插件信息会被加入到config.xml,注意github项目名称必须等于插件名称,这样下次从版本库checkout出来之后,需要用

cordova prepare

来重新下载这些插件, 如果名称不相等,这里会出错。还有几个常用的命令:

  • cordova plugin list 列举当前项目的插件
  • cordova plugin rm xxx 删除某个插件

定时

和web端一样,定时主要依靠setInterval和setTimeout两个API,但因为移动端app在熄屏之后会被系统挂起,因此永远不能依赖定时来计时。正确的方式是用Data.now()来计时,只用定时来刷新显示。

手机状态栏

Android上Material设计对状态栏的颜色修改有支持,并且也可以通过APP隐藏上面的状态栏,这些可以通过cordova-plugin-statusbar这个插件做到,比如:

if (cordova.platformId === 'android') {
  window.StatusBar.backgroundColorByHexString('#027be3')
}

注意,前景色是设置不了的,只能通过几个style接口修改为浅色或是深色,但在android下,需要6.0以上才支持style设置。

splash

默认创建的cordova应用没有splash屏幕(但其实资源文件还是在的),添加cordova-plugin-splashscreen之后就有了,如果安装了插件,还想不显示splash,可以加上设置

<preference name="SplashScreenDelay" value="0"/>

实际上cordova webview的加载速度很快,所以大部分的时间应该是花在应用自身后续的加载上,所以也可以考虑使用html5的loading指示来代替这个splash。

CORS

缺省的情况下,cordova下的webview也遵循cors规则,要解除这些限制,需要安装whitelist这个插件,并配置config.xml,文档里有详细解释,这些通常在add platform时自动配好了。

但是这只在cordova从file://加载时生效,如果debug版本让cordova从远端加载应用,cors还是生效了,whitelist插件也无能为力。同时也导致了dev版本无法绕过CORS,比较简单的一个做法是,桌面上启动一个禁止CORS的浏览器来做dev版本的开发和调试,启动的方法是通过命令行直接运行chrome,并增加以下参数:

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="c:/tools/chrome-dev"

注意,一定要增加user-data-dir参数,否则不生效。另外两个绕过CORS的方法:

  • 不使用webview提供的http服务,而是调用原生http接口,缺点是无法在桌面浏览器下调试了。
  • 使用webpack的proxy功能

权限

通常对权限的指定是通过插件的配置文件来实现,cordova对plugin.xml的处理方式如下:

  • 插件安装卸载时cordova会处理plugin.xml,写入里面需要的权限到平台配置文件
  • 平时build时,cordova不再处理plugin.xml,所以我们可以手动修改平台配置文件增删权限,并不会在下次build时丢失。
  • 暂时还没有办法在config.xml里增加权限,只能手工修改平台配置文件,或通过一个外部插件cordova-custom-config(可以让我们在config.xml里指定权限)。

一些插件

常用的几个插件:

  • cordova-plugin-statusbar 控制手机状态栏
  • cordova-plugin-spalshscreen 控制App的splash
  • cordova-plugin-device 获取设备信息
  • cordova-plugin-app-version 获取APP的版本号
  • cordova-plugin-insomnia 防止app锁屏(某些应用交互少,阅读多)
  • cordova-plugin-app-launcher 检查app是否安装,跳转app等

crosswalk

如果不想依赖os上的webview,可以用这个插件打包一份较新的webview,但是安装包会变大。

cordova plugin add cordova-plugin-crosswalk-webview
cordova plugin add cordova-android-support-gradle-release

后面这个插件是解决一个编译问题。

工程

cordova工程使用版本控制时,那些文件入库,哪些不入库,需要系统考虑,我的经验是:

  • platforms、node_modules、plugins目录都不入库,同时也不要手工修改这几个目录。

入库的问题在于,当clean项目的时候,重新prepare的时候可能和预留的文件冲突,导致编译不过,另外clean之后一定要用cordova prepare来保持原来的插件版本,如果某些插件需要修改之后才能工作,应该在github上单独为插件fork或新建一个项目,用这个新项目地址配置到cordova工程里,下面详述。

创建自己的插件

  • 从github上复制cordova-plugin-hello,作为基础
  • 使用plugman为新插件创建package.json

这样就创建了一个最基本的插件,并可以通过cordova plugin add 去添加了(后面使用本地文件的相对路径,比如..\plugin-demo),插件在add的时候,根据plugin.xml里的指示,cordova会做一些事情,将一些代码和配置文件丢到android目录下,然后就可以编译了。

但是,非常重要的一点是,插件add的时候所做的事情在以后build的时候是不会再做的,所以如果直接修改插件源码或plugin.xml,直接build是不会生效的,必须在android目录下修改插件生成的文件,这就给插件开发者带了一些不便,通常在android目录下调通插件所有功能后,再把相关文件和配置反向复制回插件源码中,这一步只能手工来做了。

插件的调试离不开Native调试器了,Android的话就要用android studio,如果不用IDE的话,只用log也可以,相对比较痛苦。

修改插件

  • 从插件的github库fork一个出来
  • 在项目中使用自己fork的插件,plugin add的时候后面用github库地址
  • 同时单独clone fork后的插件到本地一个目录
  • 调试时如果修改项目下插件相关文件,需要同步到本地插件目录,push到github

问题

  • cordova requirements 报错Android target: not installed, 并显示一些乱码 这应该是一个bug,执行chcp 65001切换到UTF-8之后就不报错了,不过通常不需要管这个错,没什么影响。