阅读须知

本文目的是介绍如何开发cordova插件:voiceManager(android),阅读者需要了解Android原生开发和WeX5基本使用方法。Android开发工具为Android Studio和WeX5。

voiceManager插件功能:

切换设备外放/听筒模式

目录

1 生成本地App
1.1 概念
1.2 设计
1.3  生成页面
1.4 生成本地App
2、开发cordova插件
2.1 在本地App中开发插件的意义
2.2 导入Android工程到Android Studio
2.3 开发cordova插件
2.4 整合代码为标准的cordova插件

1、生成本地App

1.1 概念

本地App

官网对cordova的解释:Apache cordova是一个开放源代码的移动开发框架,它允许您使用标准的web技术如HTML5、CSS3和JavaScript进行跨平台开发,避免使用每个移动平台的原生语言开发,应用程序在有针对性的每个平台的包装内执行,并依靠符合标准的API绑定访问每个设备的感应器、资料和网络状态。

本文的“本地App”指的是基于Cordova框架标准的工程,可以包含一个或者多个平台的支持(IOS平台、Android平台等)。对应到目录,就是在下文即将创建的Native->voiceManager。

Android工程:“本地App”可以增加删除对各个平台的支持,在“本地App”里(platforms文件夹下)包含的各个平台的代码可以被看做集成了Cordova库的普通原生工程(Android工程、IOS工程),也可以将原生工程导入到相对应的平台开发工具中开发。也就是说,“本地App”之所以能支持多平台是因为生成了各个平台的工程,虽然这样描述并不精确,但是便于理解。对应到目录,就是在下文即将创建的Native->voiceManager->build->src->platforms->android

1.2 设计

在做一个新的工程、项目甚至是一个很小的程序之前,计划是重要的组成部分。开始的计划甚至比后期的开发更重要。本文要做的插件是控制切换设备外放/听筒模式。设想应该有一个页面,有按钮可以切换外放\听筒模式,页面需要音频的播放功能,播放音频的时候点击按钮切换听筒模式/外放模式。虽然实际开发插件并不需要页面,但是有页面能很方便的调试。

1.3  生成页面

启动WeX5的studio,在UI2/demo目录下创建voiceManager文件夹

createuifolder

鼠标右键选中voiceManager文件夹创建index.w,

create_w

选择创建标准页面,点击完成

uiindex

系统创建了w文件,并生成了.css样式文件和.js文件。页面右侧可以拖动控件在页面添加元素。

对WeX5编写页面不熟悉的可以参照官网WeX5基础教程,本文不做详细介绍,只展示完成的样式。

index.w的样式

indexw

index.w源码

<?xml version="1.0" encoding="utf-8"?>

<div xmlns="http://www.w3.org/1999/xhtml" component="$UI/system/components/justep/window/window" design="device:m;" xid="window" class="window">  

<div component="$UI/system/components/justep/model/model" xid="model" style="left:18px;top:83px;height:244px;"> 
  </div>

  

<div component="$UI/system/components/justep/panel/panel" class="x-panel x-full" xid="panel1"> 

<div class="x-panel-top" xid="top1"> 

<div component="$UI/system/components/justep/titleBar/titleBar" title="标题" class="x-titlebar">

<div class="x-titlebar-left"> 
            <a component="$UI/system/components/justep/button/button" label="" class="btn btn-link btn-only-icon" icon="icon-chevron-left" onClick="{operation:'window.close'}" xid="backBtn"> 
              <i class="icon-chevron-left"/>  
              <span></span> 
            </a> 
          </div>

  

<div class="x-titlebar-title">标题</div>

  

<div class="x-titlebar-right reverse"> 
          </div>

          
        </div>

 
      </div>

  

<div class="x-panel-content" xid="content1">
<div xid="div1" style="height:103px;"><audio src="music.mp3" controls="controls"></audio></div>
<div xid="div2" style="height:93px;"><a component="$UI/system/components/justep/button/button" class="btn btn-default" label="call" xid="callBtn" style="width:100%;" onClick="callBtnClick">
   <i xid="i1"></i>
   <span xid="span1">call</span></a></div>


<div xid="div3" style="height:78px;"><a component="$UI/system/components/justep/button/button" class="btn btn-default" label="normal" xid="normalBtn" style="width:100%;" onClick="normalBtnClick">
   <i xid="i2"></i>
   <span xid="span2">normal</span></a></div>

  <a component="$UI/system/components/justep/button/button" class="btn btn-default" label="exit" xid="exitBtn" style="width:845px;" onClick="exitClick">
   <i xid="i3"></i>
   <span xid="span3">exit</span></a></div>

  </div>

 
