最近开发了一个蓝牙控制器App,用手机远程控制小球的运动。

包含了一些基础知识:多线程使用,页面UI数据更新,按钮事件,选择项功能,蓝牙功能(蓝牙打开,蓝牙搜索,蓝牙连接,蓝牙命令发送,蓝牙命令接收)。

开发环境为:Android Studio+Java,蓝牙模块HC-06(自发自收模式)。

实现代码

主Activity代码:

  1. package com.example.cxk.lightballcontroller;
  2.  
  3. import android.app.Activity;
  4. import android.bluetooth.BluetoothAdapter;
  5. import android.bluetooth.BluetoothDevice;
  6. import android.bluetooth.BluetoothSocket;
  7. import android.content.BroadcastReceiver;
  8. import android.content.Context;
  9. import android.content.Intent;
  10. import android.content.IntentFilter;
  11. import android.os.Bundle;
  12. import android.os.Handler;
  13. import android.os.Message;
  14. import android.view.Menu;
  15. import android.view.MenuItem;
  16. import android.view.View;
  17. import android.widget.AdapterView;
  18. import android.widget.ArrayAdapter;
  19. import android.widget.Button;
  20. import android.widget.CompoundButton;
  21. import android.widget.EditText;
  22. import android.widget.Spinner;
  23. import android.widget.Switch;
  24. import android.widget.TextView;
  25. import android.widget.Toast;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.OutputStream;
  29. import java.lang.ref.WeakReference;
  30. import java.util.ArrayList;
  31. import java.util.List;
  32. import java.util.Set;
  33. import java.util.UUID;
  34.  
  35. public class MainActivity extends Activity {
  36.  
  37. private Switch bluetoothSwitch;
  38. private Button bluetoothSearch;
  39. private Spinner bluetoothList;
  40. private Button bluetoothConnect;
  41. private TextView recvData;
  42. private EditText sendData;
  43. private Button bluetoothSend;
  44.  
  45. private Button upSend;
  46. private Button leftSend;
  47. private Button downSend;
  48. private Button rightSend;
  49.  
  50. private BluetoothAdapter bluetoothAdapter;
  51. private List<String> list = new ArrayList<String>();
  52. private ArrayAdapter<String> adapter;
  53. private String strMacAddress;
  54. private boolean booleanConnect = false;
  55. private ConnectedThread connectedThread;
  56. private MyHandler myHandler = new MyHandler(this);
  57.  
  58. @Override
  59. protected void onCreate(Bundle savedInstanceState) {
  60. super.onCreate(savedInstanceState);
  61. setContentView(R.layout.activity_main);
  62.  
  63. bluetoothSwitch = (Switch) findViewById(R.id.swtch);
  64. bluetoothSearch = (Button) findViewById(R.id.search);
  65. bluetoothList = (Spinner) findViewById(R.id.list);
  66. bluetoothConnect = (Button) findViewById(R.id.connect);
  67. recvData = (TextView) findViewById(R.id.recvdata);
  68. sendData = (EditText) findViewById(R.id.senddata);
  69. bluetoothSend = (Button) findViewById(R.id.send);
  70.  
  71. upSend = (Button)findViewById(R.id.BtnUp);
  72. leftSend = (Button)findViewById(R.id.BtnLeft);
  73. downSend = (Button)findViewById(R.id.BtnDown);
  74. rightSend = (Button)findViewById(R.id.BtnRight);
  75.  
  76. bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  77. if(bluetoothAdapter == null){
  78. //表明此手机不支持蓝牙
  79. Toast.makeText(MainActivity.this, "未发现蓝牙设备", Toast.LENGTH_SHORT).show();
  80. return;
  81. }
  82.  
  83. if (bluetoothAdapter.isEnabled()) {
  84. bluetoothSwitch.setChecked(true);
  85. }
  86.  
  87. adapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, list);
  88. adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  89. bluetoothList.setAdapter(adapter);
  90.  
  91. bluetoothList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  92. @Override
  93. public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
  94. strMacAddress = adapter.getItem(i);
  95. adapterView.setVisibility(View.VISIBLE);
  96. }
  97.  
  98. @Override
  99. public void onNothingSelected(AdapterView<?> adapterView) {
  100.  
  101. }
  102. });
  103.  
  104. bluetoothSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  105. @Override
  106. public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
  107. if (b) {
  108. if (!bluetoothAdapter.isEnabled()) { //蓝牙未开启,则开启蓝牙
  109. Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  110. startActivity(enableIntent);
  111. } else {
  112. Toast.makeText(MainActivity.this, "蓝牙已开启", Toast.LENGTH_SHORT).show();
  113. }
  114. } else {
  115. bluetoothAdapter.disable();
  116. Toast.makeText(MainActivity.this, "蓝牙已关闭", Toast.LENGTH_SHORT).show();
  117. }
  118. }
  119. });
  120.  
  121. bluetoothSearch.setOnClickListener(new View.OnClickListener() {
  122. @Override
  123. public void onClick(View view) {
  124. if (bluetoothAdapter == null) {
  125. Toast.makeText(MainActivity.this, "未发现蓝牙设备", Toast.LENGTH_SHORT).show();
  126. return;
  127. } else if (!bluetoothAdapter.isEnabled()) {
  128. Toast.makeText(MainActivity.this, "蓝牙设备未开启", Toast.LENGTH_SHORT).show();
  129. }
  130.  
  131. Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
  132. if (pairedDevices.size() > 0) {
  133. for (BluetoothDevice device : pairedDevices) {
  134. adapter.remove(device.getAddress());
  135. adapter.add(device.getAddress());
  136. }
  137. } else {
  138. //注册,当一个设备被发现时调用mReceive
  139. IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
  140. registerReceiver(mReceiver, filter);
  141. }
  142. }
  143. });
  144.  
  145. bluetoothConnect.setOnClickListener(new View.OnClickListener() {
  146. @Override
  147. public void onClick(View view) {
  148. if (strMacAddress == null) {
  149. Toast.makeText(MainActivity.this, "请先搜索设备", Toast.LENGTH_SHORT).show();
  150. } else {
  151. BluetoothDevice device = bluetoothAdapter.getRemoteDevice(strMacAddress);
  152. ConnectThread connectThread = new ConnectThread(device);
  153. connectThread.start();
  154. }
  155. }
  156. });
  157.  
  158. bluetoothSend.setOnClickListener(new View.OnClickListener() {
  159. @Override
  160. public void onClick(View view) {
  161. if (booleanConnect == false) {
  162. Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
  163. } else {
  164. String strSendData = new String(sendData.getText().toString());
  165. connectedThread.write(strSendData.getBytes());
  166. sendData.setText("");
  167. }
  168. }
  169. });
  170.  
  171. upSend.setOnClickListener(new View.OnClickListener() {
  172. @Override
  173. public void onClick(View view) {
  174. if (booleanConnect == false) {
  175. Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
  176. } else {
  177. String str = "ONA\n";
  178. String strSendData = new String(str);
  179. connectedThread.write(strSendData.getBytes());
  180. sendData.setText("");
  181. }
  182. }
  183. });
  184.  
  185. downSend.setOnClickListener(new View.OnClickListener() {
  186. @Override
  187. public void onClick(View view) {
  188. if (booleanConnect == false) {
  189. Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
  190. } else {
  191. String str = "ONB\n";
  192. String strSendData = new String(str);
  193. connectedThread.write(strSendData.getBytes());
  194. sendData.setText("");
  195. }
  196. }
  197. });
  198.  
  199. leftSend.setOnClickListener(new View.OnClickListener() {
  200. @Override
  201. public void onClick(View view) {
  202. if (booleanConnect == false) {
  203. Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
  204. } else {
  205. String str = "ONC\n";
  206. String strSendData = new String(str);
  207. connectedThread.write(strSendData.getBytes());
  208. sendData.setText("");
  209. }
  210. }
  211. });
  212.  
  213. rightSend.setOnClickListener(new View.OnClickListener() {
  214. @Override
  215. public void onClick(View view) {
  216. if (booleanConnect == false) {
  217. Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
  218. } else {
  219. String str = "OND\n";
  220. String strSendData = new String(str);
  221. connectedThread.write(strSendData.getBytes());
  222. sendData.setText("");
  223. }
  224. }
  225. });
  226. }
  227. /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  228.  
  229. private static class MyHandler extends Handler {
  230. WeakReference<MainActivity> mActivity;
  231.  
  232. MyHandler(MainActivity activity) {
  233. mActivity = new WeakReference<MainActivity>(activity);
  234. }
  235.  
  236. @Override
  237. public void handleMessage(Message msg) {
  238. MainActivity theActivity = mActivity.get();
  239.  
  240. theActivity.recvData.append(msg.obj.toString());
  241. }
  242. }
  243.  
  244. @Override
  245. protected void onDestroy() {
  246. if (connectedThread != null) {
  247. connectedThread.cancel();
  248. }
  249. super.onDestroy();
  250. }
  251.  
  252. private BroadcastReceiver mReceiver = new BroadcastReceiver() {
  253. @Override
  254. public void onReceive(Context context, Intent intent) {
  255. String action = intent.getAction();
  256. if(BluetoothDevice.ACTION_FOUND.equals(action)){
  257. BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
  258. // 已经配对的则跳过
  259. if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
  260. adapter.add(device.getAddress());
  261. }
  262. }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //搜索结束
  263. if (adapter.getCount() == 0) {
  264. Toast.makeText(MainActivity.this, "没有搜索到设备", Toast.LENGTH_SHORT).show();
  265. }
  266. }
  267. }
  268. };
  269.  
  270. private class ConnectThread extends Thread{
  271. private BluetoothSocket mmsocket;
  272. private BluetoothDevice mmdevice;
  273.  
  274. public ConnectThread(BluetoothDevice device){
  275. mmdevice = device;
  276. BluetoothSocket tmp = null;
  277. try {
  278. tmp = mmdevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
  279. } catch (IOException e) {
  280.  
  281. }
  282. mmsocket = tmp;
  283. }
  284.  
  285. public void run(){
  286. bluetoothAdapter.cancelDiscovery(); //取消设备查找
  287. try {
  288. mmsocket.connect();
  289. } catch (IOException e) {
  290. try {
  291. mmsocket.close();
  292. } catch (IOException e1) {
  293.  
  294. }
  295. //连接失败
  296. return;
  297. }
  298.  
  299. booleanConnect = true;
  300. //新建一个数据交换线程
  301. connectedThread = new ConnectedThread(mmsocket);
  302. connectedThread.start();
  303. }
  304.  
  305. public void cancel() {
  306. try {
  307. mmsocket.close();
  308. } catch (IOException e) {
  309.  
  310. }
  311. }
  312. }
  313.  
  314. private class ConnectedThread extends Thread{
  315. private BluetoothSocket mmsocket;
  316. private InputStream inStream;
  317. private OutputStream outStream;
  318.  
  319. public ConnectedThread(BluetoothSocket socket){
  320.  
  321. mmsocket = socket;
  322. try {
  323. //获得输入输出流
  324. inStream = mmsocket.getInputStream();
  325. outStream = mmsocket.getOutputStream();
  326. } catch (IOException e) {
  327.  
  328. }
  329. }
  330.  
  331. public void run(){
  332. byte[] buff = new byte[1];
  333. int len = 0;
  334. //读数据需不断监听,写不需要
  335. while(true){
  336. try {
  337. len = inStream.read(buff);
  338. //把读取到的数据发送给UI进行显示
  339. String strBuffer = new String(buff);
  340.  
  341. Message toMain = myHandler.obtainMessage();
  342. toMain.obj = strBuffer;
  343. myHandler.sendMessage(toMain);
  344. } catch (IOException e) {
  345.  
  346. break;
  347. }
  348. }
  349. }
  350.  
  351. public void write(byte[] buffer) {
  352. try {
  353. outStream.write(buffer);
  354. } catch (IOException e) {
  355.  
  356. }
  357. }
  358.  
  359. public void cancel() {
  360. try {
  361. mmsocket.close();
  362. } catch (IOException e) {
  363.  
  364. }
  365. }
  366. }
  367.  
  368. @Override
  369. public boolean onCreateOptionsMenu(Menu menu) {
  370. // Inflate the menu; this adds items to the action bar if it is present.
  371. getMenuInflater().inflate(R.menu.menu_main, menu);
  372. return true;
  373. }
  374.  
  375. @Override
  376. public boolean onOptionsItemSelected(MenuItem item) {
  377. // Handle action bar item clicks here. The action bar will
  378. // automatically handle clicks on the Home/Up button, so long
  379. // as you specify a parent activity in AndroidManifest.xml.
  380. int id = item.getItemId();
  381.  
  382. //noinspection SimplifiableIfStatement
  383. if (id == R.id.action_settings) {
  384. super.finish();
  385. return true;
  386. }
  387.  
  388. return super.onOptionsItemSelected(item);
  389. }
  390. }

