新增补充
插件的配置信息地址: cordova.apache.org/docs/en/lat…
cordova 分享文档
目的
能在android手机跑起来一个android App,显示内容是我们预期的内容,并且使用自定义的插件。
cordova
为啥是cordova,不是weex,rn不是其他技术。 大前端环境下,直面客户的都可以说是前端的内容,不过是web端,桌面端,移动端还是嵌入式其他系统等,都可以归纳为前端。cordova在移动端和外部交互的历史上,可算是稳如老狗的老。其实没有其他的太多原因,cordova简单。hybird这些原理都是一个,weex rn这些还更详细的做了其他的原生渲染处理,比如说web端渲染的dom树,用原生的节点渲染等,优化上是做到了,也复杂了。对于我们现在目标,玩起来再说。
cordova 安装过程不再详细描述,部分可以参照原有文档confluence.51baiwang.com/pages/viewp…
评论有安装遇到的一些问题。
在这里再次说说项目目录结构的内容。
// tree -L 1
├── config.xml
├── hooks
├── node_modules
├── package.json
├── platforms
├── plugins
├── res
└── www
主要的文件夹。
/www
如同大多数项目的结构一样,/www 目录下面的是网页端的代码。
// tree -L 2
├── css
│ └── index.css
├── img
│ └── logo.png
├── index.html
└── js
└── index.js
熟悉的前端代码,不再多说。
/plugins 接着再来看插件文件目录。插件目录属于不常手动修改的目录。
//▶ tree -L 1
.
├── android.json
├── cordova-plugin-whitelist
├── fetch.json
├── ios.json
└── org.lee.cordova.qrcodeanalysis
这块主要是下载的cordova plugin文件目录,和一些平台相关的json文件,引入某些插件或者其他。插件的引入删除操作建议直接用指令来操作,影响到的文件不少,很容易出问题 尽量直接只用cordova plugin add / remove 操作。除非某些插件,在直接使用指令引入会产生报错的时候,可以稍做处理。 这里涉及到一个平台引入,也就是生成平台代码的过程中。按照测试的流程,平台插件的代码,是从plugin里面获取的,也就是说,在我添加了plugin,还没有添加platform的情况下,修改了plugin里面的代码,再添加platform,所引入的plugin代码为修改过后的代码。所以当某一个插件添加,某个环境报错的时候,可以尝试先删除平台,修改代码,重新引入平台。
/platforms:各平台构建(或者说自动生成的项目代码)文件目录 比如说添加了如下ios和android两个平台,分别有xcode的ios项目文件,ios dir;和gradle构建的android项目文件,android dir。 使用对应的studio打开,即可对项目进行更多配置之外的操作。
PS:需要注意的是。platform文件夹,相当于vue-cli默认build的dist文件夹。cordova项目里的HTML代码修改了,没有再次构建,在platform项目里面是不包含的。同时,重新build会修改platform内容代码。
// tree -L 2
.
├── android
│ ├── CordovaLib
│ ├── android.json
│ ├── app
│ ├── build.gradle
│ ├── cordova
│ ├── org.lee.cordova.qrcodeanalysis
│ ├── platform_www
│ ├── project.properties
│ ├── settings.gradle
│ └── wrapper.gradle
└── ios
├── CordovaLib
├── HelloCordova
├── HelloCordova.xcodeproj
├── HelloCordova.xcworkspace
├── cordova
├── ios.json
├── platform_www
├── pods-debug.xcconfig
├── pods-release.xcconfig
└── www
在对应的项目目录下,我们都可以看到一个/www目录
/platforms/android/app/src/main/assets/www/platforms/ios/www
▶ tree -L 1
.
├── cordova-js-src
├── cordova.js
├── cordova_plugins.js
├── css
├── img
├── index.html
├── js
└── plugins
发现目录结构和外面的www有点儿相像,多了些文件,也就是cordova 自动生成的部分js,详情可以打开这些js文件看看,通过cordova.js和插件的js相关联,也是通过cordova.js以某种协议发送信息。详细内容可以自行寻找部分hybird原理的资料。hybird是打开一个webview(类似iframe),再通过这样打开一个外部的网页,原生部分可以通过拦截url scheme,简单说就是拦截请求,获取到对应的内容,进行了原生部分的操作,然后原生通过webview可以随意调用js的函数,这时候调用到对应的callback函数,完成数据的交互。 交互的这个过程,封装成Bridge文件,也就是这些cordova.js
config.xml
config.xml可以说是我们修改得稍微多一点的文件了。
比如说app的唯一值id
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
描述、作者等,还有平台定制化的内容,引入插件时候的插件添加等配置。
最不能忽略的一点也就是入口。
<content src="index.html" />
显而易见的,入口是指向的是www下的index。
既然入口是可以指定的,那么,入口可以指定为某一个已经部署项目的服务端地址吗?
答案是当然的。
比如说将www目录下部署到一个node服务上,把content的内容设置为192.168.xxx.xxx:8080,访问到这个服务的主页面。一切都是这么的美好,真机开发的时候,只需要安装一次android apk或ipa就可以在真机上看到开发的效果了。
然后你会发现,这些网站都是从浏览器打开的,一脸懵逼,生无可恋,每次开发验证难道又得一遍又一遍的打包了?针对性的排查这个问题,我们可以修改了allow-navigation Controls 就可以允许应用内打开外部页面了。更多参考文档~cordova.apache.org/docs/en/lat…
接着,页面是显示了,可是要怎么打开原生的功能呢?如果不能调用原生的功能,在真机上开发又有什么意义呢,还不如在浏览器开发。
抛开外部链接或者是内部文件,打开原生功能的流程是什么呢,cordova.js啊,对,那就得了,我们再在远端链接给html引入cordova.js,cordovajs依赖了哪个js呢?到这里,可以联想到上面说的平台内的www文件目录。直接将依赖到的内容,放置到远端部署的文件目录内即可。
注意:,在web文件内引入了cordova相关文件,也只是引入了一半的【桥】,还有另一半的还没有确认搭建,如果在原生模块,还没有引入相关的功能,web端通过桥通知到原生,但是没有这个功能的时候,是没法调用起来相关内容的。对应的前端报错就是undefined。
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>HelloCordova</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.
</description>
<author email="dev@cordova.apache.org" href="http://cordova.io">
Apache Cordova Team
</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
</platform>
<engine name="ios" spec="^4.5.5" />
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
<plugin name="org.lee.cordova.qrcodeanalysis" spec="/Users/lizhaowen/work/plugin/qrcodeanalysis" />
<engine name="android" spec="~7.0.0" />
</widget>
到这里cordova 更详细的说明到一个段落了。
下面是android部分的一些简单内容,针对的目标是没有java基础,没有android开发基础。请选择性跳过。
Android
android studio打开/platforms/android 这个项目。