</div>

index.js没有在点击事件里添加调用,因为还没有可以引用的插件,现在写不存在的调用,会因为调用失败造成编译App失败。

index.js源码


define(function(require){
 var $ = require("jquery");
 var justep = require("$UI/system/lib/justep");

var Model = function(){
 this.callParent();
 };

function success(){
 alert("成功");
 }
 function fail(){
 alert("失败");
 }
 Model.prototype.callBtnClick = function(event){

};

Model.prototype.normalBtnClick = function(event){
 
 };

return Model;
});

需要注意的是index.w同级目录要放一个music.mp3文件,找一首mp3的音乐改个名字就可以了。

1.4 生成本地App

在Native文件夹点击鼠标右键,选择“创建本地APP”

vmapp01

 

 

 

 

 

 

选择模式3,这种模式是依赖于服务器的,页面元素放在服务器,可以很方便的修改页面,包括js,保存后就能在应用上看到效果而不需要重新安装APP。非常适合开发调试。

注意填写应用名字

vmapp02

填写

服务地址(下拉框点选)

web路径(下拉框点选)

首页(在选择需要发布的资源里选择刚才创建的voiceManager的index.w,要双击点选index.w,该文件地址会自动添加到首页。)

点击下一步

vmapp03

填写版本号、应用包名,点击下一步。

vmapp04

勾选Android证书,填写密码,勾选新生成证书文件,点击下一步。

vmapp05

无需修改点击下一步。

vmapp06

无需修改点击下一步(自动加载插件默认是勾选的)。这一步是可以勾选参数配置的。有时候需要在应用里配置一些可变的参数,

比如需要使用微信插件需要自行配置weixinappid等参数才能保证功能正常使用,如右图所示。配置参数还

需要plugin-ex.xml和plugin.xml配合使用(感兴趣的读者可以在WeX5的studio里找到Native->plugins->com.justep.cordova.plugin.weixin.v2,查看plugin-ex.xml和plugin.xml文件关于weixinappid、partner_id等5个参数的配置写法)。注意本次举例的工程没有需要配置的参数,为了避免出现歧义,不对参数配置详细描述。

vmapp07prams_setting

无需修改点击下一步。

vmapp08

勾选“完成后启动APP生成向导”,点击完成。

vmapp09

点击下一步

vmapp10

勾选安卓机器人图标,勾选“重新编译使用到的UI资源”,点击完成。

vmapp11

生成APP过程中可以点击详细信息查看过程信息,如果出现异常会显示报错信息,根据报错信息调整后重新编译即可。

vmapp12

正确生成了APP,扫描二维码可以下载APP。因为本文目的是编写插件,目前下载APP还没有意义。关闭该对话框即可

vmapp13

有时候编译的过程会出现错误(只是为了举例,正常是不会报错的,错误原因也是多种多样的),如下:

buildfailed

从提示看,是编译的时候有一个文件无法删除,按着提示找到该目录手动删除classes.jar,提示该文件被Java Platform SE binary占用,关闭 WeX5或者用任务管理器结束该进程再删除classes.jar文件就可以了。

在Native目录下找到voiceManager,这就是刚才编译失败的“本地App”。目录和结构已经生成,鼠标右键点击voiceManager选择“生成本地App包”,重新编译生成App。

rebuildapp

重新执行剩余的编译步骤即可,这里就不再重复。

成功编译后的App目录结构如下:

apptree

platforms下是各个平台的代码(自动生成Android和ios平台代码,如需添加其它平台需要自行添加),plugins文件夹下是引用的插件,build.properties记录了引用的插件名,平台等信息。一般来说,WeX5编译的App不需要修改配置文件,config.xml比较特殊,有时候因为网络环境的变化,config.xml记录的首页地址如下:

<content src=”http://192.168.1.154:8080/x5/UI2/demo/voiceManager/index.w”/>  需要更改为当前的网络地址并重新编译App,才能保证App和服务器的正确连接。

以上的流程执行后“本地App”就被成功创建了。

2、开发cordova插件

2.1  在“本地App”中开发插件的意义

理论上开发cordova插件并不需要创建“本地App”,只要按着cordova插件的目录结构写好代码就可以了。之所以先创建“本地App”,在“本地App”里开发cordova插件有三个原因:1 有助于理解cordova插件的原理 2 写完插件可以很方便的测试 3 因为理解原理,可以迅速准确的编写cordova插件配置文件。

2.2 导入Android工程到Android Studio

右键选中android文件夹,查看属性,复制该文件夹位置信息。

