关于Android蓝牙开发的一些经验之谈

来源:互联网 发布:sql添加默认值 编辑:程序博客网 时间:2024/05/19 13:16
            最近在开发一款基于蓝牙的手机远程控制软件,程序没有分开服务端和客户端进行开发,而是把它们都集合在一起,在安装该软件的两台手机后可以通过搜索发现后进行匹配连接,然后通过主控端通过发送命令控制子机进行一些操作,其中功能包括控制打电话,发短信,打开网页,照相后返回照片,获取联系人,基本的远程文件管理和下载,打开关闭闪光灯(如果有),文字聊天和语音聊天。从功能所用的数据类型来看,需要传输的数据类型有字符串,二进制数据,语音数据等等。从现有的Android蓝牙支持来看,Google对于蓝牙的支持相对比较少,虽然它支持像socket之类的数据传输(bluetoothSocket),但不像一般一socket一样可以支持并发监听和发送接收操作。此文主要介绍如何在只有一个数据连接的基础上对不同数据的发送和接收并处理,结合本人开发的小软件谈谈本人的一些看法。

            在蓝牙连接建立起来后(具体可以参考其他蓝牙的相关开发资料,不再赘述),我通过新开一个线程去监听蓝牙端口是否有数据写入,为方便控制数据读入写出的方便,我没有直接采用BluetoothSocket的默认的InputStream和 OutputStream,而是以此为基础上加装成为对象输入输出流:(ObjectInputStream/OutputStream),具体代码如下:

socket=mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);//mAdapter为BluetoothAdapter类型socket = mmServerSocket.accept();//等待连接,成功返回一个bluetoothsocket,mmServerSocket为BluetoothSocket类型InputStream tmpIn = socket.getInputStream();OutputStream tmpOut = socket.getOutputStream();//获取输入输出流

新建一个新线程监听端口:

