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閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容