Android smartphone connect with Bluetooth module.

Key words: Android, bluetoothmodule, SPP, 蓝牙

目录:

一. android 官方提供的案例分析

1 主Activity

1.1 onCreate()

1.2 onStart()

1.3 onResume()

1.4 onDestroy()

1.5 menuItem

2 BluetoothClient

2.1 BluetoothSocket Programming

2.1.1 Server
2.1.2 Client
2.1.3 ConnectedThread

3 Mobile Phone Bluetooth With Other Bluetooth Module (Bluetooth Hardware)

3.1 UUID配对问题

3.2 Javax.Comm 或者 RXTXComm API & AT command

————————————————————————————————————————————————————————————————————————————

一. android 官方提供的案例分析

我首先用简单的话来概括一下,该蓝牙的案例是由两个部分组成: 第一是主activity,第二个是BluetoothClient,包含connect进程 & connected进程。

接受到的数据通过handler为手段,msg类作为容器来进行传递,传递给activity以后,获得msg的内容在进行相应的GUI改变或者其他操作(数据库等等根据所需)。

1 主Activity

在这个activity里,首先根据activity的流程(注意我的解释基本按照activity的流程来说,编程的时候无论用不用那个节点的方法,我总是写上,并且带上相应的debug语句方便以后监视调试)。

1.1 onCreate()

protected void onCreate(Bundle savedInstanceState)

这个总是初始化GUI 组件,获得组件名,然后特别的蓝牙部分在于:

        //Dealing with the bluetooth connection.
        // Get local Bluetooth adapter
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        // If the adapter is null, then Bluetooth is not supported
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
要用蓝牙总归要先判断一下你手机有没有蓝牙模块,这一段就是起这个作用。

1.2 onStart()

public void onStart() 
有模块是不是开启了,是不是又权限,这个需要用户决定,所以这里系统将检查并询问用户是否开启使用蓝牙。
        // If BT is not on, request that it be enabled.
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
        } else {
            if(mBluetoothClient==null) setupBlueToothClient();
        }
这里运用了android的信息传递机制就是Intent对象。

void startActivityForResult(intent, “是否启用返回值处理”)

@Override //返回值处理方法
    public void onActivityResult(int requestCode, int resultCode, Intent data){switch case}
这两个是系统现有的方法,如果你需要处理系统返回的消息,则成对出现。 “是否启用返回值处理” 在android里是由系统定义的RequestCode, 当requestCode > 0的时候,你就有必要调用 "返回值处理方法", 因为的确有返回值需要处理。

这两个方法需要被再次调用用来询问是否进行连接。

1.3 onResume()

    @Override
    public synchronized void onResume() {
        super.onResume();
        if(D) Log.e(TAG, "+ ON RESUME +");
        
        if(mBluetoothClient!=null){
            mBluetoothClient.setmHandler(this._Handler); //cannot be deleted, keep mhanlder is always represents the current Activities Handler
            if(mBluetoothClient.getState()==BlueToothClient.STATE_NONE)
                mBluetoothClient.start();//first time initialize connectionstatus.
        }

BluetoothClient 是管理蓝牙的客户端(顾名思义), 管理连接,断开,信息传递。 觉得真是一个很对象的概念。

如果有连接而且从其他activity回到当前activity那当然要把handler抓回来,因为下面的操作都将在当前activity发生。

那么什么时候client会被初始化呢? 相信非常好理解,当你的手机有蓝牙功能,Client就要出现了!来处理连接等问题。

1.4 onDestroy()

这个就是我之前所说debug专用风格。

    @Override   //进行数据的保存,这是程序不会被android由于内存不够强行关闭时的保存, 如果android申请额外内存是必须关闭当前程序是,则有必要使用
                  onSaveInstanceState(){} 这个方法给出机会给程序保存必须保存的东西。
 public synchronized void onPause() {
        super.onPause();
        if(D) Log.e(TAG, "- ON PAUSE -");
    }

    @Override
    public void onStop() {
        super.onStop();   
        if(D) Log.e(TAG, "-- ON STOP --");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Stop the Bluetooth chat services
        if (mBluetoothClient != null) mBluetoothClient.stop();
        mBluetoothClient=null;
        if(D) Log.e(TAG, "--- ON DESTROY ---");
    }
onDestroy(),又要顾名思义了,处理一切需要处理的结尾工作,个人觉得譬如关闭线程,关闭动画,不提倡保存什么数据。

1.5 menuItem

前面提到过按键然后进行连接。按的键其实就是menuItem对象。对于蓝牙连接,启动模块以后(操作已经完成), 我们需要:扫描,能被找——2个按键。

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.option_menu, menu);
        return true;
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu){
        MenuInflater inflater = getMenuInflater();
        menu.clear();
        inflater.inflate(R.menu.option_menu, menu);
    	return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.scan:
            // Launch the DeviceListActivity to see devices and do scan
        	Intent serverIntent = new Intent(this, DeviceListActivity.class);            
            startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);            
            return true;
            
        case R.id.discoverable:
            // Ensure this device is discoverable by others
            ensureDiscoverable();            
            return true;     
        }        
        return false;    
    }