界面代码:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. android:paddingLeft="@dimen/activity_horizontal_margin"
  7. android:paddingRight="@dimen/activity_horizontal_margin"
  8. android:paddingTop="@dimen/activity_vertical_margin"
  9. android:paddingBottom="@dimen/activity_vertical_margin"
  10. tools:context=".MainActivity"
  11. android:weightSum="1">
  12.  
  13. <Switch
  14. android:id="@+id/swtch"
  15. android:textOn="@string/open"
  16. android:textOff="@string/close"
  17. android:layout_width="wrap_content"
  18. android:layout_height="48dp"
  19. android:layout_gravity="center_horizontal" />
  20.  
  21. <LinearLayout
  22. android:layout_width="fill_parent"
  23. android:layout_height="wrap_content"
  24. android:orientation="horizontal"
  25. android:weightSum="1">
  26.  
  27. <Button
  28. android:id="@+id/search"
  29. android:text="@string/search"
  30. android:layout_width="wrap_content"
  31. android:layout_height="37dp"
  32. android:background="#ff9ed7ff" />
  33.  
  34. <Spinner
  35. android:id="@+id/list"
  36. android:layout_width="162dp"
  37. android:layout_height="48dp"
  38. android:layout_weight="1.17" />
  39.  
  40. <Button
  41. android:id="@+id/connect"
  42. android:text="@string/connect"
  43. android:layout_width="93dp"
  44. android:layout_height="38dp"
  45. android:background="#ff9ed7ff" />
  46. </LinearLayout>
  47.  
  48. <LinearLayout
  49. android:layout_width="fill_parent"
  50. android:layout_height="wrap_content"
  51. android:orientation="horizontal">
  52.  
  53. </LinearLayout>
  54.  
  55. <RelativeLayout
  56. android:layout_width="350dp"
  57. android:layout_height="112dp"
  58. android:layout_gravity="center_horizontal"
  59. android:id="@+id/DirectionPanel">
  60.  
  61. <Button
  62. android:layout_width="wrap_content"
  63. android:layout_height="wrap_content"
  64. android:text="@string/up"
  65. android:id="@+id/BtnUp"
  66. android:layout_above="@+id/BtnRight"
  67. android:layout_centerHorizontal="true"
  68. android:background="#ffd0e2ff" />
  69.  
  70. <Button
  71. android:layout_width="wrap_content"
  72. android:layout_height="wrap_content"
  73. android:text="@string/left"
  74. android:id="@+id/BtnLeft"
  75. android:layout_alignParentBottom="true"
  76. android:layout_toLeftOf="@+id/BtnUp"
  77. android:background="#ffd0e2ff" />
  78.  
  79. <Button
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content"
  82. android:text="@string/right"
  83. android:id="@+id/BtnRight"
  84. android:layout_alignTop="@+id/BtnLeft"
  85. android:layout_toRightOf="@+id/BtnUp"
  86. android:background="#ffd0e2ff" />
  87. </RelativeLayout>
  88.  
  89. <Button
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:text="@string/down"
  93. android:id="@+id/BtnDown"
  94. android:layout_alignParentBottom="true"
  95. android:layout_centerHorizontal="true"
  96. android:layout_gravity="center_horizontal"
  97. android:background="#ffd0e2ff" />
  98.  
  99. <EditText
  100. android:id="@+id/senddata"
  101. android:layout_width="356dp"
  102. android:layout_height="wrap_content"
  103. android:layout_weight="0.18" />
  104.  
  105. <Button
  106. android:id="@+id/send"
  107. android:text="@string/send"
  108. android:layout_width="84dp"
  109. android:layout_height="33dp"
  110. android:background="#ff9ed7ff"
  111. android:layout_gravity="right" />
  112.  
  113. <TextView
  114. android:text="@string/recv"
  115. android:layout_width="wrap_content"
  116. android:layout_height="wrap_content" />
  117.  
  118. <ScrollView
  119. android:layout_width="match_parent"
  120. android:layout_height="66dp"
  121. android:id="@+id/scrollView"
  122. android:layout_weight="0.82"
  123. android:background="#ffc8c8c8">
  124.  
  125. <TextView
  126. android:id="@+id/recvdata"
  127. android:layout_width="fill_parent"
  128. android:layout_height="wrap_content"
  129. android:layout_weight="0.99"
  130. android:background="#ffc8c8c8" />
  131. </ScrollView>
  132.  
  133. </LinearLayout>

