所谓智能蓝牙(Smart Bluetooth)防丢器,是采用蓝牙技术专门为智能手机设计的防丢器。其工作原理主要是通过距离变化来判断物品是否还控制在你的安全范围。主要适用于手机、钱包、钥匙、行李等贵重物品的防丢,也可用于防止儿童或宠物的走失 。[ 请看正版请百度:beautifulzzzz(看楼主博客园官方博客,享高质量生活)嘻嘻!!! ]
图 1-1 蓝牙防丢器应用领域
目前比较成熟的产品一般是采用蓝牙4.0技术,具有低功耗、双向防丢、自动报警等优点。虽然市场上该类产品种类繁多、层出不穷,但其核心构成一般包括:蓝牙4.0芯片、蓝牙芯片辅助电路、蓝牙天线、蜂鸣器、开关、电源等。
图 2-1 蓝牙防丢器构成
由于这是第一个智能硬件DIY篇,楼主可不想一下子把大家给吓倒了。所以这里我们先用一个相对简单但常用的蓝牙模块HC-05/HC-06进行DIY 。如下图该模块把天线、滤波电路、Soc、晶振都集成在了一起,当我们用的时候只要关注1、2、12、13、24、26这几个引脚就能实现比较完整的蓝牙通信功能,这样就为我们制作蓝牙防丢器节省了很多挑选元件、设计电路、焊接制板的功夫,是不是超赞呀?
图 3-1 蓝牙模块1
其实还有更赞的呢!由于考虑到很多读者在硬件方面还都是新手,初次拿到邮票边缘式引脚的模块会焊接不好,于是楼主又找到了一款封装更好的蓝牙模块(其实就是把上面的模块加一个托,然后将VCC/GND/TXD/RXD四个关键的引脚引出)。当我们只是想把蓝牙模块作为标签时,只要在VCC和GND之间给它加上相应的电压就行了;当想用它进行无线数据传输时,这时TXD和RXD两个引脚就起作用了。
图 3-2 蓝牙模块2
上面说了这么多了,那么我们的蓝牙防丢器的设计方案到底是什么样的呢?简单起见,咱么仅仅实现通过距离变化来判断物品是否还控制在你的安全范围内的具有核心功能的防丢器,对于节能功能、双向报警功能甚至是自拍功能咱们就先不考虑了。哈哈,可能说到这里大家还是对咱们要做的防丢器一点想法都没有,其实通过上面的铺垫楼主有信心大家可以在一分钟之内知道怎么完成它!
图 4-1 简易蓝牙防丢器
相信很多看完上面图片同学会恍然大悟——不是嘛,只要将蓝牙模块接上相应的电源模块就能够做成一个简单的可以发出蓝牙信号的防丢器了!对的,由于我们没有加入复杂的通信功能,所以我们仅仅把蓝牙模块通上电做成一个蓝牙标签就可以了。但是大家不要高兴太早,虽然是第一个DIY,楼主也不会这么水吧~(没发现上面图片中手机屏幕里的应用还是一片空白吗?哈哈)。
上面制作蓝牙防丢器的硬件部分让大家觉得没什么挑战性,那么接下来的东西可就有一定难度了!记得楼主当时学安卓蓝牙开发的时候费了好大力气的。这里楼主强烈建议大家着手了解安卓某个功能的应用时最好去安卓开发者社区,但是随着Google被屏Android Developer也不能被访问了。虽然楼主在MIT网站上又找到了一个类似的网页,但是访问速度不是那么流畅,为了方便,LZ挑出了和本节相关的知识帮助大家理解和运用。
图 5-1 Android Developer页面
安卓平台支持蓝牙协议栈,允许一台设备和其他设备进行无线数据交换,当大家想使用蓝牙时可以通过调用相应的API实现功能。这些API提供的主要功能有:
△ 扫描搜索其他蓝牙设备(Scan for other Bluetooth devices)
△ 查询本地蓝牙适配器配对的蓝牙设备(Query the local Bluetooth adapter for paired Bluetooth devices)
△ 建立RFCOMM通道(Establish RFCOMM channels)
△ 连接其他设备(Connect to other devices through service discovery)
△ 与其他设备进行数据交换(Transfer data to and from other devices)
△ 管理多组连接(Manage multiple connections)
当准备在应用程序中使用蓝牙时,首先需要在manifest文件中包含BLUETOOTH和BLUETOOTH_ADMIN权限:
<uses-permission android:name= "android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name= "android.permission.BLUETOOTH" />
接着涉及蓝牙的操作主要有:1、开启蓝牙 2、关闭蓝牙 3、能被搜到 4、获取配对设备 5、传输数据。由于本节只涉及到搜索周边设备,所以配对并传输数据暂时不介绍。
要想通过编程操作蓝牙设备,首先我们得了解一下有可供我们使用的相关类及其成员函数有哪些,如下图:蓝牙设备包括本地设备和远程设备,本地设备对应的类为BluetoothAdapter,远程设备对应的类为BluetoothDevice,两者的成员函数基本相同,楼主将在接下来详细讲解。
图 7-1 蓝牙设备相关函数
我们要做带有蓝牙的应用,首先要保证用户的本地蓝牙设备是打开的,否则什么都不管直接使用搜索、连接、通信等函数肯定会得到和预期不一样的效果。但是有时候用户为了节省电池电量会把蓝牙关闭,那我们该怎么办呢?
其实,遇到这种情况大家也不用担心!请看下面的解决方案:其中第1行调用BluetoothAdapter的getDefaultAdapter()方法获得本地蓝牙设备,接着调用isEnabled()判断本地蓝牙设备是否被打开,如果被打开了就执行接下来的操作,否则可以发送Intent消息,请求打开本地蓝牙设备,接着待用户打开蓝牙后再进入接下来的操作。
1 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 2 if (!mBluetoothAdapter.isEnabled()) { 3 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 4 startActivityForResult(enableIntent, REQUEST_ENABLE_BT); 5 } else { 6 nextOperation(); 7 }
这时候有些同学可能要吐槽楼主了“你上面发送、请求、接着、然后说的挺轻巧,我怎么知道我发送完Intent消息后系统到底干了什么?用户又如何打开蓝牙的?应用程序又在哪里等待用户完成打开蓝牙事件,然后在哪里执行nextOperation()函数的?”哈哈,楼主知道大家动手心切啦!下面将给大家详细介绍这一过程。
想解答大家的这些问题还得看上面代码:其中第4行startActIvityForResult会启动一个系统Preference Activity并将ACTION_REQUEST_ENABLE静态常量作为其动作字符串,得到的Preference Activity如下图:
该Activity提醒用户是否授予权限打开本地蓝牙设备,当用户点击“是”或者“否”的时候,该Activity将会关闭。我们可以使用onActivityResult处理程序中返回的结果代码参数来确定是否操作成功。
正如上面介绍,当用户点击“是”授予蓝牙权限请求后,确认请求的Activity会关闭,在下面的函数中将会收到此操作的消息,这样我们就能很潇洒的在第4行安全的进入接下来的操作了。
1 protected void onActivityResult(int requestCode,int resultCode,Intent data){ 2 if(requestCode==ENABLE_BLUETOOTH){ 3 if(resultCode==RESULT_OK){ 4 nextOperation(); 5 } 6 } 7 }
7_2 搜索周边蓝牙设备
上面解决了蓝牙设备打开问题,接下来我们就要尝试调用相关函数搜索周边的蓝牙设备,这样我们就能知道我们制作的蓝牙防丢器是否在我们周边了(是不是很兴奋呢?)。
这里我们要用到BluetoothAdapter的成员函数startDiscovery,该方法可以执行一个异步方式获得周边蓝牙设备,因为是一个异步的方法所以我们不需要考虑线程被阻塞问题,整个过程大约需要12秒时间。这时我们可以注册一个BroadcastReceiver 对象来接收查找到的蓝牙设备信息,我们通过Filter来过滤ACTION_FOUND这个Intent动作以获取每个远程设备的详细信息,过滤ACTION_DISCOVERY_FINISHED这个Intent动作以获取整个蓝牙搜索过程结束的信息。
1 // Register for broadcasts when a device is discovered 2 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 3 this.registerReceiver(mReceiver, filter); 4 // Register for broadcasts when discovery has finished 5 filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 6 this.registerReceiver(mReceiver, filter);
上面讲的可能有点专业,下面俺用一种简单非专业的方式向大家介绍一下:每次我们想编程搜索周边的蓝牙设备时,只要简单调用BluetoothAdapter的成员函数startDiscovery就可以了。所谓的异步方法大家可以这样理解——start方法就好像一个总司令告诉情报搜查员去搜查情报,然后自己继续做自己的事,情报员去收集各种情报;这里的filter就好像总司令告诉情报搜查员,我只要ACTION_FOUND和ACTION_DISCOVERY_FINISHED信息;那么这里的mReceiver就是总司令指定的情报搜查员了。
接下来就让咱们神奇的情报搜查员登场!如下,相信大家一看就明白了,咱们的情报搜查员会一丝不苟地将总司令下达的任务执行。这样当他收到FOUND动作信息时就用第6~8行的方法将每个发现的蓝牙设备的名字和地址存储进一个Vector中,这样等自己完成任务时,就能告诉总司令任务完成,所有周边蓝牙情报都在xxx向量中(嘻嘻,多么让领导喜欢的员工呀!)。
1 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 2 @Override 3 public void onReceive(Context context, Intent intent) { 4 String action = intent.getAction(); 5 if (BluetoothDevice.ACTION_FOUND.equals(action)) { 6 BluetoothDevice device = 7 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 8 mDevicesVector.add(device.getName() + "/n" + device.getAddress()); 9 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 10 //... 11 } 12 } 13 };
7_3 获取蓝牙设备信号强度
到目前想必很多人已经能够一气呵成自己的简单蓝牙防丢器了,因为大家已经掌握了硬件部分的设计方法、拥有软件蓝牙开发打开和搜索周边蓝牙的编程技巧。但是如果心急的同学只用前面介绍的一点小知识的话,肯定设计的小东西很不尽人意(自己都不好意思给自己及格)。是不是出现了蓝牙防丢器放到很远很远时应用程序才会报告东西已丢?如果是这样请耐心地看完下面的介绍(也许能给你更多的启发(⊙o⊙)哦)。
想必大家也想到了问题所在——距离没控制好!如果我们能够知道蓝牙防丢器距离我们手机的距离那就好了,这样我们就能设定一个范围值,当它超出这个范围时手机就发出丢失警告,这样就不会出现防丢器要放很远才能被手机察觉已丢失的问题了。要解决这个问题就要向大家介绍一下无线传感器网络中的RSSI了!
RSSI全名Received Signal Strength Indication,翻译过来是接收的信号强度指示。在我们的经验中一般离得越近信号越强,所以通过这个RSSI能够大致估计接收点与发射点之间的距离。而在无线传感器网络中经常会设置多个固定的发射源(也是所谓的标签),然后根据一定的算法来确定移动目标的空间位置信息。例如下图左分别在A、B、C三点放置三个发射功率相同的信号源(标签),这样移动点D通过收集A、B、C三点的RSSI并估计距离r A 、r B 、r C ,由于这个距离并不是完全精准,所以三个圆并不一定交于一点,大多数情况是下面交于一个公共区域的情况,而移动点D的当前位置很有可能在该区域。
图 7-2 蓝牙标签定位
我们这里只要简单地用到通过RSSI估计手机和防丢器之间的距离就行了,上面的定位技术相信大家也能举一反三轻松搞定(/(^o^)/~其实没那么简单,哈哈)。那么在安卓编程时如何获得RSSI呢?其实并不难,我们可以利用和获取周边蓝牙设备的名称和地址类似的方法获取RSSI:
1 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 2 mDevicesVector.add(device.getName() + "/n" + device.getAddress()); 3 short rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI); 4 mRSSIVector.add(rssi);
1) 使用Eclipse创建一个安卓项目,命名为first_test,并将Activity Name命名为UI_Main,Layout Name命名为ui_main(如图8-1所示)。
图8-1 新建安卓项目
2) 展开res文件夹下的layout文件夹,双击ui_main.xml文件,选择xml编辑模式,并将其代码改为:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 7 <SurfaceView 8 android:id="@+id/surfaceView" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:layout_weight="0.75" /> 12 13 <Button 14 android:id="@+id/button_add" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:text="添加蓝牙防丢器" /> 18 19 <Button 20 android:id="@+id/button_start" 21 android:layout_width="match_parent" 22 android:layout_height="wrap_content" 23 android:text="开始防丢" /> 24 25 </LinearLayout>
操作说明 :这里修改的ui_main.xml为应用的主界面(如图8-2所示)。该页面采用LinearLayout布局模式,从上到下依次为用于动态显示蓝牙信号强弱的SurfaceView控件、用于添加防丢设备的按钮和用于开始防丢控制的按钮。
图 8-2 ui_main.xml效果
3) 右击layout,依次选择New|Android XML File新建安卓XML文件。
4) 将新建的Android XML File命名为ui_list,点击Finish按钮。接着同第二步将新建的ui_list.xml修改为:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 7 <ListView 8 android:id="@+id/listView1" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:layout_weight="1" > 12 </ListView> 13 14 <Button 15 android:id="@+id/button_search" 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 android:text="search" /> 19 20 <Button 21 android:id="@+id/button_ok" 22 android:layout_width="match_parent" 23 android:layout_height="wrap_content" 24 android:text="OK" /> 25 26 </LinearLayout>
操作说明 :这里新建的ui_list.xml为搜索并添加蓝牙防丢器界面。该界面同样采用LinearLayout布局模式,包含一个用于开始搜索的按钮、一个用于列出搜索结果的list和一个用于确认返回的OK按钮。
5) 右击src文件夹下的包名,依次选择New|Class命令(如图8-3所示)
图 8-3 新建类
6) 新建类文件命名为My_BTS,点击Finish按钮。
7) 将My_BTS.java文件修改为:
1 package com.example.first_test; 2 3 import java.util.Vector; 4 5 public class My_BTS { 6 public String mName; 7 public String mAddr; 8 public Vector<Short> mRSSIVector; 9 10 public My_BTS() { 11 mName = new String(); 12 mAddr = new String(); 13 mRSSIVector = new Vector<Short>(); 14 } 15 16 public My_BTS(String name, String addr) { 17 mName = name; 18 mAddr = addr; 19 mRSSIVector = new Vector<Short>(); 20 } 21 }
操作说明: 该类表示蓝牙防丢器。其中mName和mAddr分别表示蓝牙防丢器的名字和地址;mSSIVector用来存放一段时间检测到该蓝牙防丢器的RSSI值(之所以保留多组数据,是方便今后大家扩展)。
8) 采用同样的方法新建一个Func_Draw.java文件,并将文件修改为:
1 package com.example.first_test; 2 3 import java.util.Vector; 4 5 import android.graphics.Canvas; 6 import android.graphics.Color; 7 import android.graphics.Paint; 8 import android.graphics.Paint.Style; 9 import android.view.SurfaceHolder; 10 11 public class Func_Draw { 12 private static Vector<Paint> mPaint = new Vector<Paint>(); 13 public static Integer times = 0;// 防丢搜索次数 14 public static float Bei = 200;// 绘制图形时放大倍数 15 16 public static void initPaint() { 17 Paint paint0 = new Paint(); 18 paint0.setAntiAlias(true); 19 paint0.setStyle(Style.STROKE); 20 paint0.setColor(Color.RED); 21 mPaint.add(paint0); 22 Paint paint1 = new Paint(); 23 paint1.setAntiAlias(true); 24 paint1.setStyle(Style.STROKE); 25 paint1.setColor(Color.GREEN); 26 mPaint.add(paint1); 27 Paint paint2 = new Paint(); 28 paint2.setAntiAlias(true); 29 paint2.setStyle(Style.STROKE); 30 paint2.setColor(Color.BLUE); 31 mPaint.add(paint2); 32 Paint paint3 = new Paint(); 33 paint3.setAntiAlias(true); 34 paint3.setStyle(Style.STROKE); 35 paint3.setColor(Color.YELLOW); 36 mPaint.add(paint3); 37 Paint paint4 = new Paint(); 38 paint4.setAntiAlias(true); 39 paint4.setStyle(Style.STROKE); 40 paint4.setColor(Color.WHITE); 41 mPaint.add(paint4); 42 Paint paint5 = new Paint(); 43 paint5.setAntiAlias(true); 44 paint5.setStyle(Style.STROKE); 45 paint5.setColor(Color.LTGRAY); 46 mPaint.add(paint5); 47 Paint paint6 = new Paint(); 48 paint6.setAntiAlias(true); 49 paint6.setStyle(Style.STROKE); 50 paint6.setColor(Color.CYAN); 51 mPaint.add(paint6); 52 } 53 54 public static void draw(SurfaceHolder mHolder) { 55 Canvas canvas = mHolder.lockCanvas(); 56 canvas.drawRGB(0, 0, 0); 57 for (int i = 0; i < UI_Main.mBTSArrayList.size(); i++) { 58 boolean find = false; 59 short rssi = 0; 60 for (int j = 0; j < UI_Main.mFuncBT.mAddrVector.size(); j++) { 61 if (UI_Main.mBTSArrayList.get(i).mAddr 62 .equals(UI_Main.mFuncBT.mAddrVector.get(j))) { 63 find = true; 64 rssi = UI_Main.mFuncBT.mRSSIVector.get(j); 65 } 66 } 67 if (find == false) { 68 canvas.drawText( 69 times + ": NOT_FIND " 70 + UI_Main.mBTSArrayList.get(i).mName, 5, 71 i * 10 + 12, mPaint.get(i)); 72 } else { 73 float power = (float) ((Math.abs(rssi) - 59) / (10 * 2.0)); 74 float dis = (float) Math.pow(10, power); 75 76 canvas.drawText( 77 times + ": FIND " + UI_Main.mBTSArrayList.get(i).mName 78 + " dis: " + new Float(dis).toString() 79 + " rssi: " + rssi, 5, i * 10 + 12, 80 mPaint.get(i)); 81 canvas.drawCircle(canvas.getWidth() / 2, 82 canvas.getHeight() / 2, Bei * dis, mPaint.get(i));//画圆圈 83 } 84 } 85 times++; 86 mHolder.unlockCanvasAndPost(canvas);// 更新屏幕显示内容 87 UI_Main.mFuncBT.mRSSIVector.clear(); 88 UI_Main.mFuncBT.mNameVector.clear(); 89 UI_Main.mFuncBT.mAddrVector.clear(); 90 } 91 }
操作说明 :该类提供在SurfaceView上绘制功能。其中静态方法initPaint对画笔进行初始化,draw函数负责绘制。
draw函数的核心在于canvas绘图,canvas绘图的过程和我们在白纸上绘绘图的过程很像,如:
l 第55行锁定canvas相当于得到一张纸; l 第56行用RGB为0的颜色来刷新canvas相当于用橡皮擦把纸上原来的东西擦掉; l 第68和76行drawText相当于在纸的相应位置写文字; l 第81行drawCircle相当于在纸的相应位置绘制一个指定的圆; l 第86行的nlockCanvasAndPost相当于你把绘制好的作品拿出来展示给别人看;
正是因为canvas的加锁和解锁这一机制,才保证了绘制过程中屏幕正确地显示。
接着再来理解这里draw函数的功能:mBTSArrayList是一个My_BTS类型的数组,保存我们想要防丢的蓝牙防丢器设备的名称、地址等信息;mFuncBT是一个可以实时搜索周边蓝牙设备的一个对象,其静态变量mNameVector、mAddrVector、mRSSIVector保存着实时搜索结果;这样核心部分的功能便是通过两层循环遍历待防丢设备是否在本次搜索中,如果不在则显示“NOT_FIND”,如果在则由RSSI计算距离。(效果如图8-4)
图 8-4 找到蓝牙设备图
9) 新建一个Func_BT.java文件,并修改为:
1 package com.example.first_test; 2 3 import java.util.Vector; 4 5 import android.app.Activity; 6 import android.bluetooth.BluetoothAdapter; 7 import android.bluetooth.BluetoothDevice; 8 import android.content.BroadcastReceiver; 9 import android.content.Context; 10 import android.content.Intent; 11 import android.content.IntentFilter; 12 import android.os.Bundle; 13 import android.os.Handler; 14 import android.os.Message; 15 16 public class Func_BT { 17 private BluetoothAdapter mBtAdapter;// 蓝牙适配器 18 private static final int ENABLE_BLUETOOTH = 1; 19 // 分别用于存储设备名地址名称和RSSI的向量 20 public Vector<String> mNameVector; 21 public Vector<String> mAddrVector; 22 public Vector<Short> mRSSIVector; 23 24 private Handler myHandler; 25 private Activity activity; 26 27 public Func_BT(Activity activity, Handler myHandler) { 28 this.myHandler = myHandler; 29 this.activity = activity; 30 31 mNameVector = new Vector<String>();// 向量 32 mAddrVector = new Vector<String>(); 33 mRSSIVector = new Vector<Short>(); 34 35 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 36 activity.registerReceiver(mReceiver, filter); 37 filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 38 activity.registerReceiver(mReceiver, filter); 39 activity.registerReceiver(mReceiver, filter); 40 41 mBtAdapter = BluetoothAdapter.getDefaultAdapter(); 42 } 43 44 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 45 @Override 46 public void onReceive(Context context, Intent intent) { 47 String action = intent.getAction(); 48 if (BluetoothDevice.ACTION_FOUND.equals(action)) { 49 BluetoothDevice device = intent 50 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 51 short rssi = intent.getExtras().getShort( 52 BluetoothDevice.EXTRA_RSSI); 53 mNameVector.add(device.getName()); 54 mAddrVector.add(device.getAddress()); 55 mRSSIVector.add(rssi); 56 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED 57 .equals(action)) { 58 /*if (mNameVector.size() != 0) { 59 Message msg = new Message();// 消息 60 msg.what = 0x01;// 消息类别 61 myHandler.sendMessage(msg); 62 }*/ 63 } 64 } 65 }; 66 67 public void doDiscovery() { 68 if (mBtAdapter.isDiscovering()) { 69 mBtAdapter.cancelDiscovery(); 70 } 71 mBtAdapter.startDiscovery(); 72 new TimeLimitThread().start(); 73 } 74 75 public void openBT() { 76 // 如果没有打开则打开 77 if (!mBtAdapter.isEnabled()) { 78 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 79 activity.startActivityForResult(intent, ENABLE_BLUETOOTH); 80 } else { 81 doDiscovery(); 82 } 83 } 84 85 protected void onActivityResult(int requestCode, int resultCode, Intent data){ 86 if (requestCode == ENABLE_BLUETOOTH) { 87 if (resultCode == Activity.RESULT_OK) { 88 doDiscovery(); 89 } 90 } 91 } 92 93 public void setHandler(Handler myHandler) { 94 this.myHandler = myHandler; 95 } 96 97 public void setFunc_BT(Activity activity, Handler myHandler) { 98 this.myHandler = myHandler; 99 this.activity = activity; 100 } 101 102 class TimeLimitThread extends Thread{ 103 public void run() { 104 try { 105 sleep(3000); 106 if (mBtAdapter.isDiscovering()) { 107 mBtAdapter.cancelDiscovery(); 108 } 109 Message msg = new Message();// 消息 110 msg.what = 0x01;// 消息类别 111 myHandler.sendMessage(msg); 112 } catch (InterruptedException e) { 113 e.printStackTrace(); 114 } 115 } 116 } 117 }
操作说明 :该类用来实时搜索周边蓝牙设备,并把搜索结果保存在其静态成员变量mNameVector、mAddrVector、mRSSIVector中。此外这里还使用Handler用于向调用其搜索方法的Activity发送本次搜索完毕的消息。具体介绍如下:
l 第27到42行为构造函数,主要负责成员变量初始化、注册filter、获取本地蓝牙设备; l 第44到65行为BroadcastReceiver,主要负责异步搜索周边蓝牙设备的广播的接收; l 第67到73行为开始搜索周边蓝牙设备函数; l 第75到83行为带有检查本地蓝牙是否开启并能够发出开启请求的搜索周边蓝牙设备函数; l 第85到91行为接收用户是否授权打开蓝牙设备的Activity的结果函数; l 第102到116行是一个线程类主要用于限定搜索周边蓝牙设备的最长时间(默认为12s左右);
正是因为加了独立用于限定搜索时长的线程,才让搜索过程的时间长短便于我们控制,但是sleep的时间也不要设置的过小,否则会出现一个蓝牙设备也搜索不到的情况。(建议最好参照第七部分——“安卓蓝牙编程主要操作”来理解本部分的代码)
10) 修改UI_Main.java为:
1 package com.example.first_test; 2 3 import java.util.ArrayList; 4 import android.app.Activity; 5 import android.content.Intent; 6 import android.os.Bundle; 7 import android.os.Handler; 8 import android.os.Message; 9 import android.view.SurfaceHolder; 10 import android.view.SurfaceHolder.Callback; 11 import android.view.SurfaceView; 12 import android.view.View; 13 import android.view.View.OnClickListener; 14 import android.widget.Button; 15 16 public class UI_Main extends Activity implements Callback { 17 18 public Func_Draw mFuncDraw; 19 public SurfaceHolder mHolder; 20 public static Func_BT mFuncBT; 21 // 防丢设备 22 public static ArrayList<My_BTS> mBTSArrayList = new ArrayList<My_BTS>(); 23 24 // 消息句柄(线程里无法进行界面更新, 25 // 所以要把消息从线程里发送出来在消息句柄里进行处理) 26 public Handler myHandler = new Handler() { 27 @Override 28 public void handleMessage(Message msg) { 29 if (msg.what == 0x01) { 30 Func_Draw.draw(mHolder); 31 } 32 mFuncBT.doDiscovery(); 33 } 34 }; 35 36 @Override 37 protected void onCreate(Bundle savedInstanceState) { 38 super.onCreate(savedInstanceState); 39 setContentView(R.layout.ui_main); 40 41 Func_Draw.initPaint(); 42 43 SurfaceView mSurface = (SurfaceView) findViewById(R.id.surfaceView); 44 mHolder = mSurface.getHolder(); 45 mHolder.addCallback(this); 46 47 mFuncBT = new Func_BT(this, myHandler); 48 49 Button mButton1 = (Button) findViewById(R.id.button_start); 50 mButton1.setOnClickListener(new OnClickListener() { 51 @Override 52 public void onClick(View v) { 53 Func_Draw.times = 0; 54 mFuncBT.openBT(); 55 } 56 }); 57 58 Button mButton2 = (Button) findViewById(R.id.button_add); 59 mButton2.setOnClickListener(new OnClickListener() { 60 @Override 61 public void onClick(View v) { 62 startActivity(new Intent(UI_Main.this, UI_List.class)); 63 } 64 }); 65 } 66 67 @Override 68 public void surfaceCreated(SurfaceHolder holder) { 69 // TODO Auto-generated method stub 70 71 } 72 73 @Override 74 public void surfaceChanged(SurfaceHolder holder, int format, int width, 75 int height) { 76 // TODO Auto-generated method stub 77 78 } 79 80 @Override 81 public void surfaceDestroyed(SurfaceHolder holder) { 82 // TODO Auto-generated method stub 83 84 } 85 }
操作说明: 上面第2步时我们修改了ui_main.xml并做出应用程序主页面的效果,而该类则是其对应的逻辑实现。
nn 首先看第37到65行的onCreate函数:
l 第39行设置要显示的页面为ui_main.xml所展示的效果; l 第41行调用Func_Draw的静态initPaint()方法对画笔进行初始化; l 第43到45行负责获取并设置SurfaceView; l 第47行实例化一个Func_BT; l 第49到56行是给开始防丢按钮绑定一个监听器,一旦点击该按钮则执行onClick内代码; l 第58到65行是给添加蓝牙防丢器绑定一个监听器,一旦点击则启动另一个Activity;
nn 第24到34行实例化一个Handler用于接收Func_BT一次搜索结束时发回结束的消息。在这里一旦收到本次周边蓝牙设备搜索结束的消息就调用Func_Draw.draw(mHolder)进行绘制,然后继续调用mFuncBT.doDiscovery()实现周期性搜索/防丢。
nn 第67到84行是自动生成的,暂且不用管(也不要删除!)。
11) 新建一个UI_List.java文件,并修改代码为:
1 package com.example.first_test; 2 3 import java.util.ArrayList; 4 import android.annotation.SuppressLint; 5 import android.app.Activity; 6 import android.app.ProgressDialog; 7 import android.content.Intent; 8 import android.os.Bundle; 9 import android.os.Handler; 10 import android.os.Message; 11 import android.util.Log; 12 import android.view.View; 13 import android.view.View.OnClickListener; 14 import android.widget.ArrayAdapter; 15 import android.widget.Button; 16 import android.widget.ListView; 17 18 public class UI_List extends Activity { 19 20 private ArrayList<String> Items; 21 private ListView myListView; 22 private ArrayAdapter<String> aa; 23 private boolean getDIV; 24 25 @SuppressLint("InlinedApi") 26 protected void onCreate(Bundle savedInstanceState) { 27 super.onCreate(savedInstanceState); 28 setContentView(R.layout.ui_list); 29 30 UI_Main.mFuncBT.setFunc_BT(this, myHandler); 31 32 // 获取listview并设置为多选 33 myListView = (ListView) findViewById(R.id.listView1); 34 myListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 35 myListView.setTextFilterEnabled(true); 36 // 设置listview数组并绑定 37 Items = new ArrayList<String>(); 38 aa = new ArrayAdapter<String>(this, 39 android.R.layout.simple_list_item_checked, Items); 40 myListView.setAdapter(aa); 41 42 // 获取OK按钮,并遍历选择的设备,返回主Activity 43 Button myButton1 = (Button) findViewById(R.id.button_ok); 44 myButton1.setOnClickListener(new OnClickListener() { 45 @Override 46 public void onClick(View v) { 47 int num = 0; 48 for (int i = 0; i < myListView.getCount(); i++) { 49 if (myListView.isItemChecked(i)) { 50 String item = myListView.getItemAtPosition(i) 51 .toString(); 52 String name = item.substring(0, item.indexOf("/n")); 53 String addr = item.substring(item.indexOf("/n") + 1); 54 Log.i("UI_LIST", name + " " + addr); 55 UI_Main.mBTSArrayList 56 .add(num++, new My_BTS(name, addr)); 57 } 58 } 59 Intent mIntent = new Intent(UI_List.this, UI_Main.class); 60 mIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 61 startActivity(mIntent); 62 } 63 }); 64 // 获取Search按钮,设置监听事件 65 Button myButton2 = (Button) findViewById(R.id.button_search); 66 myButton2.setOnClickListener(new OnClickListener() { 67 @Override 68 public void onClick(View v) { 69 getDIV = false; 70 UI_Main.mFuncBT.openBT(); 71 final ProgressDialog dialog = ProgressDialog.show(UI_List.this, 72 "搜索蓝牙设备", "稍等一下~", true); 73 new Thread(new Runnable() { 74 public void run() { 75 while (getDIV == false); 76 dialog.dismiss(); 77 } 78 }).start(); 79 } 80 }); 81 } 82 83 // 消息句柄(线程里无法进行界面更新, 84 // 所以要把消息从线程里发送出来在消息句柄里进行处理) 85 public Handler myHandler = new Handler() { 86 @Override 87 public void handleMessage(Message msg) { 88 if (msg.what == 0x01) { 89 Items.clear(); 90 for (int i = 0; i < UI_Main.mFuncBT.mNameVector.size(); i++) { 91 Items.add(i, UI_Main.mFuncBT.mNameVector.get(i) + '/n' 92 + UI_Main.mFuncBT.mAddrVector.get(i)); 93 aa.notifyDataSetChanged();// 通知数据变化 94 } 95 getDIV = true; 96 UI_Main.mFuncBT.mRSSIVector.clear(); 97 UI_Main.mFuncBT.mNameVector.clear(); 98 UI_Main.mFuncBT.mAddrVector.clear(); 99 } 100 } 101 }; 102 }
操作说明 :这个类和ui_list.xml也是配套的。因此:
nn 在onCreate中:
l 第28行设置要显示的页面为ui_list.xml所展示的效果; l 第30行负责将周边蓝牙搜索对象的Handler设置为本Activity内的Handler; l 第32到40行是list相关; l 第42到80行是两个按钮点击事件监听相关;
nn 第85到101行的Handler则同UI_Main.java中的Handler类似负责接收周期性蓝牙搜索结束消息。在这里当接到蓝牙搜索结束的消息后是将搜索到的设备信息放入list中待用户选择。(没有再次调用蓝牙搜索)
nn 此外两个按钮监听中执行部分要特别说明下:
l 当点击OK按钮时程序将用户在list中选择的设备的信息存放在UI_Main.mBTSArrayList中,然后结束当前Activity并启动UI_Main所对应的Activity。 l 当点击search按钮时会启动周边蓝牙设备搜索并打开一个等待对话框,其中的Thread负责等待直到搜索完毕。
12) 最后(如图8-5)找到AndroidMainFest.xml文件修改为:
图 8-5 AnDroidManifest.xml文件
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.example.first_test" 4 android:versionCode="1" 5 android:versionName="1.0" > 6 7 <uses-sdk 8 android:minSdkVersion="8" 9 android:targetSdkVersion="19" /> 10 11 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 12 <uses-permission android:name="android.permission.BLUETOOTH" /> 13 14 <application 15 android:allowBackup="true" 16 android:icon="@drawable/ic_launcher" 17 android:label="@string/app_name" 18 android:theme="@style/AppTheme" > 19 <activity 20 android:name=".UI_Main" 21 android:label="@string/app_name" > 22 <intent-filter> 23 <action android:name="android.intent.action.MAIN" /> 24 <category android:name="android.intent.category.LAUNCHER" /> 25 </intent-filter> 26 </activity> 27 28 <activity 29 android:name=".UI_List" 30 android:label="@string/app_name" > 31 <intent-filter> 32 <action android:name="android.intent.action.UI_List" /> 33 <category android:name="android.intent.category.DEFAULT" /> 34 </intent-filter> 35 </activity> 36 37 </application> 38 39 40 </manifest>
操作说明 :主要是在11、12行添加蓝牙权限以及在28到35行添加一个activity。
到目前为止我们已经全部完工,下面是成果自评的时候啦(请参照下列条目给自己打分):
及格分70分,不够的要注意打好基础哦!
[请看正版请百度:beautifulzzzz(看楼主博客园官方博客,享高质量生活)嘻嘻!!!]
[如果有需要制作蓝牙防丢器或蓝牙室内定位的可以联系我哦~]