android藍牙通訊開發(詳細)


新建一個工程之后,我們可以先看到界面左邊的項目欄,我們可以看到,除了app目錄以外,大多數的文件和目錄都是自動生成的,我們也不需要對他們進行修改,而app目錄之下的文件才是我們工作的重點。下面,我先對app目錄下的內容進行一些講解。

1.AndroidManifest.xml

這是整個項目的配置文件,我們在程序中定義的四大組件都需要在這里注冊,另外,也可以在這里給應用程序添加權限聲明。

2.java

這個是放置我們所有java代碼的地方。

3.res

這個項目中所使用到的所有圖片、布局、字符串資源都要存放在這個項目。其中,drawable文件夾和mipmap文件夾都是用來存放圖片資源的。layout文件夾是用來存放布局資源的,values文件夾是存放字符串等資源的。

現在,我開始介紹一下關於android 藍牙的通信的知識。

首先,我們要在一個頁面中打開和關閉藍牙。我們可以點擊layout下的布局文件,先在其中添加兩個按鈕。在布局文件中,我們可以使用兩種方法來調出兩個按鈕。第一種就是直接在design界面,點擊Palette,找到Button,點擊然后拖動到旁邊的界面上。第二種就是在Text界面直接打入代碼,例如:

1 <Button
android:id="@+id/bt"
2 android:layout_width="match_parent" 3 android:layout_height="wrap_content" 4 tools:ignore="MissingConstraints" />

其中,layout_width和layout_height是必須要有的屬性,設置為match_parent表示充滿父控件空間,wrap_content表示根據控件自身大小顯示。id則是這個控件的名稱。然后我們開始設置這個按鈕的點擊事件。在我們的java文件中先定義一個Button對象,然后使用Button的對象調用findViewId方法。方法里的參數為控件的id。然后就設置點擊事件。

button.findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

在onClick方法中添加事件的代碼即可。

 

藍牙的開啟

首先,要在新建項目中的AndroidManifest.xml中聲明兩個權限:BLUETOOTH權限和BLUETOOTH_ADMIN權限。其中,BLUETOOTH權限用於請求連接和傳送數據;BLUETOOTH_ADMIN權限用於啟動設備、發現或進行藍牙設置,如果要擁有該權限,必須現擁有BLUETOOTH權限。

 因為android 6.0之后采用新的權限機制來保護用戶的隱私,如果我們設置的targetSdkVersion大於或等於23,則需要另外添加ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION權限,否則,可能會出現搜索不到藍牙設備的問題。

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

 

然后要在Activity中,添加

BluetoothAdapter blueadapter = BluetoothAdapter.getDefaultAdapter();
//獲取藍牙適配器

來獲取藍牙適配器的對象

在按鈕的點擊事件中添加以下代碼來開啟藍牙

if(blueadapter==bull)
//表示手機不支持藍牙
return;

 

 

 if (!blueadapter.isEnabled())
        //判斷本機藍牙是否打開
        {//如果沒打開,則打開藍牙
        blueadapter.enable();
        }

 

同理,使用disable()可以關閉藍牙。

搜索藍牙

接下來是搜索藍牙設備,如果想要改變自己藍牙能否被搜索的狀態,可以使用以下的代碼來使自己的藍牙設備可被發現

if (blueadapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) //不在可被搜索的范圍
        {
        Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//設置本機藍牙在300秒內可見
        startActivity(discoverableIntent);
        }

如果想搜索別人的設備,就可以在按鈕的點擊事件中調用startDiscover()來搜索藍牙

public void doDiscovry() {
    if (blueadapter.isDiscovering()) {
        //判斷藍牙是否正在掃描,如果是調用取消掃描方法;如果不是,則開始掃描
        blueadapter.cancelDiscovery();
    } else
        blueadapter.startDiscovery();

}

我們要想獲得搜索到的結果,就需要注冊廣播