(android studio打开的项目是会以gradle构建的,如果项目结构内没有找到gradle文件,或者说直接打开的是cordova project,弹窗提示之后,让自动生成gradle结构的文件目录,studio就会去下载好多东西)
打开项目,如上图developType,点击左上角切换目录结构类型,使用比较多的是android 和 project,project的目录结构就是finder的目录结构。
使用android模式打开
manifest文件,android设置activity,主入口和授权等内容的地方

接着看到java文件夹 按照包的路径打开,我们可以看到MainActivity,程序的主入口。既然路过我们就探头看一眼主入口都干了什么吧
public class MainActivity extends CordovaActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// enable Cordova apps to be started in the background
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
moveTaskToBack(true);
}
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
}
...
}
继承自cordovaActivity 写了这么久的vue,相信都有生命周期的这个概念了吧,每一个Activity不也就相当于一个vue么,有它自己的生命周期。这里只展示了一个onCreate,走进来就一点儿代码,主要的是loadUrl这个方法。很明显,我们可以猜测到是干什么用的了,就是用webview打开一个url,这个url也就是我们web程序的主入口了。除了在config.xml 修改url入口,我们也可以在这里修改为服务器地址的入口。
其他java文件也就是插件的java文件了。后续再讨论这块。 接下来是一个/assets 资源文件。emmm上面说过的一个文件夹。web资源和生成的cordova web桥文件。
最后是/res文件夹,是android的一些静态资源存放的文件夹,icon和img等,对我们影响较大的就是下面xml配置相关的文件夹,这个config同等于cordova的config,获取的配置信息就是在这里。同样的,修改它也会影响工程。
app同级下有个CordovaLib,可以说是一个android的module project,相关工程导包在这里不做延伸。
最后在看看Gradle script脚本。
可以看到有好几个的build.gradle,后面都有括号标出来后缀。简单说一下这个,每一个android project只有一个project的build脚本,这个工程引入了多个module,app也是作为一个module被工程引入,只不过是同时也作为了入口的module。cordovaLib作为了其他的依赖module。
这两个又是怎么样区分的呢。勇敢打点开属于app的这个build.gradle文件~
第一行代码 apply plugin: 'com.android.application' 这个插件作为android 的application。
然后我们再打开cordova的,呀,什么都没。再往下看看。像这样 apply plugin: 'com.android.library' 。当导入项目作为依赖的时候,我们就需要将项目的apply plugin设置为lib啦。
然后再settings.gradle 把module include进来。
// GENERATED FILE - DO NOT EDIT
include ":"
include ":CordovaLib"
include ":app"
说到这儿,也没说gradle脚本里面的内容,顺序不太对呀嘿嘿。其实里面就是一些json,也类似于xml的属于配置项,接在第一行代码之下的
buildscript {
repositories {
mavenCentral()
jcenter()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
资源库,maven仓库等一些构建项目的东西,dependencies,是项目需要引入的一些依赖,有远端下载的依赖也有本地jar aar包的依赖等。
说了一大堆没啥用的,还不如直接手机插到电脑上,配置好开发模式,按一下那个绿色的启动按钮,跑到手机上,遇到问题再查嘿嘿。
plugin
终于到了感觉挺复杂的,其实并没有这么复杂的这个插件介绍。 作为JS和native沟通的桥梁。plugin都分别对接了两方,沟通对接这个过程,永远的都是一个难题。但是插件也起到的作用也只是单纯的作为一个管道的作用,流通js和native的数据。
定位清楚了,我们在写插件的时候,就完成了插件所应有的功能就好了。
下面是使用android 扫码SDK,然后写的一个插件。 插件的生成 cordova.apache.org/docs/en/lat… 类似vue-cli,自动生成插件文件(需要注意的是不会生成package.json,插件引入的时候需要这个文件的数据,直接用npm init生成,直接回车一直下去就可以了)
▶ tree -L 3
.
├── package.json
├── plugin.xml
├── src
│ ├── android
│ │ ├── qrcodeanalysis.gradle
│ │ ├── qrcodeanalysis.java
│ │ └── zxinglibrary-debug.aar
│ └── ios
│ └── qrcodeanalysis.m
└── www
└── qrcodeanalysis.js
从/www/qrcodeanalysis导出scan函数
var exec = require('cordova/exec');
exports.coolMethod = function (arg0, success, error) {
exec(success, error, 'qrcodeanalysis', 'coolMethod', [arg0]);
};
exports.scan = function (arg0, success, error) {
exec(success, error, 'qrcodeanalysis', 'scan', [arg0]);
};
exec调用的是cordova/exec方法。 调用到qrcodeanalysis这个execute => private scan 下面直接给出代码,结合着看
/**
* This class echoes a string called from JavaScript.
*/
public class qrcodeanalysis extends CordovaPlugin {
private CallbackContext callbackContext = null;
private int REQUEST_CODE_SCAN = 199;
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("coolMethod")) {
String message = args.getString(0);
this.coolMethod(message, callbackContext);
return true;
}
if (action.equals("scan")) {
String message = args.getString(0);
this.scan(message, callbackContext);
return true;
}
return false;
}
private void coolMethod(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
private void scan(String message, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
cordova.setActivityResultCallback(this);
AndPermission.with(cordova.getActivity())
.permission(Permission.CAMERA, Permission.READ_EXTERNAL_STORAGE)
.onGranted(new Action() {
@Override
public void onAction(List<String> permissions) {
Intent intent = new Intent(cordova.getActivity(), CaptureActivity.class);
/*ZxingConfig是配置类
*可以设置是否显示底部布局,闪光灯,相册,
* 是否播放提示音 震动
* 设置扫描框颜色等
* 也可以不传这个参数
* */
ZxingConfig config = new ZxingConfig();
// config.setPlayBeep(false);//是否播放扫描声音 默认为true
// config.setShake(false);//是否震动 默认为true
// config.setDecodeBarCode(false);//是否扫描条形码 默认为true
// config.setReactColor(R.color.colorAccent);//设置扫描框四个角的颜色 默认为白色
// config.setFrameLineColor(R.color.colorAccent);//设置扫描框边框颜色 默认无色
// config.setScanLineColor(R.color.colorAccent);//设置扫描线的颜色 默认白色
config.setFullScreenScan(false);//是否全屏扫描 默认为true 设为false则只会在扫描框中扫描
intent.putExtra(Constant.INTENT_ZXING_CONFIG, config);
cordova.getActivity().startActivityForResult(intent, REQUEST_CODE_SCAN);
}
})
.onDenied(new Action() {
@Override
public void onAction(List<String> permissions) {
Uri packageURI = Uri.parse("package:" + cordova.getActivity().getPackageName());
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cordova.getActivity().startActivity(intent);
Toast.makeText(cordova.getActivity(), "没有权限无法扫描呦", Toast.LENGTH_LONG).show();
}
}).start();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 扫描二维码/条码回传
if (requestCode == REQUEST_CODE_SCAN) {
if (data != null) {
String content = data.getStringExtra(Constant.CODED_CONTENT);
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, content);
pluginResult.setKeepCallback(true);
callbackContext.sendPluginResult(pluginResult);
}
}
}
}
我们把调用扫码的过程给划分开。
SDK
- 唤起摄像头
- 获取视频流
- 解析视频流
- 得到扫码的数据
plugin
- 接收扫码数据
- 返回扫码数据
主要的步骤都在sdk上面了。
首先,我们默认导入了扫码SDK,可以直接使用。直接在scan函数体AndPermission.with(cordova.getActivity())....startActivityForResult(intent, REQUEST_CODE_SCAN);调用起来扫码。扫码过程是个类似js异步的过程,需要等到数据解析完成之后才执行callback。在看过SDK的demo之后,使用android自带的数据接收方式
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 扫描二维码/条码回传
if (requestCode == REQUEST_CODE_SCAN) {
LOG.i("111", "111")
...
}
}
}
我们看到一个单词@Override,重写的自带的函数。 再个就是,android调用Activity的时候,会用一个自定义的int数据来标记返回。REQUEST_CODE_SCAN。
接下来,我们先测试一下这个步骤是否能走通。一步一步来嘛~
跑起来,扫码~在控制台Logcat一看,log info没有输出任何111相关的。
挠头.gif
再来一次,确定没有输出。
再看看demo,没错啊。
懵逼.jpg
那就是数据没到onActivity,为啥呢?
搬运: 解决方法:
不是使用cordova.getActivity().startActivityForResult();这样调试跟踪后会发现被主Activity的OnActivityResult给拦截了。
解决方法使用 cordova.StartActivityForResult(cordovaplugin,Intent,int)
如下代码
cordova.setActivityResultCallback(this);
cordova.setActivityForResult(this,intent,RESULT);
原因是:plugin会通过CordovaInterface中的startActivityForResult(cordovaPlugin,intent,int)方法启动该Activity。
当 Activity 结束后,系统将调用回调函数 onActivityResult(int requestCode, int resultCode, Intent intent)
于是我们在调用扫码之前set一下。哎,ok了。
接着就是最后一个步骤了。返回数据。喵了一眼自动生成的coolMethod,是使用一个传进来的callbackContent里面的方法。可是在异步还是另外的函数,怎么办呢?
联想了一下js的window,我也给这个android的东西挂到一个两个函数都可以访问的地方去呗。
于是定义了一个private CallbackContext callbackContext = null;再喵一眼coolMethod是怎么返回数据的,测试一下。
完事儿啦 作者:斯文的烟鬼去shi吧链接:juejin.cn/post/684490…