这三个方法是定义OptionMenu的三段论。很好理解。 同样这里出现了startActivityForResult()方法来处理连接其他机器这个动作,为什么呢?同样很好理解,会有很多机器有蓝牙开着被找,这个返回的DeviceList肯定是要被处理的,出一个列表,然后选择一个(EventonClick 事件监听方法),交给BluetoothClient去连接。 第一部分就全部结束了,个人觉得比较关键的其实是这个方法:

   @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(D) Log.d(TAG, "onActivityResult " + resultCode);
        switch (requestCode) {
        case REQUEST_CONNECT_DEVICE:
            // When DeviceListActivity returns with a device to connect
            if (resultCode == Activity.RESULT_OK) {
                // Get the device MAC address
                String address = data.getExtras()
                                     .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                // Get the BLuetoothDevice object
                BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
                // Attempt to connect to the device
                mBluetoothClient.connect(device);
            }
            break;
        case REQUEST_ENABLE_BT:
            // When the request to enable Bluetooth returns
            if (resultCode == Activity.RESULT_OK) {
                // Bluetooth is now enabled, so set up a chat session
                setupBlueToothClient();
            } else {
                // User did not enable Bluetooth or an error occured
            	if(D) Log.d(TAG, "BT not enabled");
                Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }
他将是开启Java Bluetooth Socket的一扇门。 下回就将阐述管理连接和接收到数据后的处理方法。

2 BluetoothClient

首先来理顺一下连接创建的思路:

当当前手机是作为client进行主动连接角色的时候, 所需要的工作是“连接”以及“连接任务完成后的数据流导向”

当当前手机是作为server被动连接角色的时候,所需要的工作是“监听”以及“连接任务完成后的数据流导向”

2.1 BluetoothSocket Programming

和平时处理网络socket编程的不同处在于,首先不牵涉到IP地址等。对于Bluetooth而言,使用的配对对象是已经被很好打包的“BluetoothDevice”。 同时应用程序需要有一个ID来进行区别,在此使用的RFComm信道就是用UUID进行区别,UUID产生的是伪随机码,而且基本没有重复的可能性, 可以很好的进行软件端口的区分。 UUID 将会在下面一部分进行额外的讨论。这一部分由Server, Client & ConnectedThread组成。

Socket Programming Diagram:


一句话server和client都各由两个线程进行管理。

server监听(AcceptThread),有连接后(ConncectedThread)把client打包成一个对象——Socket对象,然后就可以通过这个Socket对象获得Client的数据流IO对象(ConnectedThread功能)。

client连接后(ConnectThread, 有连接后(ConnectedThread把server打包成一个对象——Socket对象,然后就可以通过这个Socket对象获得Server的数据流IO对象(ConnectedThread功能)。

在server端,看到的是socket,

在client端,看到的也是socket。

2.1.1 Server

Server端的主要是由 “监听线程AcceptThread ” 以及 “ConnectedThread” 进行控制。

以下是AcceptThread主体,觉得这个也是一般线程定义的模板,构造方法,run(),  收尾工作(关闭socket)。

AcceptThread需要在BluetoothClient被构造以后立刻初始化,因为如果你的系统有蓝牙,而且能被找,就是一个server,所以AcceptThread的初始化将会在BluetoothClient的构造函数被定义。

流程: 获得ServerSocket-> 用ServerSocket监听->有接入则返回->(只允许一个接入,如果已有通信进行则关闭该socket连接)->进入ConnectedThread进行流数据处理。(如下关键程序所示:)

tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
socket = mmServerSocket.accept();
startConnectedThread(socket,socket.getDevice())
mmServerSocket.close();

private class AcceptThread extends Thread {
        // The local server socket
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;

            // Create a new listening server socket
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
            }
            mmServerSocket = tmp;
        }

        public void run() {
            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
            setName("AcceptThread");
            BluetoothSocket socket = null;

            // Listen to the server socket if we're not connected
            while (mState != STATE_CONNECTED) {
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                }

                // If a connection was accepted
                if (socket != null) {
                    synchronized (BluetoothChatService.this) {
                        switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            // Situation normal. Start the connected thread.
                            connected(socket, socket.getRemoteDevice());
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            // Either not ready or already connected. Terminate new socket.
                            try {
                                socket.close();
                            } catch (IOException e) {
                                Log.e(TAG, "Could not close unwanted socket", e);
                            }
                            break;
                        }
                    }
                }
            }
            if (D) Log.i(TAG, "END mAcceptThread");
        }

        public void cancel() {
            if (D) Log.d(TAG, "cancel " + this);
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }
2.1.2 Client
Client端的主要是由 “连接线程ConnectThread ” 以及 “ConnectedThread” 进行控制。

以下是ConnectThread主体,again->构造方法,run(),  收尾工作(关闭socket)。

AcceptThread需要在BluetoothClient被构造以后立刻初始化,因为如果你的系统有蓝牙,而且能被找,就是一个server,所以AcceptThread的初始化将会在BluetoothClient的构造函数被定义。

流程: 获得ServerSocket-> 用ServerSocket监听->有接入则返回->(只允许一个接入,如果已有通信进行则关闭该socket连接)->进入ConnectedThread进行流数据处理。(如下关键程序所示:)

tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
connect(从找到的机器里获得的Device名);
startConnectedThread(socket,socket.getDevice())
mmSocket.close(); 

private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");

            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                // Start the service over to restart listening mode
                BluetoothChatService.this.start();
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
2.1.3 ConnectedThread

这个进程是最关键的一个,处理数据流的节点。 通过得到的socket分别抓取对应的输入输出流, inputstream 以及outputstream。

public class ConnectedThread{
    BluetoothSocket mSocket;
    InputStream mInputStream;
    OutputStream mOutputStream;

    public ConnectedThread(BluetoothSocket socket){
        mSocket=socket;
        InputStream tmpIn=null;
        OutputStream tmpOut=null;

        try{
           tmpIn=mSocket.getInputStream();  //获得输入流
           tmpOut=mSocket.geOutputStream(); //获得输出流
        }catch(IOException e){
          e.printStackTrace();
        }
    }

    public void run(){
        byte[] buffer=new byte[2048];
          while(true){
            try{
                  bytes=mInputStream.read(buffer);
                  mHandler.ObtainMessage(Bluetooth.MessageRead, bytes,-1, buffer). SendToTarget();
            }catch(IOException e){
                connectionLost();
                break;
            }
        }
    }

    public void write(byte[] buffer){
        try{
            mOutputStream.write(buffer);
        }catch(IOException e){
              connectionLost();
              break;
        }
    }

    public void cancel(){

       try{
         mSocket.close();  
       }catch(IOException e){
         e.printStackTrace();
       }
    }
}


我给大家提供一个相当稳定的数据流处理方法, 非常适合于传输的信息是有通信协议的,无论是通过XML 或者webservice类似或者是自己定义的rules用来区分不同的数据段,都很适合。

我用xml做个例子,已知结构如下:

<cyk>
   <label1/>
   <label2/>
</cyk>

那么要处理这个xml文件就应该等他全部接收到以后才能处理。那如何才能判断并且做到呢? 如下(替代掉bytes=mInputStream.read(buffer);)。

byte[] buffer=new byte(2048);
StringBuilder curMsg= new StringBuilder();
while (true) {
    try {
        //Ensure the whole xml file is received completedly
        bytes = mmInStream.read(buffer);                    
        curMsg.append(new String(buffer, 0, bytes));                    
        int endIdx = curMsg.indexOf(end);
        
        if(D) Log.e(TAG,"endIdx: "+endIdx);
        if (endIdx != -1) {                    
            String fullMessage = curMsg.substring(0, endIdx + end.length());                        
            if(D) Log.e(TAG,fullMessage);                        
            curMsg.delete(0, endIdx + end.length());  
        }                  
        XMLProcessing(fullMessage);

    }catch (IOException e) {                   
        if(D) Log.e(TAG, "disconnected", e);                   
        connectionLost();                   
        break;
    }
我使用了一段时间,觉得非常的elegant,可以尝试使用。


3 Mobile Phone Bluetooth With Other Bluetooth Module (Bluetooth Hardware)

不知道有没有朋友开发手机和其他蓝牙硬件进行配对,不是手机和手机简单的软件方法就可以解决。解决的途径:
  • 蓝牙硬件——javax.Comm 或者 RXTXComm API & AT command
  • 手机端 ——BluetoothSocket Programming
  • UUID配对问题。

3.1 UUID配对问题

手机连接蓝牙模块比较特殊:

首先查一下蓝牙模块的说明书,查找哪一个UUID是进行串口通行的Serial Connection。我使用的是“1101”。

然后就挺无奈的,因为我们看到的Android的UUID 都是“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”的模式,哪有“1101” 4位的。

最后从网上得到了SPP协议的Android专用串口连接UUID:"0000xxxx-0000-1000-8000-00805F9B34F" 的。

这里的“xxxx”就是蓝牙模块的串口号。这个首要问题解决了后面就方便了。

UUID=0000xxxx-0000-1000-8000-00805F9B34F;

device.createRfcommSocketToServiceRecord(UUID); client(手机)连接Server(蓝牙模块)。

3.2 Javax.Comm 或者 RXTXComm API & AT command


-------------------------------------------------------------------------------------------------------------------------

English Version

Content:


1. The example from android

 

   1.1 The problems lie in the example


2. How can we modify the code and make the connection to be robust


    2.1  AT Command.


    2.2  What the hell with the UUID in android and Bluetoot Module?



3. Incoming data is XML how to lead the date to XMLParser.

-------------------------------------------------------------------------------------------------------------------------

2.2  What the hell with the UUID in android and Bluetoot Module?

First in my application, I need to use the android phone to connect an Bluetooth module which is integrated on a PCB board. The Bluetooth module I used is provided by Stollmann.


The following is an email I wrote to technical support of stollmann.

The email described how to manage a connection from android phone to the bluetooth module via AT Command.

 

For spp connection (Bluetooth module launchs the scan, theconnection and so on):

The uuid of App in android should be like:    0000xxxx-0000-1000-8000-00805F9B34FB, where"xxxx" is the App uuid displayed via AT command : at**binqserv d0x.

But I believe "CAFE" has been reserved only forapple products, so when I try to use "CAFE" the  AT Command : atd d0x uCAFE. The connectionalways failed.

So I suggest that "CAFE" can also be used byother products not only for apples :)

 

For spp connection(Android phone launched the scan, theconnection).

The java code in android for handling a bluetoothconnection should be like this:

device.createRfcommSocketToServiceRecord(UUID);

Still it's the problem of UUID.

The service in BlueEva+P25/G2 that I should attach is theservice with UUID"1101"; So the UUID should be like:00001101-0000-1000-8000-00805F9B34FB.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值