Ionic2Cordova蓝牙插件封装

首先有Ionic2以及Cordova环境
如果没有在命令行执行以下命令

    npm install -g ionic cordova  //全局安装ionic 和cordova指令
    ionic start myApp tabs --v1  创建ionic2项目之前文章讲到了 “--v1&&--v2代表ionic不同版本”
    npm install -g plugman  //全局安装cordova插件命令 
    cordova platform add android //添加android平台 默认创建的项目只包含ios平台 其他的平台需要使用命令添加

现在基本基本开发环境已经就绪如果需要具体安装环境请跳转到Ionic2探索总结

首先我们查看一下plugman的帮助命令 直接输入plugman回车就能看到帮助,从帮助中找到下面这一句话

    plugman create --name <pluginName> --plugin_id <pluginID> --plugin_version <version> [--path <directory>] [--variable NAME=VALUE]

这句话就是创建插件的命令,下面我给出一个创建的事例代码 最好是到刚才创建好的项目跟目录执行

    plugman create --name bluetooths --plugin_id bluetooths --plugin_version 0.0.1 
    /*
    [--path <directory>]这个是可选的,如果你想创建到别的目录下的话可以通过这个可选的指定路径,如果仅仅想创建到当前路径下的话 可以省略这个可选的指令  
    */

来看一下插件创建完毕后的目录以及文件

    .
    ├── plugin.xml
    ├── src       
    └── www
        └── bluetooths.js

这是当前的目录,然而感觉缺点什么?缺的是各平台的代码。我们再回头看看plugman这个命令的帮助 有这样的命令

    Add a Platform to a Plugin
    --------------------------

        $ plugman platform add --platform_name <platform>

    Parameters:

    - <platform>: One of android, ios

    Remove a Platform from a Plugin
    -------------------------------

        $ plugman platform remove --platform_name <platform>

    Parameters:

    - <platform>: One of android, ios

看到这应该就知道了 我们添加平台

    cd bluetooths
    plugman platform add --platform_name android
    plugman platform add --platform_name ios

我们查看目录

    .
    ├── plugin.xml
    ├── src
    │   ├── android
    │   │   └── bluetooths.java
    │   └── ios
    │       └── bluetooths.m
    └── www
        └── bluetooths.js

    4 directories, 4 files

分析文件

plugin.xml

    <?xml version='1.0' encoding='utf-8'?>
    <plugin id="bluetooths" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0"   @1 xmlns:android="http://schemas.android.com/apk/res/android">
        <name>bluetooths</name>           @2
        <js-module name="bluetooths" src="www/bluetooths.js">   @3
            <clobbers target="cordova.plugins.bluetooths" />    @4
        </js-module>                                            @5
        <platform name="android">                               @6
            <config-file parent="/*" target="res/xml/config.xml">
                <feature name="bluetooths">
                    <param name="android-package" value="bluetooths.bluetooths" />
                </feature>
            </config-file>
            <config-file parent="/*" target="AndroidManifest.xml" />
            <source-file src="src/android/bluetooths.java" target-dir="src/bluetooths/bluetooths" />
        </platform>
        <platform name="ios">                                 @7
            <config-file parent="/*" target="config.xml">
                <feature name="bluetooths">
                    <param name="ios-package" value="bluetooths" />
                </feature>
            </config-file>
            <source-file src="src/ios/bluetooths.m" />
        </platform>
    </plugin>

1, 这个行指定了这个插件的id 版本

2, 插件名字

3, 4,5, 这个是插件的js部分 src说明js插件的文件的位置 target代表在怎么在全局中引用这个插件如果在es5中可以直接使用cordova.plugins.bluetooths这个对象上的各个方法,如果在es6以及typescript中使用的时候得先在代码的最上边加入这个

    declare var cordova:any
    ...
    cordova.plugins.bluetooths.function...

6, android平台的配置

7, ios平台的配置

bluetooths.js

    var exec = require('cordova/exec');  //引入cordova内部已经实现与原生交互的接口

    exports.coolMethod = function(arg0, success, error) {
        exec(success, error, "bluetooths", "coolMethod", [arg0]);
    };

