android 藍牙開發---與藍牙模塊進行通訊 基於eclipse項目

来源:http://www.cnblogs.com/liumce/archive/2017/10/20/7698632.html
-Advertisement-
Play Games

2017.10.20 之前參加一個大三學長的創業項目,做一個智能的車鎖App,用到嵌入式等技術,App需要藍牙、實時位置等技術,故查了幾篇相關技術文章,以此參考! //先說說如何開啟藍牙設備和設置可見時間: private void search() { BluetoothAdapter adapt ...


  2017.10.20 之前參加一個大三學長的創業項目,做一個智能的車鎖App,用到嵌入式等技術,App需要藍牙、實時位置等技術,故查了幾篇相關技術文章,以此參考!       複製代碼       //先說說如何開啟藍牙設備和設置可見時間:    
 private void search() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (!adapter.isEnabled()) {
            adapter.enable();
        }
        Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); //3600為藍牙設備可見時間
        startActivity(enable);
        Intent searchIntent = new Intent(this, ComminuteActivity.class);
        startActivity(searchIntent);
    }
複製代碼

      首先,需要獲得一個BluetoothAdapter,可以通過getDefaultAdapter()獲得系統預設的藍牙適配器,當然我們也可以自己指定,但這個真心沒有必要,至少我是不需要的。然後我們檢查手機的藍牙是否打開,如果沒有,通過enable()方法打開。接著我們再設置手機藍牙設備的可見,可見時間可以自定義。
      完成這些必要的設置後,我們就可以正式開始與藍牙模塊進行通信了:

複製代碼
public class ComminuteActivity extends Activity {
    private BluetoothReceiver receiver;
    private BluetoothAdapter bluetoothAdapter;
    private List<String> devices;
    private List<BluetoothDevice> deviceList;
    private Bluetooth client;
    private final String lockName = "BOLUTEK";
    private String message = "000001";
    private ListView listView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.search_layout);

        listView = (ListView) this.findViewById(R.id.list);
        deviceList = new ArrayList<BluetoothDevice>();
        devices = new ArrayList<String>();
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        bluetoothAdapter.startDiscovery();
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        receiver = new BluetoothReceiver();
        registerReceiver(receiver, filter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                setContentView(R.layout.connect_layout);
                BluetoothDevice device = deviceList.get(position);
                client = new Bluetooth(device, handler);
                try {
                    client.connect(message);
                } catch (Exception e) {
                    Log.e("TAG", e.toString());
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }

    private final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Bluetooth.CONNECT_FAILED:
                    Toast.makeText(ComminuteActivity.this, "連接失敗", Toast.LENGTH_LONG).show();
                    try {
                        client.connect(message);
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                    break;
                case Bluetooth.CONNECT_SUCCESS:
                    Toast.makeText(ComminuteActivity.this, "連接成功", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.READ_FAILED:
                    Toast.makeText(ComminuteActivity.this, "讀取失敗", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.WRITE_FAILED:
                    Toast.makeText(ComminuteActivity.this, "寫入失敗", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.DATA:
                    Toast.makeText(ComminuteActivity.this, msg.arg1 + "", Toast.LENGTH_LONG).show();
                    break;
            }
        }
    };

    private class BluetoothReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (isLock(device)) {
                    devices.add(device.getName());
                }
                deviceList.add(device);
            }
            showDevices();
        }
    }

    private boolean isLock(BluetoothDevice device) {
        boolean isLockName = (device.getName()).equals(lockName);
        boolean isSingleDevice = devices.indexOf(device.getName()) == -1;
        return isLockName && isSingleDevice;
    }

    private void showDevices() {
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                devices);
        listView.setAdapter(adapter);
    }
}
複製代碼

      要想與任何藍牙模塊進行通信,首先得搜到該設備:

複製代碼
 private class BluetoothReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (isLock(device)) {
                    devices.add(device.getName());
                }
                deviceList.add(device);
            }
            showDevices();
        }
    }
複製代碼

     在這之前,我們得先調用一個方法:

 bluetoothAdapter.startDiscovery();

     startDiscovery()方法是一個非同步方法,它會對其他藍牙設備進行搜索,持續時間為12秒。搜索過程其實是在System Service中進行,我們可以通過cancelDiscovery()方法來停止這個搜索。在系統搜索藍牙設備的過程中,系統可能會發送以下三個廣播:ACTION_DISCOVERY_START(開始搜索),ACTION_DISCOVERY_FINISHED(搜索結束)和ACTION_FOUND(找到設備)。ACTION_FOUND這個才是我們想要的,這個Intent中包含兩個extra fields:EXTRA_DEVICE和EXTRA_CLASS,包含的分別是BluetoothDevice和BluetoothClass,BluetoothDevice中的EXTRA_DEVICE就是我們搜索到的設備對象。 確認搜索到設備後,我們可以從得到的BluetoothDevice對象中獲得設備的名稱和地址。

     在android中使用廣播需要我們註冊,這裡也不例外:

 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
 receiver = new BluetoothReceiver();
 registerReceiver(receiver, filter);

     廣播註冊後需要我們撤銷,這個可以放在這裡進行:

   @Override
    protected void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }

    這樣在Activity結束的時候就會自動撤銷該廣播,而不需要我們手動執行。
    我這裡使用一個ListView來顯示搜索到的藍牙設備,但因為需要只限定一個藍牙設備,所以這裡進行了檢查,檢查該設備是否是我們的目標設備,如果是,就添加。當然,為了防止重覆添加,有必要增加這麼一句:

 boolean isSingleDevice = devices.indexOf(device.getName()) == -1;

    搜索到該設備後,我們就要對該設備進行連接。

複製代碼
public void connect(final String message) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                BluetoothSocket tmp = null;
                Method method;
                try {
                    method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
                    tmp = (BluetoothSocket) method.invoke(device, 1);
                } catch (Exception e) {
                    setState(CONNECT_FAILED);
                    Log.e("TAG", e.toString());
                }
                socket = tmp;
                try {
                    socket.connect();
                    isConnect = true;
                } catch (Exception e) {
                    setState(CONNECT_FAILED);
                    Log.e("TAG", e.toString());
                }
複製代碼

      連接設備之前需要UUID,所謂的UUID,就是用來進行配對的,全稱是Universally Unique Identifier,是一個128位的字元串ID,用於進行唯一標識。網上的例子,包括谷歌的例子,它們的UUID都是說能用但是我用不了的,都會報出這樣的錯誤:

      Service discovery failed 

      原因可能是作為唯一標識的UUID沒有發揮作用,所以,我就利用反射的原理,讓設備自己提供UUID。

      這個錯誤在我們把手機既當做客戶端有當做服務端的時候,同樣也有可能出現,因為作為伺服器的時候,我們需要的也是同一個UUID:

mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

      作為客戶端是這樣的:

device.createRfcommSocketToServiceRecord(MY_UUID);

      當兩個UUID想同時建立Rfcomm的通道時,我們的選擇都是在兩個線程中分別實現,但是忽略了一件最重要的事情:同一個時間只能充當一個角色!所以,解決這個問題的方法就是在我們相連接的設備上也安裝同樣的應用程式,誰先發起連接誰就是客戶端,但我這裡是藍牙模塊啊!!怎麼能安裝我的應用程式呢!!解決辦法就在下麵的通信中。

      連接設備之前還有一件事必須確保:

 bluetoothAdapter.cancelDiscovery();

      這是為了停掉搜索設備,否則連接可能會變得非常慢並且容易失敗。
      有關於Socket的編程都需要我們設置一些狀態值來標識通信的狀態,以方便我們調錯,而且連接應該放在一個線程中進行,要讓該線程與我們程式的主線程進行通信,我們需要使用Handle,關於Handle的使用,可以參考我的另一篇博客http://www.cnblogs.com/wenjiang/p/3180324.html,這裡不多講。

      在使用Socket中,我註意到一個方法:isConnect(),它返回的是布爾值,但是根本就不需要使用到這個方法,Socket的連接如果沒有報錯,說明是已經連接上了。

      在谷歌提供的例子中,我們可以看到谷歌的程式員的程式水平很高,一些好的編碼習慣我們可以學習一下,像是在try..catch中才定義的變數,我們應該在try...catch之前聲明一個臨時變數,然後再在try...catch後賦值給我們真正要使用的變數。這種做法的好處就是:如果我們直接就是使用真正的變數,當出現異常的時候,該變數的使用就會出現問題,而且很難進行排查,如果是臨時變數,我麽可以通過檢查變數的值來確定是否是賦值時出錯。

     谷歌的例子中最大的感想就是滿滿的異常檢查,但也是因為這個,導致它的可讀性不高。java的異常處理機制有時候對於代碼的閱讀真的不是一件舒服的事情,能避免就儘量避免。

     如果連接沒有問題,我們就可以和藍牙模塊進行通信:

複製代碼
              if (isConnect) {
                    try {
                        OutputStream outStream = socket.getOutputStream();
                        outStream.write(getHexBytes(message));
                    } catch (IOException e) {
                        setState(WRITE_FAILED);
                        Log.e("TAG", e.toString());
                    }
                    try {
                        InputStream inputStream = socket.getInputStream();
                        int data;
                        while (true) {
                            try {
                                data = inputStream.read();
                                Message msg = handler.obtainMessage();
                                msg.what = DATA;
                                msg.arg1 = data;
                                handler.sendMessage(msg);
                            } catch (IOException e) {
                                setState(READ_FAILED);
                                Log.e("TAG", e.toString());
                                break;
                            }
                        }
                    } catch (IOException e) {
                        setState(WRITE_FAILED);
                        Log.e("TAG", e.toString());
                    }
                }

                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        Log.e("TAG", e.toString());
                    }
                }
            }
        }
複製代碼

        這裡包括寫入和讀取,用法和基本的Socket是一樣的,但是寫入的時候,需要將字元串轉化為16進位:

複製代碼
        private byte[] getHexBytes(String message) {
        int len = message.length() / 2;
        char[] chars = message.toCharArray();
        String[] hexStr = new String[len];
        byte[] bytes = new byte[len];
        for (int i = 0, j = 0; j < len; i += 2, j++) {
            hexStr[j] = "" + chars[i] + chars[i + 1];
            bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
        }
        return bytes;
    }
複製代碼

       當然,這裡只是將手機當做客戶端,但是接收藍牙模塊發送過來的信息是沒有必要特意創建服務端的,我們只要一個不斷監聽並讀取對方消息的迴圈就行。
       很簡單的程式就能實現像是藍牙串口助手的功能,由於是項目的代碼,不能貼完整的代碼,但是基本上都在上面了,大家可以參考一下。要想使用藍牙,相應的許可權也是必不可少的:

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

這篇博客出自:http://www.cnblogs.com/wenjiang/p/3200138.html

項目可以借鑒,但是打開項目稍微有些問題      

代碼放在github上:https://github.com/wenjiang/Bluetooth2.git

 

在此基礎上,可以用Android studio 來嘗試修改代碼建立藍牙通信!

 

後續項目建成上傳至github上。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • vue的實例對象 首先用js的new關鍵字實例化一個vue el: vue組件或對象裝載在頁面的位置,可通過id或class或標簽名 template: 裝載的內容。HTML代碼/包含指令或者其他組件的HTML片段,template將是我們使用的模板 data: 數據通過data引入到組件中 在組件 ...
  • 我們都知道,JS 中沒有的類的概念。 而 函數 則是 JS 中 很重要,很重要,很重要的一點。 理解 函數 也是非常有必要的。 先來看看 函數的定義。 如下: 上面講到了“函數的定義”,但是要怎麼理解“函數聲明”與“函數表達式”的不同呢? 讓我們看看什麼是“函數提升”?。 “函數提升”,會把當前作用 ...
  • 在使用Validform v5.3.2時(http://validform.rjboy.cn/) 問題:可以為空,但不為空時需要按照指定格式驗證數據 查看文檔: 5.2.1版本之後,datatype支持:1、直接綁定正則:如可用這樣寫datatype="/\w{3,6}/i",要求是3到6位的字母, ...
  • 非同步實時搜索jquery select插件 一、先看看效果。 二、做此插件的原因。 1.數據量過大(幾千、幾萬條),無法一次性全部載入。 2.現有插件各不相同,無法滿足功能需求。 3.美觀性,可控性不足。 三、如何使用。 1.html和js 2.實例。 3.詳細配置。 還有一些其它的api,詳細請看 ...
  • 品牌:超凡魔術師h5案例地址:http://www.199case.com/caseview.aspx?id=2668 1、內容:一個以”超凡魔術師“為主題的視頻+回答類的推文,用戶可以通過觀看一段一個人物在表演魔術的視頻,在其間用戶可以選擇兩個選項中的一項與之進行互動。 2、亮點:這篇推文最大的亮 ...
  • 1、java堆得Young區由哪些組成: Java堆由Perm區和Heap區組成,Heap區由Old區和New區(也叫Young區)組成,New區由Eden區、From區和To區(Survivor)組成。 2、實現ContentProvider需要實現哪些方法: insert、delete、quer ...
  • #菜單 pod 'LGSideMenuController' # 刷新 pod 'MJRefresh' # 網路請求 pod 'AFNetworking', '~> 3.0' # 圖片緩存 pod 'SDWebImage', '~> 4.0' # 數據解析 pod 'MJExtension' # 網 ...
  • CVPixelBufferRef YUV NV12 pixelFormatType 創建 數據填充 數據讀取 CGImageRef to CVPixelBufferRef ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...