cordova 简单趟坑文档二(android相关)

新增补充css

插件的配置信息地址: cordova.apache.org/docs/en/lat… html


### cordova 分享文档
#### 目的
能在android手机跑起来一个android App,显示内容是咱们预期的内容,而且使用自定义的插件。

#### cordova
为啥是cordova,不是weex,rn不是其余技术。
大前端环境下,直面客户的均可以说是前端的内容,不过是web端,桌面端,移动端仍是嵌入式其余系统等,均可以概括为前端。cordova在移动端和外部交互的历史上,可算是稳如老狗的老。其实没有其余的太多缘由,cordova简单。hybird这些原理都是一个,weex rn这些还更详细的作了其余的原生渲染处理,好比说web端渲染的dom树,用原生的节点渲染等,优化上是作到了,也复杂了。对于咱们如今目标,玩起来再说,其实客户端用前端写,也差很少是这个技术了。简单好用才是硬道理。

cordova 安装过程再也不详细描述,部分能够参照原有文档[http://confluence.51baiwang.com/pages/viewpage.action?pageId=15665622](http://confluence.51baiwang.com/pages/viewpage.action?pageId=15665622)

评论有安装遇到的一些问题。
    
在这里再次说说项目目录结构的内容。

```
// 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` 就能够容许应用内打开外部页面了。更多参考文档~[https://cordova.apache.org/docs/en/latest/config_ref/index.html](https://cordova.apache.org/docs/en/latest/config_ref/index.html)   
接着,页面是显示了,但是要怎么打开原生的功能呢?若是不能调用原生的功能,在真机上开发又有什么意义呢,还不如在浏览器开发。
抛开外部连接或者是内部文件,打开原生功能的流程是什么呢,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 这个项目。
![develop type](http://aaa-test.51baiwang.com/images/markdown/developType.jpg)

(android studio打开的项目是会以gradle构建的,若是项目结构内没有找到gradle文件,或者说直接打开的是cordova project,弹窗提示以后,让自动生成gradle结构的文件目录,studio就会去下载好多东西)

打开项目,如上图developType,点击左上角切换目录结构类型,使用比较多的是android 和 project,project的目录结构就是finder的目录结构。

使用android模式打开

manifest文件,android设置activity,主入口和受权等内容的地方
![manifest](http://aaa-test.51baiwang.com/images/markdown/manifest.jpg)

接着看到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脚本。
![gradle](http://aaa-test.51baiwang.com/images/markdown/gradle.jpg)
能够看到有好几个的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,而后写的一个插件。
插件的生成
[https://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html](https://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html)
相似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)

原文:https://blog.csdn.net/xiangwang666/article/details/51699721 

因而咱们在调用扫码以前set一下。哎,ok了。
接着就是最后一个步骤了。返回数据。喵了一眼自动生成的coolMethod,是使用一个传进来的callbackContent里面的方法。但是在异步仍是另外的函数,怎么办呢?
联想了一下js的window,我也给这个android的东西挂到一个两个函数均可以访问的地方去呗。
因而定义了一个`private CallbackContext callbackContext = null;`再喵一眼coolMethod是怎么返回数据的,测试一下。

完事儿啦


复制代码
相关文章
相关标签/搜索