上面三行代码实现了 导出一个名为coolMethod的方法 这个方法有三个参数 arg0为我们调用这个方法的时候参数,以及回调方法。

这个方法的内部是调用cordova插件的接口前两个是回调的方法,第三个是指定会响应到那个插件,第四个表面看是调用指定的方法,其实cordova插件的工作只是把coolMethod这个值传了过去(这个在android代码的时候说明) 以及后面的参数。

我们现在来看android的代码

    public class bluetooths extends CordovaPlugin {

        @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;
            }
            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.");
            }
        }
    }

这个插件类继承CordovaPlugin类,重写execute这个方法
action:触发的事件名 String类型
args:之前用户调用插件穿的值 JSONArray类型
callbackContext:回调类 CallbackContext类型

为什么说只是传了个方法的值过来,先看action是一个字符串类型的,在下面我们通过action.equals("coolMethod")来判断是否相等 这个类似于 ==。如果相等就调用this.coolMethod(message, callbackContext);这个内部方法可以随意更改。

当我们完成这插件后,我们就需要把这个插件应用到我们的项目中(ios因为没接触过暂时不讲)

    cd ../
    cordova plugin add ./bluetooths/

添加完成后点开plugins目录看到有bluetoots这个文件夹说明插件添加成功,调用在前面已经说了,下面写一下具体的用法

    declare var cordova:any
    ...
    cordova.plugins.bluetooths.coolMethod("message",(res)=>{
        alert(res)
    },(err)=>{
        alert(err)
    })

虽然cordvoa提供的插件库比较丰富,但是我们的业务需要监听蓝牙被用户手动在设置里更改后的信息,因为cordova提供的插件并没有这样的监听,所以踩这个坑了。

蓝牙监听插件js代码

js这边的代码js这边的代码非常简单

    var arg0="message"
    exports.registerReceiver = function(success,error){   //注册监听的js方法
        exec(success,error,"bluetooths","registerReceiver",[arg0]);
    }
    exports.unregisterReceiver = function(success,error){
        exec(success,error,"bluetooths","unregisterReceiver",[arg0]);  //取消监听的js方法
    }

蓝牙监听插件android代码

添加权限

在android代码里面基本都是纯android api里的方法,类。所以熟悉android的很容易写出下面的代码,当然我是个纯web前端开发出生的,android代码写的不好请见谅。

因为是要操作蓝牙所以需要取得权限在AndroidManifest.xml文件中的manifest标签里添加下面的代码

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

分发执行函数

在js代码中我们传递的是五个参数 但是经过CordovaPlugin这个类的接受处理后我们在adnroid看到的只有三个参数。
它把js这端的成功和失败回调通过CallbackContext处理,使得我们可以通过类型为CallbackContext传进来的参数,调用js端的方法,同时传参给js方法。第三个参数也是就“bluetooths”是给CordovaPlugin的标志,调用的是哪一个插件,最后两个参数分别为调用的方法名以及参数。对应到execute方法中的参数为action,args。

先匹配方法名调用不同的方法或者做不同的事代码如下

    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
            if(action.equals("registerReceiver")){  //如果action==“registerReceiver”注册
                String message = args.getString(0);
                this.registerReceiver(callbackContext);   //自定义方法后面讲
                return true;
            }else if(action.equals("unregisterReceiver")){ 如果action==“unregisterReceiver” 取消
                this.unregisterReceiver(callbackContext);  //自定义方法后面讲
            }
            return true;
        }

device对象转化为JSONObject

这段代码是从cordova-plugin-bluetooth-serial这个插件的328-338行代码拷贝过来的

    private JSONObject deviceToJSON(BluetoothDevice device) throws JSONException {
        JSONObject json = new JSONObject();
        json.put("name", device.getName());
        json.put("address", device.getAddress());
        json.put("id", device.getAddress());
        if (device.getBluetoothClass() != null) {
            json.put("class", device.getBluetoothClass().getDeviceClass());
        }
        return json;
    }

