Android基于蓝牙的聊天demo
来源:互联网 发布:windows ad域搭建 编辑:程序博客网 时间:2024/05/16 06:36
本文介绍了一个基于蓝牙的聊天demo:在服务端,通过打开蓝牙、设置可见状态、不断监听客户端的访问、建立连接、交换数据等步骤,实现服务端的蓝牙功能创建;在客户端,通过打开蓝牙、搜索蓝牙设备(服务端)、绑定蓝牙设备、建立连接、交换数据等步骤,实现客户端的蓝牙功能创建。有关上述蓝牙基础部分的内容,请参见我的博文《Android蓝牙开发介绍》。
模块构成及依赖关系
在软件设计之初,为了降低各功能之间的耦合,提高软件的复用性,提出了分层的概念:
demo分为3层,分别是UI层、业务逻辑层、网络层(蓝牙传输),每层包含的类如下所示:
其中UI层包含MainActivity、DeviceAdapter类;
业务逻辑层包含ChatControl、BlueToothControl类;
网络层包含ConnectedThread、AcceptThread、ConnectThread类。
各个类的依赖关系如下所示:
由图可知,模块之间是自顶至下的顺序依赖关系,这样做的好处是,当有某一个功能所对应的模块需要改变时,只需修改其内部逻辑即可,而向上提供的接口无需改变,这样也就无需修改上层的结构。
layout布局
首先是layout布局,初始时,默认显示聊天面板,隐藏蓝牙列表:
<!-- activity_main.xml --><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <!-- 蓝牙设备列表 --> <ListView android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"></ListView> <!--聊天面板 --> <RelativeLayout android:id="@+id/chat_panel" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible"> <!-- 聊天按钮 --> <Button android:id="@+id/bt_send" android:layout_width="100dp" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="@string/send" /> <!-- 聊天输入框 --> <EditText android:id="@+id/chat_edit" android:layout_width="match_parent" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_toLeftOf="@+id/bt_send" /> <!-- 聊天对话框 --> <TextView android:id="@+id/chat_content" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/bt_send" /> </RelativeLayout></RelativeLayout>
UI层
UI层主要负责初始化并更新用户界面、设置广播接收器、展示蓝牙搜索列表、通知用户蓝牙状态等。下面是MainActivity的代码:
public class MainActivity extends Activity { //startActivityForResult()的请求码 public static final int REQUEST_CODE = 0; //搜索到的蓝牙设备列表 private List<BluetoothDevice> mDeviceList = new ArrayList<>(); //已绑定的蓝牙设备列表 private List<BluetoothDevice> mBondedDeviceList = new ArrayList<>(); //管理蓝牙操作的类 private BlueToothController mController = new BlueToothController(); private ListView mListView; //自定义ListView的Adapter适配器 private DeviceAdapter mAdapter; private Toast mToast; //聊天面板 private View mChatPanel; //发送按钮 private Button mSendBt; //输入框 private EditText mInputBox; //显示聊天内容区域 private TextView mChatContent; //输入内容 private StringBuilder mChatText = new StringBuilder(); //Handler,用于线程之间传递信息以更新UI private Handler mUIHandler = new MyHandler(); //广播接收器,用于收听系统发出的广播以触发操作 private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //接收广播附带的intent中的action String action = intent.getAction(); //接收到开始搜索设备的action if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {//在标题栏显示转动的progressBar,表示开始搜索 setProgressBarIndeterminateVisibility(true); //初始化数据列表 mDeviceList.clear(); //刷新ListView mAdapter.notifyDataSetChanged(); } //接收到搜索完毕的action else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //关闭标题栏转动的progressBar,表示搜索结束 setProgressBarIndeterminateVisibility(false); } //接收到找到设备的action else if (BluetoothDevice.ACTION_FOUND.equals(action)) { //从extra数据中获得搜索到的蓝牙设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //找到一个,添加一个 mDeviceList.add(device); //刷新ListView列表 mAdapter.notifyDataSetChanged(); } //扫描模式改变,即设备在可见性之间切换 else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) { //可见性的模式 int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 0); //本设备对其他设备可见 if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { setProgressBarIndeterminateVisibility(true); } //本设备对其他设备隐藏 else { setProgressBarIndeterminateVisibility(false); } } //绑定状态改变 else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { //获得绑定设备 BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //无绑定 if (remoteDevice == null) { showToast("no device"); return; } int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0); //已绑定 if (status == BluetoothDevice.BOND_BONDED) { showToast("Bonded " + remoteDevice.getName()); } //正在绑定 else if (status == BluetoothDevice.BOND_BONDING) { showToast("Bonding " + remoteDevice.getName()); } //未绑定 else if (status == BluetoothDevice.BOND_NONE) { showToast("Not bond " + remoteDevice.getName()); } } } }; //点击搜索到的设备,并点击某一项并与之绑定时回调的接口对象 private AdapterView.OnItemClickListener bindDeviceClick = new AdapterView.OnItemClickListener() { //绑定设备需要设备版本不低于Android 4.4 (API 19) @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { //从列表中获取该设备 BluetoothDevice device = mDeviceList.get(i); //绑定设备需要设备版本不低于Android 4.4 (API 19),低于该版本的设备无法绑定蓝牙 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //绑定设备 device.createBond(); } } }; //点击已绑定设备列表中的某一项时回调的接口对象 private AdapterView.OnItemClickListener bindedDeviceClick = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { //获得绑定的设备 BluetoothDevice device = mBondedDeviceList.get(i);//与选中的设备聊天 ChatController.getInstance().startChatWith(device, mController.getAdapter(), mUIHandler); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //初始化ActionBar initActionBar(); //绑定界面 setContentView(R.layout.activity_main); //初始化UI控件 initUI(); //注册广播接收器以接收系统广播 registerBluetoothReceiver(); //打开蓝牙 mController.turnOnBlueTooth(this, REQUEST_CODE); } private void registerBluetoothReceiver() { IntentFilter filter = new IntentFilter(); //开始查找 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //结束查找 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //查找到设备 filter.addAction(BluetoothDevice.ACTION_FOUND); //设备扫描模式改变(可见性改变) filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); //绑定状态 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); //动态注册广播接收器 registerReceiver(mReceiver, filter); } private void initUI() { mListView = (ListView) findViewById(R.id.device_list); mAdapter = new DeviceAdapter(mDeviceList, this); mListView.setAdapter(mAdapter); mListView.setOnItemClickListener(bindDeviceClick); mChatPanel = findViewById(R.id.chat_panel); mSendBt = (Button) findViewById(R.id.bt_send); mSendBt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //点击发送按钮,获得输入框中的输入框 String ext = mInputBox.getText().toString(); //发送信息 ChatController.getInstance().sendMessage(ext); //保存会话内容 mChatText.append(ext).append("\n"); //将输入内容显示在对话区域 mChatContent.setText(mChatText.toString()); //清空输入框 mInputBox.setText(""); } }); mInputBox = (EditText) findViewById(R.id.chat_edit); mChatContent = (TextView) findViewById(R.id.chat_content); } @Override protected void onDestroy() { super.onDestroy(); ChatController.getInstance().stopChat(); unregisterReceiver(mReceiver); } public void enterChatMode() { //进入聊天界面,蓝牙列表隐藏 mListView.setVisibility(View.GONE); //聊天面板出现 mChatPanel.setVisibility(View.VISIBLE); } public void exitChatMode() { //退出聊天界面,显示蓝牙列表 mListView.setVisibility(View.VISIBLE); //隐藏聊天面板 mChatPanel.setVisibility(View.GONE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE) { //若用户不打算打开蓝牙功能,则activity直接被finish if (resultCode != RESULT_OK) { finish(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } private void showToast(String text) { if (mToast == null) { mToast = Toast.makeText(this, text, Toast.LENGTH_LONG); } else { mToast.setText(text); } mToast.show(); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.enable_visiblity) { mController.enableVisibly(this); } else if (id == R.id.find_device) { //查找设备 mAdapter.refresh(mDeviceList); mController.findDevice(); mListView.setOnItemClickListener(bindDeviceClick); } else if (id == R.id.bonded_device) { //查看已绑定设备 mBondedDeviceList = mController.getBondedDeviceList(); mAdapter.refresh(mBondedDeviceList); mListView.setOnItemClickListener(bindedDeviceClick); } else if (id == R.id.listening) { //等待对方设备进入聊天 ChatController.getInstance().waitingForFriends(mController.getAdapter(), mUIHandler); } else if (id == R.id.stop_listening) { ChatController.getInstance().stopChat(); exitChatMode(); } else if (id == R.id.disconnect) { exitChatMode(); } return super.onOptionsItemSelected(item); } private void initActionBar() { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); getActionBar().setDisplayUseLogoEnabled(false); setProgressBarIndeterminate(true); try { ViewConfiguration config = ViewConfiguration.get(this); Field menuKeyField = ViewConfiguration.class .getDeclaredField("sHasPermanentMenuKey"); if (menuKeyField != null) { menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); } } catch (Exception e) { e.printStackTrace(); } } //处理从子线层发给UI线程的消息以更新UI private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case Constant.MSG_START_LISTENING: setProgressBarIndeterminateVisibility(true); break; case Constant.MSG_FINISH_LISTENING: setProgressBarIndeterminateVisibility(false); exitChatMode(); break; case Constant.MSG_GOT_DATA: byte[] data = (byte[]) msg.obj; mChatText.append(ChatController.getInstance().decodeMessage(data)).append("\n"); mChatContent.setText(mChatText.toString()); break; case Constant.MSG_ERROR: exitChatMode(); showToast("error: " + String.valueOf(msg.obj)); break; case Constant.MSG_CONNECTED_TO_SERVER: enterChatMode(); showToast("Connected to Server"); break; case Constant.MSG_GOT_A_CLINET: enterChatMode(); showToast("Got a Client"); break; } } }}
以下是自定义的蓝牙列表项布局及内容
//DeviceAdapter.java//蓝牙列表的布局public class DeviceAdapter extends BaseAdapter { private List<BluetoothDevice> mData; private Context mContext; public DeviceAdapter(List<BluetoothDevice> data, Context context) { mData = data; mContext = context.getApplicationContext(); } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int i) { return mData.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { View itemView = view; //复用View,优化性能 if( itemView == null) { itemView = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_2,viewGroup,false); } TextView line1 = (TextView) itemView.findViewById(android.R.id.text1); TextView line2 = (TextView) itemView.findViewById(android.R.id.text2); //获取对应的蓝牙设备 BluetoothDevice device = (BluetoothDevice) getItem(i); //显示名称 line1.setText(device.getName()); //显示地址 line2.setText(device.getAddress()); return itemView; } public void refresh(List<BluetoothDevice> data) { mData = data; notifyDataSetChanged(); }}
业务逻辑层
业务逻辑层主要负责蓝牙功能的处理(如打开或关闭蓝牙、设置蓝牙设备的可见性、搜索蓝牙设备等)和聊天逻辑处理:
//BlueToothController.java//处理蓝牙逻辑public class BlueToothController { private BluetoothAdapter mAapter; public BlueToothController() { //创建蓝牙对象BluetoothAdapter mAapter = BluetoothAdapter.getDefaultAdapter(); } public BluetoothAdapter getAdapter() { return mAapter; } /** * 打开蓝牙 * @param activity * @param requestCode */ public void turnOnBlueTooth(Activity activity, int requestCode) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); activity.startActivityForResult(intent, requestCode); //不推荐使用BluetoothAdapter.enable()方法打开蓝牙,该方法一般有系统调用// mAdapter.enable(); } /** * 打开蓝牙可见性,系统会发出广播 * @param context */ public void enableVisibly(Context context) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); context.startActivity(discoverableIntent); } /** * 查找设备,系统会发出广播 */ public void findDevice() { assert (mAapter != null); mAapter.startDiscovery(); } /** * 获取绑定设备 * @return */ public List<BluetoothDevice> getBondedDeviceList() { return new ArrayList<>(mAapter.getBondedDevices()); }}
//ChatController.java//聊天业务逻辑public class ChatController { private ConnectThread mConnectThread; private AcceptThread mAcceptThread; /** * 网络协议的处理函数 */ private class ChatProtocol implements ProtocolHandler<String> { private static final String CHARSET_NAME = "utf-8"; //封包,以发送至网络传递 @Override public byte[] encodePackage(String data) { if( data == null) { return new byte[0]; } else { try { return data.getBytes(CHARSET_NAME); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return new byte[0]; } } } //解包,接收网络传递过来的数据 @Override public String decodePackage(byte[] netData) { if( netData == null) { return ""; } try { return new String(netData, CHARSET_NAME); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return ""; } } } /** * 协议处理 */ private ChatProtocol mProtocol = new ChatProtocol(); /** * 与服务器连接进行聊天 * @param device * @param adapter * @param handler */ public void startChatWith(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) { mConnectThread = new ConnectThread(device,adapter,handler); mConnectThread.start(); } /** * 等待客户端来连接 * @param adapter * @param handler */ public void waitingForFriends(BluetoothAdapter adapter, Handler handler) { mAcceptThread = new AcceptThread(adapter,handler); mAcceptThread.start(); } /** * 发出消息 * @param msg */ public void sendMessage(String msg) { byte[] data = mProtocol.encodePackage(msg); if(mConnectThread != null) { mConnectThread.sendData(data); } else if( mAcceptThread != null) { mAcceptThread.sendData(data); } } /** * 网络数据解码 * @param data * @return */ public String decodeMessage(byte[] data) { return mProtocol.decodePackage(data); } /** * 停止聊天 */ public void stopChat() { if(mConnectThread != null) { mConnectThread.cancel(); } else if( mAcceptThread != null) { mAcceptThread.cancel(); } } /** * 单例方式构造类对象 */ private static class ChatControlHolder { private static ChatController mInstance = new ChatController(); } public static ChatController getInstance() { return ChatControlHolder.mInstance; }}
网络层
网络层主要负责消息的传递。
//AcceptThread.java//当设备作为服务端时,不断监听来自其他设备的连接请求public class AcceptThread extends Thread { private static final String NAME = "BlueToothClass"; private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID); private final BluetoothServerSocket mmServerSocket; private final BluetoothAdapter mBluetoothAdapter; private final Handler mHandler; private ConnectedThread mConnectedThread; public AcceptThread(BluetoothAdapter adapter, Handler handler) { // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final mBluetoothAdapter = adapter; mHandler = handler; BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING); socket = mmServerSocket.accept(); } catch (IOException e) { mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e)); break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); try { mmServerSocket.close(); mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING); } catch (IOException e) { e.printStackTrace(); } break; } } } private void manageConnectedSocket(BluetoothSocket socket) { //只支持同时处理一个连接 if( mConnectedThread != null) { mConnectedThread.cancel(); } mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET); mConnectedThread = new ConnectedThread(socket, mHandler); mConnectedThread.start(); } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING); } catch (IOException e) { } } public void sendData(byte[] data) { if( mConnectedThread!=null){ mConnectedThread.write(data); } }}
//ConnectThread.java//当设备作为客户端时,不断搜索可见设备public class ConnectThread extends Thread { private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID); private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; private BluetoothAdapter mBluetoothAdapter; private final Handler mHandler; private ConnectedThread mConnectedThread; public ConnectThread(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; mBluetoothAdapter = adapter; mHandler = handler; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (Exception connectException) { mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, connectException)); // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) manageConnectedSocket(mmSocket); } private void manageConnectedSocket(BluetoothSocket mmSocket) { mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER); mConnectedThread = new ConnectedThread(mmSocket, mHandler); mConnectedThread.start(); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } public void sendData(byte[] data) { if( mConnectedThread!=null){ mConnectedThread.write(data); } }}
//ConnectedThread.java//用于支持蓝牙设备之间数据传输的类,每当建立一组新的聊天,就会通过该类创建一个新的线程public class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; private final Handler mHandler; public ConnectedThread(BluetoothSocket socket, Handler handler) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; mHandler = handler; // Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() // Keep listening to the InputStream until an exception occurs while (true) { try { // Read from the InputStream bytes = mmInStream.read(buffer); // Send the obtained bytes to the UI activity if( bytes >0) { Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA, buffer); mHandler.sendMessage(message); } Log.d("GOTMSG", "message size" + bytes); } catch (IOException e) { mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e)); break; } } } /* Call this from the main activity to send data to the remote device */ public void write(byte[] bytes) { try { mmOutStream.write(bytes); } catch (IOException e) { } } /* Call this from the main activity to shutdown the connection */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } }}
存储常量的类:
//Constant.java//蓝牙设备相互匹配必须使用唯一的字符串,否则无法连接public class Constant { public static final String CONNECTTION_UUID = "00001101-0000-1000-8000-00805F9B34FB"; /** * 开始监听 */ public static final int MSG_START_LISTENING = 1; /** * 结束监听 */ public static final int MSG_FINISH_LISTENING = 2; /** * 有客户端连接 */ public static final int MSG_GOT_A_CLINET = 3; /** * 连接到服务器 */ public static final int MSG_CONNECTED_TO_SERVER = 4; /** * 获取到数据 */ public static final int MSG_GOT_DATA = 5; /** * 出错 */ public static final int MSG_ERROR = -1;}
** * 处理网络协议,对数据进行封包或解包 * */public interface ProtocolHandler<T> { public byte[] encodePackage(T data); public T decodePackage(byte[] netData);}
0 0
- Android基于蓝牙的聊天demo
- Android基于蓝牙的聊天demo
- android-实例-基于蓝牙的聊天程序
- android中基于蓝牙开发的demo
- Android蓝牙通讯/蓝牙聊天的实现(一)_含demo下载
- Android蓝牙通讯/蓝牙聊天的实现(二)_含demo下载
- android学习日记——基于UDP的聊天demo
- 两个Android端基于蓝牙通讯的demo
- 一个基于netty的websocket聊天demo
- Android蓝牙聊天程序的扩展开发(基于Google Sample,类QQ设计)
- Android蓝牙聊天,蓝牙通讯
- Bluetooth---Android的蓝牙聊天示例应用程序
- Android 蓝牙聊天程序的实现
- 基于Android 精简版 Bluetooth 蓝牙 聊天 源码下载
- android蓝牙聊天
- android 蓝牙通信 聊天
- Android蓝牙之聊天
- Android基于UDP的局域网聊天通信(有完整Demo)
- hdu1565方格取数(1) (最大权独立集)
- POJ 1144 Network【割点个数】
- HDU 2575 Count Problem (水题)
- 虚拟机无法识别U盘的问题及解决方法
- 学习笔记:Ubuntu15.04 + Python 配置
- Android基于蓝牙的聊天demo
- 全排列函数
- 翻转子串(思路:假定两个字符串已经是翻转)
- android开发内存溢出处理记录
- 【eclipse】generate getters and setters错误
- android开发中Rsa加密的使用
- 计数排序
- U-Boot启动过程完全分析
- JavaScript执行顺序详细介绍