实现的效果

实现工程代码蓝牙控制器

参考资料:

http://www.pudn.com/downloads691/sourcecode/comm/android/detail2784484.html

Android 基于蓝牙的方向控制器的更多相关文章

  1. 【源代码】基于Android和蓝牙的单片机温度採集系统

    如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 STC89C52单片机通过HC-06蓝牙模块与Android手机通信实例- 基于And ...

  2. Android Studio 蓝牙开发实例——基于Android 6.0

    因项目需要做一个Android 的蓝牙app来通过手机蓝牙传输数据以及控制飞行器,在此,我对这段时间里写的蓝牙app的代码进行知识梳理和出现错误的总结. 该应用的Compile Sdk Version ...

  3. 【转】Android bluetooth介绍(二): android blueZ蓝牙代码架构及其uart 到rfcomm流程

    原文网址:http://blog.sina.com.cn/s/blog_602c72c50102uzoj.html 关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP ...

  4. Android 串口蓝牙通信开发Java版本

    Android串口BLE蓝牙通信Java版 0. 导语 Qt on Android 蓝牙通信开发 我们都知道,在物联网中,BLE蓝牙是通信设备的关键设备.在传统的物联网应用中,无线WIFI.蓝牙和Zi ...

  5. Android 低功耗蓝牙BLE 开发注意事项

    基本概念和问题 1.蓝牙设计范式? 当手机通过扫描低功耗蓝牙设备并连接上后,手机与蓝牙设备构成了客户端-服务端架构.手机通过连接蓝牙设备,可以读取蓝牙设备上的信息.手机就是客户端,蓝牙设备是服务端. ...

  6. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  7. ubuntu16.04连接android手机蓝牙共享网络热点

    最近的想要用android手机蓝牙共享wifi网络给ubuntu16.04系统用,查了好多资料,发现网上很少有有用的.自己实践后分享如下. 第一步:手机与电脑配对:         该步骤比较简单,网 ...

  8. Android BLE 蓝牙编程(一)

    最近在研究这个,等我有时间来写吧! 终于在端午节给自己放个假,现在就来说说关于android蓝牙ble的 最近的学习成果吧!! 需要材料(写个简单教程吧--关于小米手环的哦!嘿嘿) Android 手 ...

  9. Android基于XMPP的即时通讯3-表情发送

    这篇博文主要讲表情发送的一些东西. 参考:Android基于XMPP的即时通讯1-基本对话 1.准备好资源文件 采用的是emoji的表情,我打包好了,下载地址:http://files.cnblogs ...