蓝牙显示通信构建方法(IntentFilter)

    private IntentFilter makeFilter() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); //添加连接action
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//添加断开action
        return filter;
    }

注册和注销

先在bluetooths对象中创建两个私有对象

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    BroadcastReceiver mReceiver =null;

创建私方法 registerReceiver

    private void registerReceiver(final CallbackContext callbackContext) throws JSONException {
         final JSONArray unpairedDevices = new JSONArray(); //new  JSONArray对象
         mReceiver = new BroadcastReceiver(){
             public void onReceive(Context context, Intent intent) {
                 ...
             }
         }        //new广播对象
         Activity activity = cordova.getActivity();
         activity.registerReceiver(mReceiver, makeFilter());
    }

在bluetooths对象中我们创建了一个蓝牙对象和一个广播对象,
在registerReceiver方法创建类型为JSONArray的对象unpairedDevices是为了等会储存device转化后的JSONArray类型的对象。
重点来了!!! 重点就在我们的mReceiver这个对象上,这是一个广播对象,这个对象需要实现onReceive方法,这个方法会在广播被接收到的时候调用。那么什么时候广播会被接受呢?
这个mReceiver接收到广播和 activity.registerReceiver(mReceiver, makeFilter()); 这一句代码有关。
这句代码是注册这个监听到指定广播,那些广播被指定了呢?看上面的 蓝牙显示通信构建方法(IntentFilter)

        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); //添加连接action
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);//添加断开action

就是这,添加了两个事件,这两个事件触发后都会被通过这个显示通信的注册的监听捕获到。

处理监听结果

    if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_CONNECTED)){  
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//获取设备对象
        try {
                JSONObject o = deviceToJSON(device,"CONNECTED");  //生成json格式的device信息
                unpairedDevices.put(o);
                callbackContext.success(o);
            } catch (JSONException e) {}
        }else if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)){
            ...
    } 

这边只展示了监听连接后的处理结果,断开后的处理方式基本一样,不过这有一点要说:我们的监听又不是监听一次,我们需要一直监听下去,所以我找到pltform/android/CordovaLib/src/org/apache/cordova/CallbackContext.java这个Cordova回调的源文件结合cordova-plugin-bluetooth-serial这个插件的BluetoothSerial.java文件中discoverUnpairedDevices方法里面不断返回查找到的设备的信息
o
首先看一下PluginResult这个类简要属性

    public class PluginResult {
        private final int status;  //当前结果的的状态
        private final int messageType;  信息类型
        private boolean keepCallback = false;    是否继续发送
        private String encodedMessage; 
       ...
       public PluginResult(Status status, JSONObject message) { //构造函数
            this.status = status.ordinal();
            this.messageType = MESSAGE_TYPE_JSON;
            encodedMessage = message.toString();  //JSONObject转化为Stirng储存
        }
        public void setKeepCallback(boolean b) {
            this.keepCallback = b;  //更改是否保持回调
        }
    }

再看一下 CallbackCintext.java的一个方法

    public void sendPluginResult(PluginResult pluginResult) { //传入一个PluginResult类的实例对象
        synchronized (this) {
            if (finished) {  //先判断是不是已经结束了
                LOG.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
                return;
            } else {
                finished = !pluginResult.getKeepCallback();
                //如果没有结束取出pluginResult.keepCallback作为下一轮的判断
            }
        }
        webView.sendPluginResult(pluginResult, callbackId); //向js发送消息
    }

通过上面一些分析android如何保持持续向js发送回调已经明了。

先判断回调是否还存在,如果存在说明需要回调 。new PluginResult类的实例,设置下一回合还需要发送信息,然后发送消息到js的回调,代码如下。

    if (callbackContext != null) {
        PluginResult res = new PluginResult(PluginResult.Status.OK, o);//将信息写入 同时设置后续还有返回信息
        res.setKeepCallback(true);
        callbackContext.sendPluginResult(res); 
    }