public void run() {while (true) {try {ObjectInputStream input = new ObjectInputStream(mmInStream);// 等待数据传入Object temp = input.readObject();//从流中读入一个对象数据} catch (IOException e) {} catch (Exception e) {e.printStackTrace();}}}}
定义写出函数:

public void write(Object buffer) {try {ObjectOutputStream input = new ObjectOutputStream(mmOutStream);input.writeObject(buffer);} catch (IOException e) {}}

            这样做的好处是我们可以发送和接收更多类型的数据而不仅仅是单一的数据了,我们要做的就是根据我们的需要把传送过来的数据对象进行正确的转换。于是,对于远程操作这一类的软件来说就可以有一个比较通用的模式来应用了:

1.  两台手机在连接匹配后同时进入到同一个接收和发送模式(可以理解为发收的数据类型),我们默认为字符串类型;

2.  服务机发送一个文本命令,并根据命令的类型进行本地判断,改变本地收发的模式(如果需要),以便接收子机返回的数据;

3.  子机接收命令后进行命令解释,根据命令的类型选择不同的操作,如果需要的话也可更改收发的模式,如要返回数据,就将该数据打包成一个对象类型(Object)然后通过write(Object buffer)将数据送出;

4.  服务机接收到返回的数据,线程完整地接收到一个对象之后将数据返回给主线程,主线程根据当前的模式类型选择恰当的操作对数据进行处理,如要继续进行此类的通信,继续发送对象

5.  重复3,4,直到一方退出该模式,双方改变模式为字符串模式,子机再次等待接收另一命令。


             这是一个大概的思路了,下面我以远程文件查看和传输为例子讲一下具体的操作。

            由于发收类型已经设置为对象了,字符串模式实际上收发的也是对象类型的。


//服务机通过发送一个命令”file”:String command=”file”;Write((Object)command);//调用write方法将命令送出setState(1);//自定方法,将当前的模式转换为”file”的模式,这个方法体和参数都可以自定,总知只要可以在本类中全局调用可用来标识当前类的状态就可以了

             子机检测到有数据传入,以一个对象为单位接收好数据(查看以上线程的定义),然后可通过Handler等将收到的数据Object temp返回给主线程

mHandler.obtainMessage(MESSAGE_READ, -1, -1, temp).sendToTarget();重载Handler的p handleMessage(Message msg)方法,如:private final Handler mHandler = new Handler() {public void handleMessage(Message msg) {// receive a message from// sendMessage(Message)switch (msg.what) {                        String command=(String)msg.obj;……//其中msg.what就是用obtainMessage返回的时候的第一个参数,int类型(如我用的MESSAGE_READ),最后一个参数就是要返回的对象(关于obtainMessage可以参考SDK文档).msg.obj就是返回的temp对象,通过强制类型转换把它还原成字符串类型,然后对进行命令进行解释选择相关操作就可以了,这里我们以获取SD卡目录为例子if(command.equals(“file”))//判断方法也是多样的,视你发送的内容而定,我是采用双方统一的命令格式以减少数据传输的,此处判断仅为方便{setState(BTService.MESSAGE_TYPE_OBJECT_FILE_CLIENT);//改变模式,形式自定getFile getfile = new getFile();ArrayList<File> listfile = new ArrayList<File>();listfile = getfile.fileGet();//获取SD根目录列表listOfFiles listoffiles = new listOfFiles();listoffiles.setList(listfile);//将数据以listOfFiles对象类型封装write((Object) listoffiles);//发送封装好的对象}}};

             其中getFile和listOfFiles如下:

//getFile
package com.home.functions.file;import java.io.File;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.os.Environment;import android.util.Log;import android.widget.Toast;//use to package filepublic class getFile {private List<File> fileList = null;private String SdState = null;private File SDFile = null;private File sdPath = null;private String currentPath = null;public List<File> fileGet() {fileList = new ArrayList<File>();SdState = Environment.getExternalStorageState();if (SdState.equals(Environment.MEDIA_MOUNTED)) {try {SDFile = Environment.getExternalStorageDirectory();sdPath = new File(SDFile.getAbsolutePath());// get SD card root// pathgetName();} catch (Exception e) {Log.e("file", "error");return null;}}currentPath = SDFile.getAbsolutePath();return fileList;}public List<File> fileList(String path) {Log.d("path", currentPath);sdPath = new File(currentPath + "/" + path);if (sdPath.isFile()) {Log.v("isfile", "isfile");} else {fileList.clear();currentPath = currentPath + "/" + path;getName();}return fileList;}public List<File> previous(){if(!currentPath.equals("/mnt")){currentPath=currentPath.substring(0, currentPath.lastIndexOf("/"));Log.i("currentpath",currentPath);sdPath=new File(currentPath);fileList.clear();getName();}return fileList;}private void getName() {if (sdPath.listFiles().length > 0) { // if has any filefor (File file : sdPath.listFiles()) {fileList.add(file);}}}public String getPath(){return currentPath;}public File getCurrentFile(String filename){//filename=filename.substring(1,filename.length());File file=new File(currentPath+"/"+filename);Log.e("filepath",file.getAbsolutePath());return file;}}

//listOfFile
package com.home.functions.file;import java.io.File;import java.io.Serializable;import java.util.List;public class listOfFiles implements Serializable{List<File> filelist=null;public void setList(List<File> list){filelist=list;}public List<File> getList(){return filelist;}}


             服务机因已经在发送命令的同时改变 变了自身的接收模式,在服务机接收到数据后(接收和解释过程同子机)通过判断现在模式的类型,发现是文件管理的对象模式,于是在接收到数据并返回到主线程后对数据进行还原:

        listOfFiles listoffiles = (listOfFiles) msg.obj;ArrayList<File> listfile = listoffiles.getList();

             就得到了一个装着子机SD卡目录的ArrayList了,此时就可以对其进行后续操作。

             再深入的话,文件传输也可以进行同样的操作,子机接收到命令后进入文件发送模式,通过在本地读取一个文字的一定长度(如20KB)为一个单位,将其封装成一个对象,然后发送,服务机因已经在发送请求时已进入文件接收模式,在接收到对象后对其进行解释操作,然后写入到一个文件当中,子机重复读取文件打包发送,服务器重复读取流数据然后追加写入文件直到文件到尾,文件就“下载”下来了:


//发送:int r = 0;byte[] bytes = new byte[21504];// 本地缓存大小,一次读取的文件字节数,根据机质的不同调大可以改善传输性能BufferedInputStream input;try {input = new BufferedInputStream(new FileInputStream(file));//文件输入流while ((r = input.read(bytes)) != -1) {filecache fileCache = new filecache();fileCache.setFile(bytes);//封装数据fileCache.setFileSize(r);//设置读取的字节数据mService.write((Object) fileCache);//发出对象}input.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}String endCommand = "file_end";mService.write((Object) endCommand);//发送结束标志

//接收(在Handler的handlerMessage方法中处理)String judgeCommand = (String) msg.obj.toString();if (judgeCommand.equals("file_end")) {    setState(MESSAGE_TYPE_OBJECT_FILE_SERVER);//设置模式为文件管理模式,自定,作标识作用else {//非文件结尾,写入    filecache fileCache = (filecache) msg.obj;//还原数据    byte[] bytes = fileCache.getFile();    BufferedOutputStream output;            try {  output = new BufferedOutputStream(          new FileOutputStream(newfile, true));//追加写入,newfile为要写入的文件,File类型,自定  output.write(bytes, 0, fileCache.getFileSize());//写到磁盘中  output.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

其中filecache的定义如下:

package com.home.functions.file;import java.io.Serializable;public class filecache implements Serializable{/** *  */private static final long serialVersionUID = 1L;byte[] bytes = new byte[21504];//21KBpublic int size;//byte[] bytes=new byte[80240];//here?int r;public void setFile(byte[] source){bytes=source;}public byte[] getFile(){return bytes;}public void setFileSize(int Filesize){size=Filesize;}public int getFileSize(){return size;}}


             这样重复这两个过程,当文件读取发送完毕,文件也就从子机传送到服务机当中的,当然以上可以在独立的线程中进行,只要通过Handler传递数据就可以了。

             对于不同类型的数据,就可以考虑如上的一种设计,即设计一个对象然后将真正需要发送的数据包含在那个对象当中(考虑到发送数据的多样性,如果数据类型只是一种很单纯的基本类型就不用这种方法了,直接转换成Object然后发就可以了)然后发送接收该对象就可以了,那个对象要序列化,即实现

Serializable
接口。只要注意好模式的转换的时间,无论通过蓝牙什么时候发送什么都是可以的。


原创粉丝点击