天天看點

android 和 PC端 進行藍牙通信 demo

前提:

      1. 使用真機測試

      2. 測試前請藍牙配對好手機與PC機藍牙擴充卡(是以你需要一個藍牙擴充卡插入PC USB口)

demo測試效果:

      當手機左右搖擺時将資料傳遞到PC端,列印出來。(android重力感應)

PC服務端代碼:

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

public class BTServer implements Runnable {

    // 流連接配接通知 用于建立流連接配接
    private StreamConnectionNotifier myPCConnNotifier = null;
    // 流連接配接
    private StreamConnection streamConn = null;
    // 接受資料位元組流
    private byte[] acceptedByteArray = new byte[12];
    // 讀取(輸入)流
    private InputStream inputStream = null;

    /**
     * 主線程
     *  
     * @param args
     */
    public static void main(String[] args) {
        new BTServer();
    }

    /**
     * 構造方法
     */
    public BTServer() {
        try {
            // 得到流連接配接通知,下面的UUID必須和手機用戶端的UUID相一緻。
            myPCConnNotifier = (StreamConnectionNotifier) Connector
                    .open("btspp://localhost:0000110100001000800000805F9B34FB");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 打開連接配接通道并讀取流線程
        new Thread(this).start();
    }

    @Override
    public void run() {
        try {
            String inSTR = null;
            // 持續保持着監聽用戶端的連接配接請求
            while (true) {
                // 擷取流連接配接
                streamConn = myPCConnNotifier.acceptAndOpen();
                // 擷取流通道
                inputStream = streamConn.openInputStream();
                // 讀取位元組流
                while (inputStream.read(acceptedByteArray) != -1) {
                    inSTR = new String(acceptedByteArray);
                    System.out.println(inSTR);
                    if (inSTR.contains("EXIT")) {
                        // 手機用戶端退出則關閉連接配接通道。
                        inputStream.close();
                        if (streamConn != null) {
                            streamConn.close();
                        } 
                        break;
                    }  
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}      

服務端請導入bluecove.jar 和 commons-io.jar包

android手機用戶端代碼:

BlueTooth.java

package com.royal.bluetooth;

import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

/**
 * BlueTooth & Sensor
 * 
 * @author royal
 * 
 */

public class BlueTooth extends Activity {

    private static final int REQUEST_DISCOVERY = 0x1;
    // 建立藍牙通信的UUID 
    private static final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    // 自帶的藍牙擴充卡
    private BluetoothAdapter bluetoothAdapter = null;
    // 掃描得到的藍牙裝置
    private BluetoothDevice device = null;
    // 藍牙通信socket
    private BluetoothSocket btSocket = null;
    // 手機輸出流
    private OutputStream outStream = null;
    private byte[] msgBuffer = null;
    // 傳感器管理
    private SensorManager sensorMgr = null;
    // 傳感器感應
    private Sensor sensor = null;
    // 手機x、y、z軸方向資料
    private int x, y, z;

    /**
     * 當這個activity第一次被建立的時候呼叫該方法
     **/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /* 使程式視窗全屏 */
        // 建立一個沒有title的全屏主題
        this.setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
        // 視窗全屏
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 設定全屏标志
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // 按bluetooth.xml檔案布局風格
        setContentView(R.layout.bluetooth);

        // Gravity sensing 擷取傳感器
        sensorMgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
        sensor = sensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        // 擷取手機預設上的藍牙擴充卡
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // 開啟手機藍牙裝置
        bluetoothAction();

        // 查詢附近所有的藍牙裝置并選擇連接配接
        connectToDevice();
    }

    /**
     * 藍牙開始 查詢手機是否支援藍牙,如果支援的話,進行下一步。 檢視藍牙裝置是否已打開,如果否則打開。
     */
    public void bluetoothAction() {
        // 檢視手機是否有藍牙裝置功能
        if (hasAdapter(bluetoothAdapter)) {
            if (!bluetoothAdapter.isEnabled()) {
                // 開啟藍牙功能
                bluetoothAdapter.enable();
            }
        } else {
            // 程式終止
            this.finish();
        }
    }

    /**
     * 檢視手機是否有藍牙裝置功能
     * 
     * @param ba
     *            藍牙裝置擴充卡
     * @return boolean
     */
    public boolean hasAdapter(BluetoothAdapter ba) {
        if (ba != null) {
            return true;
        }
        displayLongToast("該手機沒有藍牙功能!");
        return false;
    }

    /**
     * 建立一個長時間彈出的提示視窗toast
     * 
     * @param str
     *            提示字元串
     */
    public void displayLongToast(String str) {
        Toast toast = Toast.makeText(this, str, Toast.LENGTH_LONG);
        toast.setGravity(Gravity.TOP, 0, 220);
        toast.show();
    }

    /**
     * 建立一個短時間彈出的提示視窗toast
     * 
     * @param str
     *            提示字元串
     */
    public void displayShortToast(String str) {
        Toast toast = Toast.makeText(this, str, Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.TOP, 0, 220);
        toast.show();
    }

    /**
     * 藍牙若啟動,則查詢附近的所有藍牙裝置進行選擇連接配接
     */
    public void connectToDevice() {
        if (bluetoothAdapter.isEnabled()) {
            // 跳到另一個activity---DiscoveryActivity,該類用于查詢附近所有的藍牙裝置。
            Intent intent = new Intent(this, DiscoveryActivity.class);
            // 彈出視窗提示
            displayLongToast("請選擇一個藍牙裝置進行連接配接!");

            // 手機此時跳進DiscoveryActivity程式界面。
            // 注意:利用startActivityForResult回調資料傳回目前的程式。
            // 詳細參考:http://snmoney.blog.163.com/blog/static/440058201073025132670/
            this.startActivityForResult(intent, REQUEST_DISCOVERY);
        } else {
            this.finish();
        }
    }

    /**
     * startActivityForResult觸發調用DiscoveryActivity後進行處理
     * 擷取到相應的藍牙位址資料後,開始我們核心的資料互動
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        // super.onActivityResult(requestCode, resultCode, data);

        // 這裡確定互相回調時資料的準确傳輸
        if (requestCode != REQUEST_DISCOVERY) {
            return;
        }
        if (resultCode != RESULT_OK) {
            return;
        }

        // 擷取到DiscoveryActivity點選項後傳過來的藍牙裝置位址
        String addressStr = data.getStringExtra("address");
        // 根據藍牙裝置位址得到該藍牙裝置對象(這是掃描到的藍牙裝置哦,不是自己的)
        device = bluetoothAdapter.getRemoteDevice(addressStr);
        try {
            //根據UUID建立通信套接字
            btSocket = device.createRfcommSocketToServiceRecord(uuid);
        } catch (Exception e) {
            displayLongToast("通信通道建立失敗!");
        }

        if (btSocket != null) {
            try {
                //這一步一定要確定連接配接上,不然的話程式就卡死在這裡了。
                btSocket.connect();
                displayLongToast("通信通道連接配接成功!");
            } catch (IOException ioe) {
                displayLongToast("通信通道連接配接失敗!");
                try {
                    btSocket.close();
                    displayLongToast("通信通道已關閉!");
                } catch (IOException ioe2) {
                    displayLongToast("通信通道尚未連接配接,無法關閉!");
                }
            } 
            try {
                // 擷取輸出流
                outStream = btSocket.getOutputStream();
                // 手機發出資料
                sendSensorData();
            } catch (IOException e) {
                displayLongToast("資料流建立失敗!");
            } 
        }
    }

    /**
     * 發送資料 發出從手機通過重力感應器擷取到的資料
     */
    public void sendSensorData() {
        // 重力感應監聽
        SensorEventListener lsn = new SensorEventListener() {
            // 重寫内部方法,當精确度發生變化是觸發該方法。
            @Override
            public void onAccuracyChanged(Sensor s, int accuracy) {
                // TODO Auto-generated method stub
            }

            // 重寫内部方法,當資料發生變化的時候觸發該方法。
            @Override
            public void onSensorChanged(SensorEvent se) {
                // TODO Auto-generated method stub
                /**
                 * 當手機橫向頭部朝左螢幕正對自己時 x=10,y=0,z=0; 當手機豎向螢幕正對自己時 x=0,y=10,z=0;
                 * 當手機平放螢幕朝上時 x=0,y=0,z=10; 由此可知:當手握手機且豎向螢幕正對自己時,有: 水準就是X軸
                 * 垂直就是Y軸 螢幕所對方向便是Z軸 具體可參考簡單例子---SensorDemo
                 */
                x = (int)se.values[SensorManager.DATA_X];
                y = (int)se.values[SensorManager.DATA_Y];
                z = (int)se.values[SensorManager.DATA_Z];
                if (y > 5 || y < -5) {
//                    String str = String.valueOf(x).concat(String.valueOf(y)).concat(String.valueOf(z));
                    String str = "x" + String.valueOf(x) + "y" + String.valueOf(y) + "z" + String.valueOf(z) + "/";
                    msgBuffer = str.getBytes();
                    try {
                        System.out.println("x=" + x + " y =" + y + " z =" + z);
                        outStream.write(msgBuffer);
                    } catch (IOException e) {
                        displayShortToast("資料發送失敗!");
                    }
                }
//                if (y > 5 || y < -5) {
//                    DataModel dataModel=new DataModel(x,y,z);
//                    try {
//                        System.out.println("x=" + x + " y =" + y + " z =" + z);
//                        msgBuffer = dataModel.convertSelfToByteArray();
//                        System.out.println("--------"+msgBuffer.length);
//                        outStream.write(msgBuffer);
//                    } catch (IOException e) {
//                        Log.e("BlueTooth",e.getMessage());
//                        e.printStackTrace();
//                        displayShortToast("資料發送失敗!");
//                    }
//                }
            }
        };


        // 别忘了注冊重力感應器,由于android的一些東東是又很大一部分都要這麼幹的。
        // 所有要注意。比如藍牙這塊,在對它打開的時候其實你也要注冊權限的。看AndroidManifest.xml檔案。
        sensorMgr
                .registerListener(lsn, sensor, SensorManager.SENSOR_DELAY_GAME);
    }

    // ////////////////////以下是退出程式的一些操作,不關核心功能的事/////////////////////////////////
    /**
     * 重寫方法:點選傳回鍵,确認是否退出程式。
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // TODO Auto-generated method stub
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Builder alertDialog = new AlertDialog.Builder(this);
            // 設定彈出框的圖示
            alertDialog.setIcon(R.drawable.icon);
            // 設定彈出框的title
            alertDialog.setTitle(R.string.prompt);
            // 設定彈出框的提示資訊
            alertDialog.setMessage(R.string.quit);
            // 設定彈出框确認鍵觸發事件
            alertDialog.setPositiveButton(R.string.confirm,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog,
                                int whichButton) {
                            // TODO Auto-generated method stub
                            try {
                                outStream.write("QUIT".getBytes());
                                btSocket.close();
                            } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                            finish(); 
                        }
                    });
            // 設定彈出框取消鍵觸發事件(不做任何操作)
            alertDialog.setNegativeButton(R.string.cancel,
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // TODO Auto-generated method stub
                        }
                    });
            // 顯示彈出框
            alertDialog.show();
            return true;
        } else {
            // 如果點選的不是傳回鍵按鈕,那麼該做什麼操作就做什麼操作。
            return super.onKeyDown(keyCode, event);
        }
    }

    /**
     * 重寫方法:銷毀線程,退出系統。
     */
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        System.exit(0);
    }

}      

DiscoveryActivity.java

package com.royal.bluetooth;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ListView;
import android.widget.SimpleAdapter;

/**
 * 該類內建ListActivity,主要是掃描并顯示出附近所有的藍牙裝置 結果傳回給BlueTooth
 * 
 * @author royal
 */
public class DiscoveryActivity extends ListActivity {

    // 擷取手機預設上的藍牙擴充卡
    private BluetoothAdapter blueToothAdapter = BluetoothAdapter
            .getDefaultAdapter();

    // 把每一個HashMap鍵值對的藍牙裝置資訊存放到list數組中并按檔案布局風格的方式呈現出來
    private ArrayList<HashMap<String, String>> list = null;
    // 用于真正存放所有掃描到的藍牙裝置的list
    private List<BluetoothDevice> _devices = new ArrayList<BluetoothDevice>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        /* 使程式視窗全屏 */
        // 建立一個沒有title的全屏主題
        this.setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
        // 視窗全屏
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 設定全屏标志
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // 按discovery.xml檔案布局風格
        setContentView(R.layout.discovery);

        list = new ArrayList<HashMap<String, String>>();

        // 把掃描都的每一個藍牙裝置放到list中,并呈現給用戶端
        showDevices();
    }

    /**
     * 把掃描都的每一個藍牙裝置放到list中,并呈現給用戶端。
     */
    public void showDevices() {
        // 擷取所有已配對的藍牙裝置
        Set<BluetoothDevice> devices = blueToothAdapter.getBondedDevices();

        if (devices.size() > 0) {
            Iterator<BluetoothDevice> it = devices.iterator();
            BluetoothDevice bluetoothDevice = null;
            HashMap<String, String> map = new HashMap<String, String>();
            while (it.hasNext()) {
                bluetoothDevice = it.next();
                // 把每一個擷取到的藍牙裝置的名稱和位址存放到HashMap數組中,比如:xx:xx:xx:xx:xx: royal
                map.put("address", bluetoothDevice.getAddress());
                map.put("name", bluetoothDevice.getName());
                // 該list用于存放呈現的藍牙裝置,存放的是每個裝置的map
                list.add(map);
                // 該list用于存放的是真正的每一個藍牙裝置對象
                _devices.add(bluetoothDevice);
            }

            // 構造一個簡單的自定義布局風格,各個參數都有明确的相對應。具體給google一下SimpleAdapter和參考一些文獻
            SimpleAdapter listAdapter = new SimpleAdapter(this, list,
                    R.layout.device, new String[] { "address", "name" },
                    new int[] { R.id.address, R.id.name });
            this.setListAdapter(listAdapter);
        }
    }

    /**
     * list點選項觸發事件 當裝置掃描顯示完成後,可選擇點選相應的裝置進行連接配接。
     */
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Intent result = new Intent();
        String addressStr = _devices.get(position).getAddress();
        //位址隻取到17位,雖然addressStr和address都一樣 xx:xx:xx:xx:xx:xx
        String address = addressStr.substring(addressStr.length() - 17);

        result.putExtra("address", address);
        // 這個就是回傳資料了,将位址傳回給BlueTooth---activity
        // 這裡的resultCode是RESULT_OK,BlueTooth---activity方法onActivityResult裡對應的resultCode也應該是RESULT_OK
        //隻有resultCode值相比對,才能確定result資料回調不出錯。
        setResult(RESULT_OK, result);
        // 一定要finish,隻有finish後才能将資料傳給BlueTooth---activity
        // 并在onActivityResult做處理
        finish();
    }

}      

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.royal.bluetooth"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">

        <activity android:name=".BlueTooth"
                  android:label="@string/app_name" 
                  android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".DiscoveryActivity"
                  android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

    </application>

    <!-- 打開和關閉藍牙部分的權限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>

    <uses-sdk android:minSdkVersion="7" />

</manifest>      

布局:

bluetooth.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/sensor"
    />
</LinearLayout>      

 device.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:paddingBottom="1dip"
    android:paddingLeft="10dip"
    android:paddingRight="10dip"
    android:paddingTop="1dip" >

    <TextView
        android:id="@+id/address"
        android:layout_width="180dip"
        android:layout_height="30dip"
        android:singleLine="true"
        android:textSize="10pt" />

    <TextView
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="right"
        android:textSize="10pt" />

</LinearLayout>      

discovery.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent">

    <LinearLayout
        android:id="@+id/listLinearLayout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ListView
            android:id="@id/android:list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:drawSelectorOnTop="false"
            android:scrollbars="vertical"/>

    </LinearLayout>

</LinearLayout>      

 ​