androidaddrs

打开Android Studio,选择打开已经存在的Android  Studio工程(Open an  existing Android studio project),将刚才复制的地址贴到输入框,点击确定导入工程

importproject

如下是Android工程的目录结构,CordovaLib、JustepGetContent是cordova和WeX5需要依赖的工程,无需修改。

 

projectree

2.3 开发cordova插件

cordova插件可以是纯js写,也可以js加android代码。本次举例的插件是js和android混合的形式。

开发一个js和Android混合的插件需要以下几步:

1 添加一个继承CordovaPlugin的java文件。

2 在assets\www\plugins添加插件的目录,并在该目录下添加一个js文件

3 配置cordova_plugins.js文件(配置js文件的相关信息)

4 配置res\xml\config.xml文件(配置java文件的相关信息)

原理:“本地App”使用插件,调用插件内的方法是通过js实现的。我们之前创建页面文件夹下有一个文件index.js,在这个文件里写js调用插件,接收指令的是插件的js文件,插件js文件调用继承cordovaplugin的java文件,java文件再执行操作或者调用工程、jar、aar或者其它,并将结果返回给插件内的js文件,再到页面的index.js。也就是说我们写插件时添加的js文件和java文件是入口,接收cordova的调用,并在内部实现对其它程序、工程的调用,使cordova获得插件所具备的功能。

下面我们添加java文件和js文件,配置cordova_plugins.js和config.xml。在java目录下添加包名为“com.justep.cordova.plugin.voicemanager”的VoiceManager.java文件。

execute(String action, JSONArray args, CallbackContext callbackContext)是VoiceManager的入口,调用VoiceManager插件,最终会进入的就是这个方法。
action是动作名,args是JSONArray 类型的参数,callbackContext是回调用到的。
代码逻辑是接收到动作为"setPlayMode"就解析args,并调用改变听筒/外放的方法setPlayMode(int voiceMode,CallbackContext callbackContext)。 
成功执行的话执行:callbackContext.success(),失败或者异常执行:callbackContext.error("")。双引号内填写内容。 
最后通过return super.execute(action, args, callbackContext)将成功或者失败的结果返回给调用方。
package com.justep.voicemanager.plugin.voicemanager;
import android.content.Context;
import android.media.AudioManager;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;

/**
 * Created by peace on 2016/3/7.
 */
public class VoiceManager extends CordovaPlugin {
   public String CALL="1";
    public String NORMAL="0";
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if ("setPlayMode".equals(action)) {
            try {
                String type = args.get(0).toString();
                if (type != null) {
                    int voiceMode = 0;
                    if (type.equals(CALL)) {
                        voiceMode = AudioManager.MODE_IN_CALL;
                    } else if (type.equals(NORMAL)) {
                        voiceMode = AudioManager.MODE_NORMAL;
                    }
                    setPlayMode(voiceMode, callbackContext);
                }else {
                    callbackContext.error("模式参数为空");
                }
            } catch (NumberFormatException ex) {
                callbackContext.error(ex.toString());
                return super.execute(action, args, callbackContext);
            }
        }
        return super.execute(action, args, callbackContext);
    }
    public void setPlayMode(int voiceMode,CallbackContext callbackContext){
        try {
            AudioManager am=(AudioManager) cordova.getActivity().getSystemService(Context.AUDIO_SERVICE);//提供访问控制音量和钤声模式的操作
            am.setMode(voiceMode);
        }catch (Exception e){
            e.printStackTrace();
            callbackContext.error(e.toString());
        }
        callbackContext.success();
    }
}
在assets\www\plugins添加com.justep.cordova.plugin.voicemanager\www的文件夹,并在www文件夹下创建voice.js文件。
setPlayMode是js的function名字,index.js调用插件voicemanager时要调用的就是setPlayMode。
onSuccess和onError是成功回调,失败回调。"voiceManager"是java类的代号(config.xml配置的feature name),"setPlayMode"是VoiceManager.java里execute方法的action,
[mode]是execute方法的args,也就是参数。
需要特别注意的是,java是区分大小写的。
voice.js
cordova.define("com.justep.cordova.plugin.voiceManager", function(require, exports, module) { var cordova = require('cordova'), exec = require('cordova/exec');
module.exports = {
		Mode: {
	        NORMAL:  0, // 正常外放模式
	        CALL: 1, // 听筒模式
	        
	    },
	    
		setPlayMode:function(mode,onSuccess,onError){
			exec(onSuccess, onError, "voiceManager", "setPlayMode", [mode]);
		}			
};

});
在cordova_plugins.js增加配置
module.exports = []内:
 {
 "file": "plugins/com.justep.cordova.plugin.voiceManager/www/voice.js",
 "id": "com.justep.cordova.plugin.voiceManager",
 "pluginId": "com.justep.cordova.plugin.voiceManager",
 "clobbers": [
 "navigator.voiceManager"
 ]
 }

注意如果添加到末尾,要把上一个”,”删除掉。}后面不要有”,”

module.exports.metadata ={}内:
"com.justep.cordova.plugin.voiceManager": "1.0.0"
注意最后一条不能有","而之前的条目,必须在每一行结尾写","
在config.xml增加配置(preference标签内)
 <feature name="voiceManager">
 <param name="android-package" value="com.justep.cordova.plugin.voicemanager.VoiceManager" />
 </feature>
VoiceManager.java、voice.js、config.xml位置如图,cordova_plugins.js在 assets\www下。

 

plugfiletree

添加了这些文件后,插件的代码就完成了,但是现在无法完成测试,因为index.js还没有添加对“voiceManager”插件的引用。现在修改一下index.js文件

增加了:

require(“cordova!com.justep.cordova.plugin.voiceManager”);   —>引用cordova插件voiceManager

var voiceManager= navigator.voiceManager;

voiceManager.setPlayMode(navigator.voiceManager.Mode.CALL,success,fail);  —>点击call按钮,调用setPlayMode,并传参navigator.voiceManager.Mode.CALL,将播放模式改为听筒模式

voiceManager.setPlayMode(navigator.voiceManager.Mode.NORMAL,success,fail);—>点击normal按钮,调用setPlayMode,并传参navigator.voiceManager.Mode.NORMAL,将播放模式改为外放模式


define(function(require){
 var $ = require("jquery");
 var justep = require("$UI/system/lib/justep");
 require("cordova!com.justep.cordova.plugin.voiceManager");
 var voiceManager= navigator.voiceManager;
 
 
 var Model = function(){
 this.callParent();
 };

 function success(){
 alert("成功");
 }
 function fail(){
 alert("失败");
 }
 Model.prototype.callBtnClick = function(event){
 voiceManager.setPlayMode(navigator.voiceManager.Mode.CALL,success,fail);
 };

 Model.prototype.normalBtnClick = function(event){
 voiceManager.setPlayMode(navigator.voiceManager.Mode.NORMAL,success,fail);
 };

 return Model;
});

启动WeX5的tomcat服务器

tomcatbutton

在Android studio运行App

asbutton

运行后可以在手机上顺序看到WeX5的欢迎页面和index.w页面

welcome voicemain

初次播放音频,音乐外放播放,点击call,音频从听筒播放,点击normal,音频外放播放。成功响应,页面会弹出alert。

successalert

至此,开发工作基本完成了,但事实上我们还没有做出一个标准的cordova插件。我们只是做了一个“本地App”,还需要把我们开发的东西剥离出来形成标准的cordova插件。

在此之前,我们再回顾一下开发流程和调用流程。

开发流程:写页面->以页面为基础生成“本地App”->将“本地App”platform下的android导入到Android Studio->写java文件,js文件,在config.xml和cordova_plugins.js写配置->补全

页面js调用->启动tomcat,启动应用测试

调用流程:

1、 index.js引用插件require(“cordova!com.justep.cordova.plugin.voiceManager”),调用插件方法voicemanager.setPlayMode(navigator.voiceManager.Mode.CALL,success,fail)。调用插件

实际的写法是navigator.voiceManager.setPlayMode,不要漏看var voiceManager= navigator.voiceManager;

navigator.voiceManager是在cordova_plugins.js配置的插件的clobbers属性,require调用的是pluginId属性。