随机推荐

  1. 16 - 文件操作-StringIO-BytesIO

    目录 1 文件操作 1.1 open函数介绍 1.2 打开操作 1.2.1 mode模式 1.2.2 文件指针 1.2.3 缓冲区 1.2.4 encoding编码 1.2.5 其他参数 1.3 读写 ...

  2. 《深入理解Java虚拟机》笔记--第十三章、线程安全与锁优化

    先保证并发的正确性,然后在此基础上来实现高效. 线程安全:     当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操 ...

  3. MYSQL三种安装方式--二进制包安装

    1. 把二进制包下载到/usr/local/src下 2. 如果是tar.gz包,则使用tar zxvf 进行解压 如果是tar包,则可以使用tar xvf 进行解压 3. $ mv mysql-5. ...

  4. 一个设置为display:none;的div,在用.height()方法获取不到它的高,获取到的高度为0.

    <div style="width:100px;height:100px;background:red;visibility:hidden"></div>/ ...

  5. day01作业

    Java技术按照用途不同分为三大版本,分别是JavaSE.JavaEE和JavaMeJava虚拟机就是一个虚拟的用于执行字节码文件的计算机.它是Java最核心的技术,是Java跨平台的基础.DOS命令 ...

  6. 三十分钟理解计算图上的微积分:Backpropagation,反向微分

    神经网络的训练算法,目前基本上是以Backpropagation (BP) 反向传播为主(加上一些变化),NN的训练是在1986年被提出,但实际上,BP 已经在不同领域中被重复发明了数十次了(参见 G ...

  7. 深度揭秘阿里移动端高性能动态化方案Weex

    2016年Qcon大会首日,阿里巴巴资深总监.淘宝移动平台.阿里百川负责人庄卓然宣布移动端高性能动态化方案Weex即时内测,并将于6月开源.此消息一出,群情汹涌,在座的程序猿.攻城狮们纷纷拿起手机扫码 ...

  8. LoadRunner 使用虚拟IP测试流程

    LoadRunner 使用虚拟IP测试流程 LoadRunner 使用IP欺骗的原因 . 当某个IP的访问过于频繁,或者访问量过大是,服务器会拒绝访问请求,这时候通过IP欺骗可以增加访问频率和访问量, ...

  9. 使用自己的域名解析 cnblogs 博客

    使用自己的域名解析 cnblogs 博客(博客园) 1.实现原理 用户访问 -> 阿里云解析 -> github page 跳转 -> 真实的博客地址 2.创建 github pag ...

  10. C++运算符重载规则

    运算符重载时要遵循以下规则:  ( 1 ) 除了类属关系运算符 " . " .成员指针运算符 " .* " .作用域运算符 " :: " . ...