Ionic2Cordova蓝牙插件封装

作者: 被代码耽误的机车手 | 来源:发表于2017-06-27 17:41 被阅读362次

    首先有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;
            }
    
        }
    
    

    相关文章

      网友评论

      • ZhAng_cd94:大神,我现在想开发一个关于cordova搜索蓝牙设备的功能,看到你这篇文章,现在是加到了我的自定义插件里面,但是不起作用,能否给指导一下,我是一个对于cordova的小白,只会使用一些ionic2提供的插件,但现在就是不输出到js上,
      • 昵称已被使用_:写android代码怎么才能有提示,需要使用android studio吗?像import android.*这些要导入的包怎么自动导入或有提示.
        昵称已被使用_: @好书生 OK,我用android studio试试
        被代码耽误的机车手: @小军617 这个你可以直接在一个原生的app写好 然后直接迁移到这个项目或者插件 如果想直接在项目里写的话 用android studio 打开platform文件下的android项目

      本文标题:Ionic2Cordova蓝牙插件封装

      本文链接:https://www.haomeiwen.com/subject/hgbecxtx.html