基本已经完成了,我相信如果有同学完整的学习完了这一篇,基本cordova插件的封装没有问题了,下面为完整android代码

    package bluetooths;

    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.CallbackContext;
    import android.app.Activity;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothDevice;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.widget.Toast;
    import org.apache.cordova.PluginResult;
    import org.json.JSONObject;

    /**
    * This class echoes a string called from JavaScript.
    */
    public class bluetooths extends CordovaPlugin  {
        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        BroadcastReceiver mReceiver =null;
        @Override
        public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
            if(action.equals("registerReceiver")){
                String message = args.getString(0);
                this.registerReceiver(callbackContext);
                return true;
            }else if(action.equals("unregisterReceiver")){
                this.unregisterReceiver(callbackContext);
            }
            return true;
        }
        private void registerReceiver(final CallbackContext callbackContext) throws JSONException {
            final JSONArray unpairedDevices = new JSONArray(); //new  JSONArray对象
            mReceiver = new BroadcastReceiver() {           //new广播对象
            @Override
            public void onReceive(Context context, Intent intent) {
            // Toast.makeText(context,"BroadcastReceiver",Toast.LENGTH_SHORT).show();
                if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_CONNECTED)){  
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//获取设备对象
                    try {
                            JSONObject o = deviceToJSON(device,"CONNECTED");  //生成json格式的device信息
                            unpairedDevices.put(o);
                            if (callbackContext != null) {
                                PluginResult res = new PluginResult(PluginResult.Status.OK, o);//将信息写入 同时设置后续还有返回信息
                                res.setKeepCallback(true);
                                callbackContext.sendPluginResult(res); 
                            }
                        } catch (JSONException e) {}
                // Toast.makeText(context,"接受到已连接,消息为:"+device.getName()+"address: "+device.getAddress(),Toast.LENGTH_LONG).show();
                }else if(intent.getAction().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)){  
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//获取设备对象
                    try {
                            JSONObject o = deviceToJSON(device,"DISCONNECTED");  //生成json格式的device信息
                            unpairedDevices.put(o);
                            if (callbackContext != null) {
                                PluginResult res = new PluginResult(PluginResult.Status.OK, o);//将信息写入 同时设置后续还有返回信息
                                res.setKeepCallback(true);
                                callbackContext.sendPluginResult(res); 
                            }
                        } catch (JSONException e) {}
                //    Toast.makeText(context,"接受到断开连接,消息为:"+device.getName()+"address: "+device.getAddress(),Toast.LENGTH_LONG).show();
                    }
                }
            };
            Activity activity = cordova.getActivity();
            activity.registerReceiver(mReceiver, makeFilter());
        }
        public void unregisterReceiver(final CallbackContext callbackContext){
            Activity activity = cordova.getActivity();
            activity.unregisterReceiver(mReceiver);
        }
        /*
        @deviceToJSON 将收到的设备对象转化为JSONObject对象方便与js交互数据
        @device 设备对象,当监听到设备变化后接受到的设备对象
        @connectType如果是连接发出的消息值为 CONNECTED 如果是断开连接发出的消息为 DISCONNECTED
        */
        private final JSONObject deviceToJSON(BluetoothDevice device,String connectType) throws JSONException {
            JSONObject json = new JSONObject();    //创建JSONObject对象
            json.put("name", device.getName());     //设备名字
            json.put("address", device.getAddress());   //设备地址
            json.put("id", device.getAddress());   //设备唯一编号使用地址表示
            json.put("connectType",connectType);
            if (device.getBluetoothClass() != null) {
                json.put("class", device.getBluetoothClass().getDeviceClass());  //设备类型 主要分别设备是哪一种设备
            }
            return json;
        }
        private IntentFilter makeFilter() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
            return filter;
        }

    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,408评论 4 371
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,690评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,036评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,726评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,123评论 3 296
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,037评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,178评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,964评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,703评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,863评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,333评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,658评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,374评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,195评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,988评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,167评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,970评论 2 279

推荐阅读更多精彩内容