首先,要定義一個列表控件listview和一個集合適配器和兩個ArrayList集合類的對象,這兩個對象都是用來存設備的地址,只是有一個不是用來顯示出來的。在主類中定義以下的集合適配器和兩個ArrayList集合類的對象,然后在onCreate中定義適配器

public ArrayAdapter adapter;
ListView listView = (ListView) findViewById(R.id.list);//控件 列表
//定義一個列表,存藍牙設備的地址。 public ArrayList<String> arrayList=new ArrayList<>(); //定義一個列表,存藍牙設備地址,用於顯示。 public ArrayList<String> deviceName=new ArrayList<>();
//定義適配器
adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1, deviceName);

//給列表添加適配器
listView.setAdapter(adapter);

然后定義廣播以及處理廣播的消息

IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//注冊廣播接收信號
registerReceiver(bluetoothReceiver, intentFilter);//用BroadcastReceiver 來取得結果

private final BroadcastReceiver bluetoothReceiver = new 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);
            deviceName.add("設備名:"+device.getName()+"\n" +"設備地址:"+device.getAddress() + "\n");//將搜索到的藍牙名稱和地址添加到列表。
            arrayList.add( device.getAddress());//將搜索到的藍牙地址添加到列表。
            adapter.notifyDataSetChanged();//更新
        }
    }
 };

搜索完設備后,要記得注銷廣播。注冊后的廣播對象在其他地方有強引用,如果不取消,activity會釋放不了資源 。

protected void onDestroy(){
    super.onDestroy();//解除注冊
    unregisterReceiver(bluetoothReceiver);
}

4.了解targetSdkVersion是否大於或等於23

       若是大於或等於23,除了添加了藍牙權限外,還要動態獲取位置權限,才能將搜索到的藍牙設備顯示出來。若是小於,則不需要動態獲取權限。
動態申請權限

public void applypermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            //檢查是否已經給了權限
            int checkpermission = ContextCompat.checkSelfPermission(getApplicationContext(),
                    Manifest.permission.ACCESS_FINE_LOCATION);
            if (checkpermission != PackageManager.PERMISSION_GRANTED) {//沒有給權限
                Log.e("permission", "動態申請");
                //參數分別是當前活動,權限字符串數組,requestcode
                ActivityCompat.requestPermissions(WiFiMainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(WiFiMainActivity.this, "已授權", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(WiFiMainActivity.this, "拒絕授權", Toast.LENGTH_SHORT).show();
        }

    }

 配對藍牙設備

 藍牙的配對和連接有兩種方式。一種是每個設備作為一個客戶端去連接一個服務端,向對方發起連接。另一種則是作為服務端來接收客戶端發來連接的消息。藍牙之間的數據傳輸采用的是和TCP傳輸類似的傳輸機制。

1.作為客戶端連接
       首先要獲取一個代表遠程設備BluetoothDevice的對象,然后使用該BluetoothDevice的對象來獲取一個BluetoothSocket對象。BluetoothSocket對象調用connect()可以建立連接。

       藍牙連接整個過程需要在子線程中執行的,並且要將 scoket.connect()放在一個新的子線程中,因為如果將這個方法也放在同一個子線程中解決的話,就會永遠報錯read failed, socket might closed or timeout, read ret: -1;借鑒網上的方法:再開一個子線程專門執行socket.connect()方法,問題可以解決;

       另外,借鑒網上方法和建議,在獲得socket的時候 ,盡量不使用uuid方式;因為這樣雖然能夠獲取到socket 但是不能進行自動,所以使用的前提是已經配對了的設備連接;

       使用反射的方式,能夠自動提示配對,也適合手機間通信。

首先,在設置列表中顯示的藍牙的點擊事件,其中ClientThread是連接,發送和接收的線程類

//定義列表Item的點擊事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
                device = adapter.getRemoteDevice(arrayList.get(i));
                mge = editText.getText().toString() + "\r\n";//獲得編輯文本框里的文字
                clientThread = new ClientThread(device, mge, context);
                clientThread.start();
            }


        });