2、voice.js接收index.js的请求,voice.js的头要声明cordova.define(“com.justep.cordova.plugin.voiceManager” ……

com.justep.cordova.plugin.voiceManager是cordova_plugins.js配置的插件id属性。

index.js调用的setPlayMode就是voice.js的名字为setPlayMode的function。

voice.js通过exec(onSuccess, onError, “voiceManager”, “setPlayMode”, [mode]) 能调用VoiceManager,因为VoiceManager在config.xml配置了VoiceManager的位置和代号,代号就是voiceManager。

cordova_plugins.js配置voice.js,config.xml配置VoiceManager.java,以及全流程的调用关系必须记清楚,否则开发遇到问题会多花费几倍的时间。

2.4 整合代码为标准的cordova插件

开发完插件后,了解到写一个cordova插件有四个关键的文件,java文件,js文件,config.xml和voice.js,config.xml。整合代码为标准cordova插件也是操作这几个文件。

下图为cordova插件的目录结构,VoiceManager和voice.js都是之前写好的文件,建好这样的结构放到对应位置即可。

plugintree

需要注意的是:

voice.js的头并没有之前cordova.define(“com.justep.cordova.plugin.voiceManager”

完整的文件是这样的:

var cordova = require('cordova'), exec = require('cordova/exec');
module.exports = {
		Mode: {
	        NORMAL:  0, // 正常外放模式
	        CALL: 1, // 听筒模式
	        
	    },
	    
		setPlayMode:function(mode,onSuccess,onError){
			exec(onSuccess, onError, "voiceManager", "setPlayMode", [mode]);
		}			
};

plugin-ex.xml和plugin.js.xdoc是WeX5特有的文件,作用是写插件的名字,描述和类描述、方法描述。直接贴出写法,不难理解。

plugin-ex.xml

<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="com.justep.cordova.plugin.voiceManager">
	<name>音频控制插件</name>
	<description>通过参数调节手机播放音频</description>
		<!-- common -->
	<platform name="common">
	</platform>
</plugin>

plugin.js.xdoc


/**
 @name com.justep.cordova.plugin.voiceManager
 @class 
 @model Native
 @category EventClass
 @description 选择设备音频的播放模式,可选听筒模式和外放模式
 @declareVar navigator.voiceManager
*/
/**
 @name com.justep.cordova.plugin.voiceManager.setPlayMode
 @function
 @param {Str} mode 参数
 @param {Function} onSuccess 执行成功回调函数
 @param {Function} onError 执行失败回调函数
 @returns {void}
 @description 当设置mode(字符串)为“CALL”时,则为听筒模式,如果为“NORMAL”时,则为外放模式
*/

plugin.xml是cordova的配置文件,贴出源码并解释重要配置的含义,需要注意的是,本次工程非常简单,并没有用到资源文件(图片、布局文件、Activity等),如果需要那些资源,也要在plug.xml里配置。plugin.xml的意义就是把插件所需要的资源按着规定的格式写好,工程调用插件的时候会按着这些配置到指定位置寻找资源并整合到工程对应的位置里。

<?xml version="1.0" encoding="UTF-8" ?>
<plugin xmlns="http://phonegap.com/ns/plugins/1.0"
    xmlns:android="http://schemas.android.com/apk/res/android"
    id="com.justep.cordova.plugin.voiceManager"
    version="1.0.0"><!--id就是cordova_plugins.js里配置的插件的pluginId,version是插件版本,根据自己需要写-->
  <engines>
    <engine name="cordova" version=">=3.3.0" /><!--指定cordova编译的版本-->
  </engines>

  <name>voiceMode</name>
  <description>Switch audio playback mode</description>

  <js-module src="www/voice.js" name="voiceManager"><!--voice.js的目录位置,navigator.voiceManager是被调用时候的名字-->
    <clobbers target="navigator.voiceManager" />
  </js-module>

  <platform name="android"><!--android配置-->
	<config-file target="res/xml/config.xml" parent="/*"><!--对应config.xml里的配置写在这里-->
	<feature name="voiceManager">
		<param name="android-package" value="com.justep.cordova.plugin.voicemanager.VoiceManager" />
	</feature>
	</config-file>
	<config-file target="AndroidManifest.xml" parent="/manifest">
		<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><!--改变声音模式所需要的权限-->
	</config-file>	
	<source-file src="src/android/VoiceManager.java"
			target-dir="src/com/justep/cordova/plugin/voicemanager" /><!--VoiceManager.java的位置以及拷贝到工程后的地址-->
  </platform>

  <platform name="ios">
    <source-file src="src/ios/CDVVoiceManager.m" />
    <header-file src="src/ios/CDVVoiceManager.h" />
    

    <config-file target="config.xml" parent="/widget">
      <feature name="voiceManager">
        <param name="ios-package" value="CDVVoiceManager"/>
      </feature>
    </config-file>
  </platform>
</plugin>


需要注意的是,开发多个平台的时候,暴露给js调用的命名要统一,才能保证一次开发,多平台调用。

在WeX5的Native–>plugins创建好插件目录,并拷入对应的文件,写好配置文件,就可以用平台生成“本地App”了。生成“本地App”的方法在前面已经详细描述过。参考1.4

贴了很多图,写了很多文字介绍了一个很简单的cordova插件开发方法,就是希望没有做过cordova插件的同学能在看完这篇文章后对cordova插件开发有比较清晰的理解,也能更快速有效的使用WeX5进行cordova开发。