然后定義一個線程類,在他的構造方法中接收listview點擊事件里傳來的參數

class ClientThread extends Thread {

public void run() {
}
}

在run方法中輸入以下代碼

final BluetoothSocket socket = (BluetoothSocket) device.getClass().getDeclaredMethod("createRfcommSocket", new Class[]{int.class}).invoke(device, 1);

   代碼中的device需要把注冊廣播時的device作為參數傳進線程中。注意,傳進來的device的值要為遠程設備的地址,若不是或有出入,則可能會出現NullPointerException異常,並提示嘗試調用一個空的對象。為了解決這個問題,可以把顯示獲得的device名字、地址和傳入線程的device的地址分在不同的集合類。傳入線程的device使用只有設備地址的集合類。

       在連接藍牙之前,還要先取消藍牙設備的掃描,否則容易連接失敗。

 

adapter.cancelDiscovery();//adapter為獲取到的藍牙適配器 socket.connect();//連接

 

2.作為服務端連接
       服務端接收連接需要使用BluetoothServerSocket類,它的作用是監聽進來的連接,在一個連接被接收之后,會返回一個BluetoothSocket對象,這個對象可以用來和客戶端進行通信。

       與客戶端一樣,服務端也要在子線程中實現。通過調用listenUsingRfcommWithServiceRecord(String,UUID)方法可以得到一個BluetoothServerSocket的對象,然后再用這個對象來調用accept()來返回一個BluetoothSocket對象。由於accept()是個阻塞的方法,它會直到接收到一個連接或異常之后才會返回,所以要放在子線程中。

bluetoothServerSocket=bluetoothAdapter.listenUsingRfcommWithServiceRecord(bluetoothAdapter.getDefaultAdapter().getName(), UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//bluetoothServerSocket= (BluetoothServerSocket) bluetoothAdapter.getClass().getMethod("listenUsingRfcommOn",new Class[]{int.class}).invoke(bluetoothAdapter,10); socket=bluetoothServerSocket.accept();//接收連接

 這個連接時只允許一個客戶端連接,因此在BluetoothServerSocket對象接收到一個連接請求時就要立刻調用close()方法把服務端關閉。

當兩個設備成功連接之后,雙方都會有一個BluetoothSocket對象,這時,就可以在設備之間傳送數據了。

       1.使用getOutputStream()方法來獲取輸出流來處理傳輸。

       2.調用write()。

復制代碼
os = socket.getOutputStream();//獲取輸出流 if (os != null) {//判斷輸出流是否為空 os.write(message.getBytes("UTF-8")); } os.flush();//將輸出流的數據強制提交 os.close();//關閉輸出流 }
復制代碼

 3.使用getInputStream()方法來獲取輸出流來處理傳輸。

4.創建一個新的線程來read()輸入流,這里是接收16進制數然后轉換為10進制數顯示

ew Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                while (true) {
                                    int count = 0;
                                    while (count == 0) {
                                        count = is.available();
                                    }
                                    byte buf[] = new byte[count];
                                    if (buf != null) {
                                        is.read(buf);
                                       // BuletoothMainActivity.num++;
                                        String message = BuletoothMainActivity.bytesToIntString(buf);

                                        BuletoothMainActivity.UpdateRevMsg(message);
                                    }

                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
public static String bytesToIntString(byte[] bytes) {//16進制轉10進制以字符串形式輸出顯示
        String result = "";
        for (int i = 0; i < bytes.length; i++) {
            String hexString = Integer.toString(bytes[i] & 0xFF);
            if (hexString.length() == 1) {
                hexString = '0' + hexString;
            }
            result += hexString.toUpperCase();
        }
        return result;
    }

 

public static void UpdateRevMsg(String revMsg) { mRevMsg=revMsg; handler.post(RefreshTextView); } private static Runnable RefreshTextView=new Runnable() { @Override public void run() { textView2.setText(mRevMsg); } };

 

在服務端發送和接收也類似上面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM