《安卓网络编程》之第六篇 Android中的WIFI和蓝牙
关于WIFI就不多介绍啦,直接来个段子吧。
问:“WiFi对人体有伤害么?”
答:“不清楚,反正没有WiFi我就浑身不舒服。
比较重要的一点就是WifiManager wm=(WifiManager)Android_Wifi.this.getSystemService(Context.WIFI_SERVICE);
关闭打开搜索都可以通过调用wm的相关方法实现。可能要开发wifi万能钥匙那一类的程序才用到这个吧,普通应用程序主要就识别是wifi网络还是移动网络。不多讲
代码参考博客:http://blog.csdn.net/sbvfhp/article/details/7007090
重点蓝牙:
一:什么是蓝牙
1:Bluetooth是目前使用最广泛的无线通讯协议,近距离无线通讯的标准。传说瑞典有个国王特别爱吃蓝莓导致自己的牙齿天天都是蓝色的,在他执政期间这位国王非常善于交际,能说会到,和邻国的搞得关系非常好,这个Bluetooth的发明者觉得蓝牙它的作用就是在近距离沟通周围的设备,跟这个国王很类似,于是起名叫蓝牙。
2:主要针对短距离设备通讯(10米)
3:无线耳机,无线鼠标,无线键盘
二:蓝牙工作流程图
首先两个设备上都要有蓝牙设备或者专业一点叫蓝牙适配器,以手机和电脑为例我画了如下流程图。其次在手机上进行扫描,扫描周围蓝蓝牙设备,先找到手机附近的电脑,然后给它发出一个信号需要进行蓝牙的配对,再次返回一个信号说明手机和电脑已经配对成功了,最后配对成功后可以进行文件传输了。这是一个最基本的一个流程。
三:蓝牙开发相关类
在Android手机中开发蓝牙程序时,离不开几个类:
BluetoothSocket:close() connect() getInputStream()......
BluetoothServerSocket : accept()
BlutoothAdapter :代表本地的蓝牙适配器设备,通过此类可以让用户能执行基本的蓝牙任务。
BluetoothClass: 代表了一个描述设备通用特性和功能的蓝牙类。比如一个蓝牙类会指定如电话、计算机或耳机的通用设备类型。
BluetoothClass.Service:
BluetoothClass.Device:
BluetoothClass.Device.Major: 定义了主要设备类的常量
其中与蓝牙相关的最重要的两个API
1:BuletoothAdapter
这个类的对象代表了本地的蓝牙适配器,相当于蓝牙工作流程图中的手机里的蓝牙适配器,也就是说比如这个应用程序是运行在手机上,那么手机上的蓝牙适配器就是本地蓝牙适配器。
2:BuletoothDevice
这个类的对象代表了远程的蓝牙设备,相当于蓝牙工作流程图中的计算机里的蓝牙适配器,也就是说比如这个应用程序是运行在手机上,那么BuletoothDevice代表了你要连接的远程的那个设备上面的蓝牙适配器。
四:蓝牙开发步骤:
此部分转载于 http://zhouyunan2010.iteye.com/blog/1186021
从查找蓝牙设备到能够相互通信要经过几个基本步骤(本机做为服务器):
1.设置权限
在manifest中配置
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
2.启动蓝牙
首先要查看本机是否支持蓝牙,获取BluetoothAdapter蓝牙适配器对象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if(mBluetoothAdapter == null){ //表明此手机不支持蓝牙 return; } if(!mBluetoothAdapter.isEnabled()){ //蓝牙未开启,则开启蓝牙 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BT); } //...... public void onActivityResult(int requestCode, int resultCode, Intent data){ if(requestCode == REQUEST_ENABLE_BT){ if(requestCode == RESULT_OK){ //蓝牙已经开启 } } }
3。发现蓝牙设备
这里可以细分为几个方面
(1)使本机蓝牙处于可见(即处于易被搜索到状态),便于其他设备发现本机蓝牙
//使本机蓝牙在300秒内可被搜索 private void ensureDiscoverable() { if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent); } }
(2)查找已经配对的蓝牙设备,即以前已经配对过的设备
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); for (BluetoothDevice device : pairedDevices) { //device.getName() +" "+ device.getAddress()); } } else { mPairedDevicesArrayAdapter.add("没有找到已匹对的设备"); }
(3)通过mBluetoothAdapter.startDiscovery();搜索设备,要获得此搜索的结果需要注册
一个BroadcastReceiver来获取。先注册再获取信息,然后处理
//注册,当一个设备被发现时调用onReceive IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); this.registerReceiver(mReceiver, filter); //当搜索结束后调用onReceive filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); this.registerReceiver(mReceiver, filter); //....... private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(BluetoothDevice.ACTION_FOUND.equals(action)){ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 已经配对的则跳过 if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); //保存设备地址与名字 } }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //搜索结束 if (mNewDevicesArrayAdapter.getCount() == 0) { mNewDevicesArrayAdapter.add("没有搜索到设备"); } } } };
4.建立连接
查找到设备 后,则需要建立本机与其他设备之间的连接。
一般用本机搜索其他蓝牙设备时,本机可以作为一个服务端,接收其他设备的连接。
启动一个服务器端的线程,死循环等待客户端的连接,这与ServerSocket极为相似。
这个线程在准备连接之前启动
//UUID可以看做一个端口号 private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); //像一个服务器一样时刻监听是否有连接建立 private class AcceptThread extends Thread{ private BluetoothServerSocket serverSocket; public AcceptThread(boolean secure){ BluetoothServerSocket temp = null; try { temp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord( NAME_INSECURE, MY_UUID); } catch (IOException e) { Log.e("app", "listen() failed", e); } serverSocket = temp; } public void run(){ BluetoothSocket socket=null; while(true){ try { socket = serverSocket.accept(); } catch (IOException e) { Log.e("app", "accept() failed", e); break; } } if(socket!=null){ //此时可以新建一个数据交换线程,把此socket传进去 } } //取消监听 public void cancel(){ try { serverSocket.close(); } catch (IOException e) { Log.e("app", "Socket Type" + socketType + "close() of server failed", e); } } }
5.交换数据
搜索到设备后可以获取设备的地址,通过此地址获取一个BluetoothDeviced对象,可以看做客户端,通过此对象device.createRfcommSocketToServiceRecord(MY_UUID);同一个UUID可与服务器建立连接获取另一个socket对象,由此服务端与客户端各有一个socket对象,此时
他们可以互相交换数据了。
创立客户端socket可建立线程
//另一个设备去连接本机,相当于客户端 private class ConnectThread extends Thread{ private BluetoothSocket socket; private BluetoothDevice device; public ConnectThread(BluetoothDevice device,boolean secure){ this.device = device; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE); } catch (IOException e) { Log.e("app", "create() failed", e); } } public void run(){ mBluetoothAdapter.cancelDiscovery(); //取消设备查找 try { socket.connect(); } catch (IOException e) { try { socket.close(); } catch (IOException e1) { Log.e("app", "unable to close() "+ " socket during connection failure", e1); } connetionFailed(); //连接失败 return; } //此时可以新建一个数据交换线程,把此socket传进去 } public void cancel() { try { socket.close(); } catch (IOException e) { Log.e("app", "close() of connect socket failed", e); } } }
6.建立数据通信线程,进行读取数据
//建立连接后,进行数据通信的线程 private class ConnectedThread extends Thread{ private BluetoothSocket socket; private InputStream inStream; private OutputStream outStream; public ConnectedThread(BluetoothSocket socket){ this.socket = socket; try { //获得输入输出流 inStream = socket.getInputStream(); outStream = socket.getOutputStream(); } catch (IOException e) { Log.e("app", "temp sockets not created", e); } } public void run(){ byte[] buff = new byte[1024]; int len=0; //读数据需不断监听,写不需要 while(true){ try { len = inStream.read(buff); //把读取到的数据发送给UI进行显示 Message msg = handler.obtainMessage(BluetoothChat.MESSAGE_READ, len, -1, buff); msg.sendToTarget(); } catch (IOException e) { Log.e("app", "disconnected", e); connectionLost(); //失去连接 start(); //重新启动服务器 break; } } } public void write(byte[] buffer) { try { outStream.write(buffer); // Share the sent message back to the UI Activity handler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer) .sendToTarget(); } catch (IOException e) { Log.e("app", "Exception during write", e); } } public void cancel() { try { socket.close(); } catch (IOException e) { Log.e("app", "close() of connect socket failed", e); } } }
到这里,蓝牙通信的基本操作已经全部完成。
五:蓝牙聊天室开发
源码参考 http://download.csdn.net/detail/mr_raptor/8033951#comment
此demo界面优美,实现基本功能,但不能保存聊天记录,黑屏自动断线,在此基础做出修改。
相关知识:
在EditText中插入表情图片 (CharacterStyle&SpannableString)
源码分析:
Utils.java
1 package cn.com.farsgiht.bluetoothdemo.utils; 2 3 import android.app.Activity; 4 import android.app.Notification; 5 import android.app.NotificationManager; 6 import android.app.PendingIntent; 7 import android.content.Context; 8 import android.content.Intent; 9 import cn.com.farsgiht.bluetoothdemo.R; 10 11 public class Utils { 12 public static final int NOTIFY_ID1 = 1001; 13 14 public static void notifyMessage(Context context, String msg, Activity activity){ 15 //Notification builder; 16 PendingIntent contentIntent = null; 17 NotificationManager nm; 18 // 发送通知需要用到NotificationManager对象 19 nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); 20 // 消息对象 21 Intent notificationIntent = new Intent(context, activity.getClass()); 22 // PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags) 23 // 用来获得一个挂起的PendingIntent,让该Intent去启动新的Activity来处理通知 24 contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); 25 26 27 //定义通知栏展现的内容信息 28 int icon = R.drawable.icon; 29 long when = System.currentTimeMillis(); 30 Notification notification = new Notification(icon, msg, when); 31 notification.defaults |= Notification.DEFAULT_VIBRATE; 32 notification.defaults |= Notification.DEFAULT_SOUND; // 调用系统自带声音 33 notification.flags |= Notification.FLAG_AUTO_CANCEL; // 点击清除按钮或点击通知后会自动消失 34 notification.defaults |= Notification.DEFAULT_LIGHTS; 35 notification.vibrate = new long[]{300, 500}; 36 notification.setLatestEventInfo(context, "BluetoothChat", msg, contentIntent); 37 /* // 定制我们要在状态栏显示的通知样式 38 builder = new Notification(context); 39 builder.setContentIntent(contentIntent) 40 .setSmallIcon(R.drawable.ic_launcher)//设置状态栏里面的图标(小图标) .setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.i5))//下拉下拉列表里面的图标(大图标) .setTicker("this is bitch!") //设置状态栏的显示的信息 41 .setWhen(System.currentTimeMillis())//设置时间发生时间 42 .setAutoCancel(true)//设置可以清除 43 .setContentTitle("This is ContentTitle")//设置下拉列表里的标题 44 .setContentText("this is ContentText");//设置上下文内容 45 */ 46 // 获得刚才创建的通知对象 47 // Notification notification = builder.getNotification();//获取一个Notification 48 49 // 通过NotificationManger来发送通知消息 50 // 参数1通知的ID,参数2发送哪个通知 51 nm.notify(NOTIFY_ID1, notification); 52 } 53 }
通知栏相关代码
2. PageIndicatorView.java
1 package cn.com.farsgiht.bluetoothdemo.UI; 2 3 import cn.com.farsgiht.bluetoothdemo.R; 4 import android.content.Context; 5 import android.graphics.Bitmap; 6 import android.graphics.BitmapFactory; 7 import android.widget.ImageView; 8 import android.widget.LinearLayout; 9 10 public class PageIndicatorView extends ImageView { 11 private final String TAG = "PageIndicatorView"; 12 private LinearLayout mPageIndicLayout; 13 14 public PageIndicatorView(Context context) { 15 super(context); 16 setSelectedView(false); 17 } 18 19 public void setSelectedView(boolean selected){ 20 Bitmap bitmap; 21 if(selected){ 22 bitmap = BitmapFactory.decodeResource(getResources(), 23 R.drawable.page_select); 24 }else{ 25 bitmap = BitmapFactory.decodeResource(getResources(), 26 R.drawable.page_item); 27 } 28 this.setImageBitmap(bitmap); 29 } 30 }
表情页页面指示器
3. DrawerHScrollView.java
1 package cn.com.farsgiht.bluetoothdemo.UI; 2 3 import java.util.Hashtable; 4 5 import android.content.Context; 6 import android.util.AttributeSet; 7 import android.util.Log; 8 import android.view.Gravity; 9 import android.view.ViewGroup; 10 import android.view.ViewParent; 11 import android.widget.HorizontalScrollView; 12 import android.widget.LinearLayout; 13 14 public class DrawerHScrollView extends HorizontalScrollView { 15 private static final String TAG = "DrawerHScrollView"; 16 17 private int currentPage = 0; 18 private int totalPages = 1; 19 private static Hashtable<Integer, Integer> positionLeftTopOfPages = new Hashtable(); 20 private LinearLayout mPageIndicLayout; 21 private Context mContext; 22 23 public DrawerHScrollView(Context context) { 24 super(context); 25 this.mContext = context; 26 } 27 28 public DrawerHScrollView(Context context, AttributeSet attrs) { 29 super(context, attrs); 30 this.mContext = context; 31 } 32 33 public DrawerHScrollView(Context context, AttributeSet attrs, int defStyle) { 34 super(context, attrs, defStyle); 35 this.mContext = context; 36 } 37 38 public void cleanup(){ 39 currentPage = 0; 40 totalPages = 1; 41 if(positionLeftTopOfPages != null){ 42 positionLeftTopOfPages.clear(); 43 } 44 } 45 46 public void setParameters(int totalPages, int currentPage, int scrollDisX, int space) { 47 Log.d(TAG, "~~~~~setParameters totalPages:"+totalPages +",currentPage:"+ currentPage +",scrollDisX:"+scrollDisX); 48 this.totalPages = totalPages; 49 this.currentPage = currentPage; 50 positionLeftTopOfPages.clear(); 51 for (int i = 0;i < totalPages;i++){ 52 int posx = (scrollDisX) * i - space; 53 positionLeftTopOfPages.put(i, posx); 54 Log.d(TAG, "~~~~~setParameters i:"+i +",posx:"+posx); 55 } 56 smoothScrollTo(0, 0); 57 setPageIndicLayout(); 58 if(mPageIndicLayout != null){ 59 updateDrawerPageLayout(totalPages, currentPage); 60 } 61 } 62 63 public void setPageIndicLayout(){ 64 // 添加表情多页图标布局 65 mPageIndicLayout = new LinearLayout(mContext); 66 mPageIndicLayout.setGravity(Gravity.CENTER); 67 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); 68 mPageIndicLayout.setLayoutParams(params); 69 ViewParent parent = this.getParent(); 70 if(parent instanceof LinearLayout){ 71 LinearLayout layout = (LinearLayout)parent; 72 layout.addView(mPageIndicLayout, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); 73 } 74 } 75 76 @Override 77 public void fling(int velocityX) { 78 Log.v(TAG, "-->fling velocityX:"+velocityX); 79 boolean change_flag = false; 80 if (velocityX > 0 && (currentPage < totalPages - 1)){ 81 currentPage++; 82 change_flag = true; 83 } else if (velocityX < 0 && (currentPage > 0)){ 84 currentPage--; 85 change_flag = true; 86 } 87 if (change_flag){ 88 int postionTo = (Integer)positionLeftTopOfPages.get(new Integer(currentPage)).intValue(); 89 Log.v(TAG, "------smoothScrollTo posx:"+postionTo); 90 smoothScrollTo(postionTo, 0); 91 updateDrawerPageLayout(totalPages, currentPage); 92 } 93 //super.fling(velocityX); 94 } 95 96 public void updateDrawerPageLayout(int total_pages, int sel_page) { 97 Log.e(TAG, "~~~updateBooksPageLayout total_pages:" + total_pages 98 + ",sel_page:" + sel_page); 99 mPageIndicLayout.removeAllViews(); 100 if (total_pages <= 0 || sel_page < 0 || sel_page >= total_pages) { 101 Log.e(TAG, "total_pages or sel_page is outofrange."); 102 return; 103 } 104 for (int i = 0; i < total_pages; i++) { 105 if (i != 0) { 106 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 107 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 108 params.setMargins(5, 0, 0, 0); 109 mPageIndicLayout.addView(new PageIndicatorView(mContext), params); 110 } else { 111 mPageIndicLayout.addView(new PageIndicatorView(mContext)); 112 } 113 } 114 PageIndicatorView selItem = (PageIndicatorView) mPageIndicLayout 115 .getChildAt(sel_page); 116 selItem.setSelectedView(true); 117 } 118 }
定制的表情页面,继承HorizontalScrollView,支持水平滑动。需要说明的是页面指示器的布局是通过代码实现的,而不是在xml文件中
4. ChatListViewAdapter.java
消息适配器。发送的消息存放在mDatalist中
在activity_chat.xml设置android:listSelector="#00FFFFFF"可以避免点击消息时出现的矩形框。
重写的getView中实现自己发送的消息显示在右边,别人发送的消息显示在左边。
TouchListener实现触摸消息变成密码形态,并在onclick方法中再次调用。我想大概是因为触摸和点击本来就不好区分吧。同时在点击事件里不要忘了设置消息显示的形态,因为listview在重绘时需要考虑这一点。
Adapter的作用就是ListView界面与数据之间的桥梁,当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View。
优化的思路两种:
1. View的重用
View的每次创建是比较耗时的,因此对于getview方法传入的convertView应充分利用 != null的判断
2.ViewHolder的应用
View的findViewById()方法也是比较耗时的,因此需要考虑只调用一次,ViewHolder就是一个持有者的类,他里面一般没有方法,只有属性,作用就是一个临时的储存器,把你getView方法中每次返回的View存起来,可以下次再用。这样做的好处就是不必每次都到布局文件中去拿到你的View,提高了效率。
5. Task.java定义了一些常量
6. TaskService.java 在Service中新开线程和直接新开线程的区别与意义
前面写到的蓝牙通信的几步走也是在这里面实现的
分为几个线程:
AcceptThread 等待客户端连接线程
ConnectThread(BluetoothDevice device) 作为客户端连接指定的蓝牙设备线程
ConnectedThread(BluetoothSocket socket) 在客户端,使用一个单独的BluetoothSocket类去初始化一个外接连接和管理该连接,发送消息也在这个线程里面
TaskThread总线程和mTaskList一起协调上面三个线程的运行工作。
要搞清TaskServic的原理,理清两个变量很重要:mServiceHandler mActivityHandler
mServiceHandler:监控着连接状态的变化,如断线,连接成功,连接中
mActivityHandler:监控着信息状态的变化,起到通知主Activity的作用。如:发消息,收消息。
7. SoundEffect.java
音效相关的类,实现了一个OnLoadCompleteListener接口,还有一个play() 方法,决定播放哪一首歌曲。
8. DataProtocol 和 Message
DataProtocol 是对不同的消息类型进行打包和解包
Message 定义了消息的几个参数
9. SelectDevice DownloadActivity ChatActivity 是应用程序里面的三个Activity
保存和恢复activity的状态数据
自己修改说明:
ChatActivity加入再按一次退出功能,更加人性化。加入消息记录功能。
消息记录实现概述:当在服务中开始收发发送消息时,就通过mActivityHandler通知ChatActivity,完成数据库读写操作。
ChatActivity.java:
1 package cn.com.farsgiht.bluetoothdemo; 2 3 import java.text.SimpleDateFormat; 4 import java.util.ArrayList; 5 import java.util.HashMap; 6 import android.app.Activity; 7 import android.app.AlertDialog; 8 import android.bluetooth.BluetoothAdapter; 9 import android.bluetooth.BluetoothDevice; 10 import android.content.ContentValues; 11 import android.content.Context; 12 import android.content.DialogInterface; 13 import android.content.DialogInterface.OnClickListener; 14 import android.content.Intent; 15 import android.database.sqlite.SQLiteDatabase; 16 import android.graphics.Bitmap; 17 import android.graphics.BitmapFactory; 18 import android.os.Bundle; 19 import android.os.Handler; 20 import android.os.Message; 21 import android.text.Spannable; 22 import android.text.SpannableString; 23 import android.text.style.ImageSpan; 24 import android.util.DisplayMetrics; 25 import android.util.Log; 26 import android.view.Display; 27 import android.view.Gravity; 28 import android.view.KeyEvent; 29 import android.view.LayoutInflater; 30 import android.view.Menu; 31 import android.view.MenuInflater; 32 import android.view.MenuItem; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.inputmethod.InputMethodManager; 36 import android.widget.AdapterView; 37 import android.widget.AdapterView.OnItemClickListener; 38 import android.widget.Button; 39 import android.widget.EditText; 40 import android.widget.GridView; 41 import android.widget.ImageView; 42 import android.widget.LinearLayout; 43 import android.widget.LinearLayout.LayoutParams; 44 import android.widget.ListView; 45 import android.widget.SimpleAdapter; 46 import android.widget.Toast; 47 import cn.com.farsgiht.bluetoothdemo.UI.ChatListViewAdapter; 48 import cn.com.farsgiht.bluetoothdemo.UI.DrawerHScrollView; 49 import cn.com.farsgiht.bluetoothdemo.sound.SoundEffect; 50 import cn.com.farsgiht.bluetoothdemo.task.Task; 51 import cn.com.farsgiht.bluetoothdemo.task.TaskService; 52 import cn.com.farsgiht.bluetoothdemo.utils.Utils; 53 54 public class ChatActivity extends Activity implements View.OnClickListener{ 55 private final String TAG = "ChatActivity"; 56 public static int sAliveCount = 0; 57 public static final String EXTRA_MESSAGER = "cn.com.farsgiht.bluetoothdemo.BUNDLE"; 58 public static final String DEVICE_NAME = "device_name"; 59 // 蓝牙状态变量 60 private static int sBTState = -1; 61 62 private final int REQUES_BT_ENABLE_CODE = 123; 63 private final int REQUES_SELECT_BT_CODE = 222; 64 65 private ListView mList; 66 private EditText mInput; 67 private Button mSendBtn; 68 private ImageView mEmoButton; 69 private GridView mGridView; 70 private boolean isUpdate = false; 71 private BluetoothDevice mRemoteDevice; 72 73 private LinearLayout mRootLayout, mChatLayout; 74 75 private View mEmoView; 76 private boolean isShowEmo = false; 77 private boolean isHaspressed = false; 78 private int mScrollHeight; 79 80 private DrawerHScrollView mScrollView; 81 private ChatListViewAdapter mAdapter2; 82 private ArrayList<HashMap<String, Object>> mChatContent2 = new ArrayList<HashMap<String, Object>>(); 83 private BluetoothAdapter mBluetoothAdapter; 84 85 private ArrayList<HashMap<String, Object>> mEmoList = new ArrayList<HashMap<String, Object>>(); 86 // 已连接设备的名字 87 private String mConnectedDeviceName = null; 88 89 private Handler mHandler = new Handler(){ 90 @Override 91 public void handleMessage(Message msg) { 92 //设置聊天信息的时间 93 SimpleDateFormat df0 = new SimpleDateFormat("MM-dd HH:mm:ss"); 94 String pdate=df0.format(System.currentTimeMillis()).toString(); 95 switch(msg.what){ 96 case -1: 97 showToast("没有连接其它用户,点击\"Menu\"扫描并选择周国用户"); 98 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_ERR); 99 break; 100 case Task.TASK_SEND_MSG: 101 // showToast(msg.obj.toString()); 102 String writeMessage = msg.obj.toString(); 103 if(writeMessage!=null && isHaspressed) 104 { 105 //将发送的信息插入到数据库 106 ContentValues values=new ContentValues(); 107 values.put("name", "我"); 108 values.put("pdate",pdate); 109 values.put("informations", writeMessage); 110 //创建数据库 111 DatabaseHelper insertdbHelper=new DatabaseHelper(ChatActivity.this,"zhsf_db"); 112 SQLiteDatabase insertdb=insertdbHelper.getWritableDatabase(); 113 insertdb.insert("info", null, values); 114 } 115 if(sAliveCount <= 0){ 116 Utils.notifyMessage(ChatActivity.this, msg.obj.toString(), ChatActivity.this); 117 } 118 break; 119 case Task.TASK_RECV_MSG: 120 String readMessage =((HashMap<String,Object>) msg.obj).get(ChatListViewAdapter.KEY_TEXT).toString(); 121 mConnectedDeviceName = ((HashMap<String,Object>) msg.obj).get(ChatListViewAdapter.KEY_NAME).toString(); 122 if(readMessage!=null) 123 { 124 //将接受的信息插入到数据库 125 ContentValues values2=new ContentValues(); 126 values2.put("name", mConnectedDeviceName); 127 values2.put("pdate",pdate); 128 values2.put("informations", readMessage); 129 DatabaseHelper insertdbHelper2=new DatabaseHelper(ChatActivity.this,"zhsf_db"); 130 SQLiteDatabase insertdb2=insertdbHelper2.getWritableDatabase(); 131 insertdb2.insert("info", null, values2); 132 } 133 134 if(msg.obj == null) 135 return; 136 if(msg.obj instanceof HashMap<?, ?>){ 137 showTargetMessage((HashMap<String, Object>) msg.obj); 138 } 139 if(sAliveCount <= 0){ 140 Utils.notifyMessage(ChatActivity.this, "您有未读取消息", ChatActivity.this); 141 } 142 break; 143 case Task.TASK_GET_REMOTE_STATE: 144 setTitle((String)msg.obj); 145 if(sAliveCount <= 0){ 146 if(isBTStateChanged(msg.arg1) && msg.arg1 != TaskService.BT_STAT_WAIT) 147 Utils.notifyMessage(ChatActivity.this, (String)msg.obj, ChatActivity.this); 148 } 149 break; 150 151 } 152 } 153 }; 154 155 @Override 156 protected void onCreate(Bundle savedInstanceState) { 157 super.onCreate(savedInstanceState); 158 setContentView(R.layout.activity_chat); 159 160 // 获得蓝牙管理器 161 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 162 if (mBluetoothAdapter == null) { 163 Log.e(TAG, "Your device is not support Bluetooth!"); 164 Toast.makeText(this, "该设备没有蓝牙设备", Toast.LENGTH_LONG).show(); 165 return; 166 } 167 168 mRootLayout = (LinearLayout) findViewById(R.id.root); 169 mChatLayout = (LinearLayout) findViewById(R.id.topPanel); 170 mList = (ListView) findViewById(R.id.listView1); 171 172 mAdapter2 = new ChatListViewAdapter(this, mChatContent2); 173 174 mList.setAdapter(mAdapter2); 175 176 // 初始化表情 177 mEmoView = initEmoView(); 178 179 mInput = (EditText) findViewById(R.id.inputEdit); 180 mInput.setOnClickListener(new android.view.View.OnClickListener() { 181 @Override 182 public void onClick(View v) { 183 // 点击输入框后,隐藏表情,显示输入法 184 InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 185 imm.showSoftInput(mInput, 0); 186 showEmoPanel(false); 187 } 188 }); 189 190 mSendBtn = (Button) findViewById(R.id.sendBtn); 191 mEmoButton = (ImageView) findViewById(R.id.emotionBtn); 192 193 mSendBtn.setOnClickListener(this); 194 mEmoButton.setOnClickListener(this); 195 196 //--------------------------------------------------------------------- 197 // 打开蓝牙设备 198 if (!mBluetoothAdapter.isEnabled()) { 199 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 200 startActivityForResult(enableBtIntent, REQUES_BT_ENABLE_CODE); 201 }else{ 202 // 默认设备作为服务端 203 startServiceAsServer(); 204 } 205 //--------------------------------------------------------------------- 206 } 207 208 private View initEmoView(){ 209 if(mEmoView == null){ 210 LayoutInflater inflater = getLayoutInflater(); 211 mEmoView = inflater.inflate(R.layout.emo_layout, null); 212 213 mScrollView = (DrawerHScrollView) mEmoView.findViewById(R.id.scrollView); 214 mGridView = (GridView) mEmoView.findViewById(R.id.gridView); 215 mGridView.setOnItemClickListener(new OnItemClickListener() { 216 @Override 217 public void onItemClick(AdapterView<?> parent, View view, 218 int position, long id) { 219 // 在android中要显示图片信息,必须使用Bitmap位图的对象来装载 220 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), (Integer) mEmoList.get(position).get("img")); 221 ImageSpan imageSpan = new ImageSpan(ChatActivity.this, bitmap); 222 SpannableString spannableString = new SpannableString((String) mEmoList.get(position).get("text"));//face就是图片的前缀名 223 spannableString.setSpan(imageSpan, 0, 8,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 224 mInput.append(spannableString); 225 System.out.println("mInput:"+mInput.getText()); 226 } 227 }); 228 229 mScrollHeight = setScrollGridView(mScrollView, mGridView, 3); 230 System.out.println("mScrollHeight:" + mScrollHeight); 231 } 232 return mEmoView; 233 } 234 235 236 237 private void startServiceAsServer(){ 238 TaskService.start(this, mHandler); 239 TaskService.newTask(new Task(mHandler, Task.TASK_START_ACCEPT, null)); 240 SoundEffect.getInstance(this).play(SoundEffect.SOUND_PLAY); 241 } 242 243 @Override 244 protected void onResume() { 245 sAliveCount++; 246 super.onResume(); 247 } 248 249 @Override 250 protected void onPause() { 251 sAliveCount--; 252 super.onPause(); 253 } 254 255 @Override 256 protected void onDestroy() { 257 super.onDestroy(); 258 // 关闭蓝牙 259 if(mBluetoothAdapter.isEnabled()) 260 mBluetoothAdapter.disable(); 261 // 停止服务 262 TaskService.stop(this); 263 } 264 265 266 @Override 267 public void onClick(View v) { 268 if(v == mSendBtn){ 269 String msg = mInput.getText().toString().trim(); 270 TaskService.newTask(new Task(mHandler, Task.TASK_GET_REMOTE_STATE, null));//通过点击按钮触发相应线程的启动,比较巧妙,值得学习 271 if(msg.length() == 0){ 272 showToast("聊天内容为空"); 273 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_ERR); 274 return; 275 } 276 277 //------ DEUBG ------ 278 TaskService.newTask(new Task(mHandler, Task.TASK_SEND_MSG, new Object[]{msg})); 279 showOwnMessage(msg);//立马显示自己发送的消息,所以在handler里面就没有再做处理 280 isHaspressed = true;//数据库可以开始记录消息啦 281 mInput.setText(""); 282 }else if(v == mEmoButton){ 283 System.out.println("Emo btn clicked"); 284 // 关闭输入法 285 InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 286 imm.hideSoftInputFromWindow(mInput.getWindowToken(),0); 287 if(isShowEmo){ 288 showEmoPanel(false); 289 }else{ 290 showEmoPanel(true); 291 } 292 } 293 } 294 295 private void showEmoPanel(boolean show){ 296 if(show && !isShowEmo){ 297 mEmoView.setVisibility(View.VISIBLE); 298 mEmoButton.setImageResource(R.drawable.emo_collapse); 299 ViewGroup.LayoutParams params = mChatLayout.getLayoutParams(); 300 params.height = mChatLayout.getHeight() - mScrollHeight; 301 mChatLayout.setLayoutParams(params); 302 isShowEmo = true; 303 }else if(!show && isShowEmo){ 304 mEmoView.setVisibility(View.GONE); 305 mEmoButton.setImageResource(R.drawable.emo_bkg); 306 ViewGroup.LayoutParams params = mChatLayout.getLayoutParams(); 307 params.height = mChatLayout.getHeight() + mScrollHeight; 308 mChatLayout.setLayoutParams(params); 309 isShowEmo = false; 310 } 311 if(!isUpdate && show){ 312 LinearLayout.LayoutParams para = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); 313 mRootLayout.addView(mEmoView, para); 314 isUpdate = true; 315 } 316 } 317 318 319 320 private boolean isBTStateChanged(int now){ 321 if(sBTState != now){ 322 sBTState = now; 323 return true; 324 }else{ 325 return false; 326 } 327 } 328 329 /** 330 * 显示对方信息 331 * @param data 332 */ 333 private void showTargetMessage(HashMap<String, Object> data){ 334 SimpleDateFormat df1 = new SimpleDateFormat("E MM月dd日 HH:mm "); 335 data.put(ChatListViewAdapter.KEY_DATE, df1.format(System.currentTimeMillis()).toString()); 336 data.put(ChatListViewAdapter.KEY_SHOW_MSG, true); 337 mChatContent2.add(data); 338 mAdapter2.notifyDataSetChanged(); 339 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_RECV); 340 } 341 342 /** 343 * 显示自己信息 344 * @param data 345 */ 346 private void showOwnMessage(String msg){ 347 HashMap<String, Object> map = new HashMap<String, Object>(); 348 map.put(ChatListViewAdapter.KEY_ROLE, ChatListViewAdapter.ROLE_OWN);//哪个角色的消息 349 map.put(ChatListViewAdapter.KEY_NAME, mBluetoothAdapter.getName()); 350 map.put(ChatListViewAdapter.KEY_TEXT, msg); 351 SimpleDateFormat df2 = new SimpleDateFormat("E MM月dd日 HH:mm "); 352 map.put(ChatListViewAdapter.KEY_DATE, df2.format(System.currentTimeMillis()).toString()); 353 map.put(ChatListViewAdapter.KEY_SHOW_MSG, true); 354 mChatContent2.add(map); 355 mAdapter2.notifyDataSetChanged(); 356 SoundEffect.getInstance(ChatActivity.this).play(SoundEffect.SOUND_SEND); 357 } 358 359 @Override 360 public boolean onCreateOptionsMenu(Menu menu) { 361 MenuInflater inflater = getMenuInflater(); 362 inflater.inflate(R.menu.option_menu, menu); 363 return true; 364 } 365 366 @Override 367 public boolean onOptionsItemSelected(MenuItem item) { 368 switch (item.getItemId()) { 369 case R.id.scan: 370 startActivityForResult(new Intent(this, SelectDevice.class), REQUES_SELECT_BT_CODE); 371 break; 372 case R.id.discoverable: 373 // 调用设置用户名方法 374 AlertDialog.Builder dlg = new AlertDialog.Builder(this); 375 final EditText devNameEdit = new EditText(this); 376 dlg.setView(devNameEdit); 377 dlg.setTitle("请输入用户名"); 378 dlg.setPositiveButton("设置", new OnClickListener() { 379 public void onClick(DialogInterface dialog, int which) { 380 if(devNameEdit.getText().toString().length() != 0) 381 mBluetoothAdapter.setName(devNameEdit.getText().toString()); 382 } 383 }); 384 dlg.create(); 385 dlg.show(); 386 return true; 387 case R.id.record: 388 Intent recordIntent = new Intent(ChatActivity.this, RecordListActivity.class); 389 startActivity(recordIntent); 390 return true; 391 case R.id.exit: 392 Intent aboutIntent = new Intent(ChatActivity.this, DownloadActivity.class); 393 startActivity(aboutIntent); 394 return true; 395 } 396 return false; 397 } 398 399 @Override 400 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 401 if(requestCode == REQUES_BT_ENABLE_CODE && resultCode == RESULT_OK){ 402 startServiceAsServer(); 403 }else if(requestCode == REQUES_SELECT_BT_CODE && resultCode == RESULT_OK){ 404 mRemoteDevice = data.getParcelableExtra("DEVICE"); 405 if(mRemoteDevice == null) 406 return; 407 TaskService.newTask(new Task(mHandler, Task.TASK_START_CONN_THREAD, new Object[]{mRemoteDevice})); 408 } 409 super.onActivityResult(requestCode, resultCode, data); 410 } 411 412 private void showToast(String msg){ 413 Toast tst = Toast.makeText(this, msg, Toast.LENGTH_SHORT); 414 tst.setGravity(Gravity.CENTER | Gravity.TOP, 0, 240); 415 tst.show(); 416 } 417 418 419 // 设置表情的多页滚动显示控件 420 public int setScrollGridView(DrawerHScrollView scrollView, GridView gridView, 421 int lines) { 422 423 DisplayMetrics dm = new DisplayMetrics(); 424 getWindowManager().getDefaultDisplay().getMetrics(dm); 425 Display display = getWindowManager().getDefaultDisplay(); 426 System.out.println("Width:" + display.getWidth()); 427 System.out.println("Height:" + display.getHeight()); 428 429 430 int scrollWid = display.getWidth(); 431 int scrollHei; 432 System.out.println("scrollWid:" + scrollWid); 433 if (scrollWid <= 0 ){ 434 Log.d(TAG, "scrollWid or scrollHei is less than 0"); 435 return 0; 436 } 437 438 439 float density = dm.density; // 屏幕密度(像素比例:0.75/1.0/1.5/2.0) 440 441 int readlViewWidht = 56; 442 // 图片都放在了Hdpi中,所以计算出图片的像素独立宽度 443 int viewWidth = (int) (readlViewWidht * density / 1.5); 444 int viewHeight = viewWidth; 445 System.out.println("viewWidth:" + viewWidth + " viewHeight:" + viewHeight); 446 447 int numColsPage = scrollWid / viewWidth; 448 int spaceing = (scrollWid - viewWidth * numColsPage)/(numColsPage); 449 System.out.println("Space:" + spaceing); 450 451 452 SimpleAdapter adapter = getEmoAdapter(); 453 int pages = adapter.getCount() / (numColsPage * lines); 454 455 if (pages * numColsPage * lines < adapter.getCount()){ 456 pages++; 457 } 458 459 System.out.println("pages:" + pages); 460 461 scrollHei = lines * viewHeight + spaceing * (lines + 1); 462 463 LayoutParams params = new LayoutParams(pages * scrollWid, LayoutParams.WRAP_CONTENT); 464 gridView.setLayoutParams(params); 465 gridView.setColumnWidth(viewWidth); 466 gridView.setHorizontalSpacing(spaceing); 467 gridView.setVerticalSpacing(spaceing); 468 gridView.setStretchMode(GridView.NO_STRETCH); 469 gridView.setNumColumns(numColsPage * pages); 470 471 //adapter = new DrawerListAdapter(this, colWid, colHei); 472 //listener = new DrawerItemClickListener(); 473 gridView.setAdapter(adapter); 474 //mGridView.setOnItemClickListener(listener); 475 476 scrollView.setParameters(pages, 0, scrollWid, spaceing); 477 //updateDrawerPageLayout(pageNum, 0); 478 // 表情区域还要加上分布显示区 479 int pageNumHei = (int) (18 * density); 480 return scrollHei + pageNumHei; 481 } 482 483 484 private SimpleAdapter getEmoAdapter(){ 485 HashMap<String, Object> map = new HashMap<String, Object>(); 486 map.put("img", R.drawable.emo001); 487 map.put("text", "<emo001>"); 488 mEmoList.add(map); 489 map = new HashMap<String, Object>(); 490 map.put("img", R.drawable.emo002); 491 map.put("text", "<emo002>"); 492 mEmoList.add(map); 493 map = new HashMap<String, Object>(); 494 map.put("img", R.drawable.emo003); 495 map.put("text", "<emo003>"); 496 mEmoList.add(map); 497 map = new HashMap<String, Object>(); 498 map.put("img", R.drawable.emo004); 499 map.put("text", "<emo004>"); 500 mEmoList.add(map); 501 map = new HashMap<String, Object>(); 502 map.put("img", R.drawable.emo005); 503 map.put("text", "<emo005>"); 504 mEmoList.add(map); 505 map = new HashMap<String, Object>(); 506 map.put("img", R.drawable.emo006); 507 map.put("text", "<emo006>"); 508 mEmoList.add(map); 509 map = new HashMap<String, Object>(); 510 map.put("img", R.drawable.emo007); 511 map.put("text", "<emo007>"); 512 mEmoList.add(map); 513 map = new HashMap<String, Object>(); 514 map.put("img", R.drawable.emo008); 515 map.put("text", "<emo008>"); 516 mEmoList.add(map); 517 map = new HashMap<String, Object>(); 518 map.put("img", R.drawable.emo009); 519 map.put("text", "<emo009>"); 520 mEmoList.add(map); 521 map = new HashMap<String, Object>(); 522 map.put("img", R.drawable.emo010); 523 map.put("text", "<emo010>"); 524 mEmoList.add(map); 525 map = new HashMap<String, Object>(); 526 map.put("img", R.drawable.emo011); 527 map.put("text", "<emo011>"); 528 mEmoList.add(map); 529 map = new HashMap<String, Object>(); 530 map.put("img", R.drawable.emo012); 531 map.put("text", "<emo012>"); 532 mEmoList.add(map); 533 map = new HashMap<String, Object>(); 534 map.put("img", R.drawable.emo013); 535 map.put("text", "<emo013>"); 536 mEmoList.add(map); 537 map = new HashMap<String, Object>(); 538 map.put("img", R.drawable.emo014); 539 map.put("text", "<emo014>"); 540 mEmoList.add(map); 541 map = new HashMap<String, Object>(); 542 map.put("img", R.drawable.emo015); 543 map.put("text", "<emo015>"); 544 mEmoList.add(map); 545 map = new HashMap<String, Object>(); 546 map.put("img", R.drawable.emo016); 547 map.put("text", "<emo016>"); 548 mEmoList.add(map); 549 map = new HashMap<String, Object>(); 550 map.put("img", R.drawable.emo017); 551 map.put("text", "<emo017>"); 552 mEmoList.add(map); 553 map = new HashMap<String, Object>(); 554 map.put("img", R.drawable.emo018); 555 map.put("text", "<emo018>"); 556 mEmoList.add(map); 557 map = new HashMap<String, Object>(); 558 map.put("img", R.drawable.emo019); 559 map.put("text", "<emo019>"); 560 mEmoList.add(map); 561 map = new HashMap<String, Object>(); 562 map.put("img", R.drawable.emo020); 563 map.put("text", "<emo020>"); 564 mEmoList.add(map); 565 map = new HashMap<String, Object>(); 566 map.put("img", R.drawable.emo021); 567 map.put("text", "<emo021>"); 568 mEmoList.add(map); 569 map = new HashMap<String, Object>(); 570 map.put("img", R.drawable.emo022); 571 map.put("text", "<emo022>"); 572 mEmoList.add(map); 573 map = new HashMap<String, Object>(); 574 map.put("img", R.drawable.emo023); 575 map.put("text", "<emo023>"); 576 mEmoList.add(map); 577 map = new HashMap<String, Object>(); 578 map.put("img", R.drawable.emo024); 579 map.put("text", "<emo024>"); 580 mEmoList.add(map); 581 map = new HashMap<String, Object>(); 582 map.put("img", R.drawable.emo025); 583 map.put("text", "<emo025>"); 584 mEmoList.add(map); 585 map = new HashMap<String, Object>(); 586 map.put("img", R.drawable.emo026); 587 map.put("text", "<emo026>"); 588 mEmoList.add(map); 589 map = new HashMap<String, Object>(); 590 map.put("img", R.drawable.emo027); 591 map.put("text", "<emo027>"); 592 mEmoList.add(map); 593 map = new HashMap<String, Object>(); 594 map.put("img", R.drawable.emo028); 595 map.put("text", "<emo028>"); 596 mEmoList.add(map); 597 map = new HashMap<String, Object>(); 598 map.put("img", R.drawable.emo029); 599 map.put("text", "<emo029>"); 600 mEmoList.add(map); 601 map = new HashMap<String, Object>(); 602 map.put("img", R.drawable.emo030); 603 map.put("text", "<emo030>"); 604 mEmoList.add(map); 605 map = new HashMap<String, Object>(); 606 map.put("img", R.drawable.emo031); 607 map.put("text", "<emo031>"); 608 mEmoList.add(map); 609 map = new HashMap<String, Object>(); 610 map.put("img", R.drawable.emo032); 611 map.put("text", "<emo032>"); 612 mEmoList.add(map); 613 map = new HashMap<String, Object>(); 614 map.put("img", R.drawable.emo033); 615 map.put("text", "<emo033>"); 616 mEmoList.add(map); 617 map = new HashMap<String, Object>(); 618 map.put("img", R.drawable.emo034); 619 map.put("text", "<emo034>"); 620 mEmoList.add(map); 621 map = new HashMap<String, Object>(); 622 map.put("img", R.drawable.emo035); 623 map.put("text", "<emo035>"); 624 mEmoList.add(map); 625 map = new HashMap<String, Object>(); 626 map.put("img", R.drawable.emo036); 627 map.put("text", "<emo036>"); 628 mEmoList.add(map); 629 map = new HashMap<String, Object>(); 630 map.put("img", R.drawable.emo037); 631 map.put("text", "<emo037>"); 632 mEmoList.add(map); 633 map = new HashMap<String, Object>(); 634 map.put("img", R.drawable.emo038); 635 map.put("text", "<emo038>"); 636 mEmoList.add(map); 637 map = new HashMap<String, Object>(); 638 map.put("img", R.drawable.emo039); 639 map.put("text", "<emo039>"); 640 mEmoList.add(map); 641 map = new HashMap<String, Object>(); 642 map.put("img", R.drawable.emo040); 643 map.put("text", "<emo040>"); 644 mEmoList.add(map); 645 map = new HashMap<String, Object>(); 646 map.put("img", R.drawable.emo041); 647 map.put("text", "<emo041>"); 648 mEmoList.add(map); 649 map = new HashMap<String, Object>(); 650 map.put("img", R.drawable.emo042); 651 map.put("text", "<emo042>"); 652 mEmoList.add(map); 653 map = new HashMap<String, Object>(); 654 map.put("img", R.drawable.emo043); 655 map.put("text", "<emo043>"); 656 mEmoList.add(map); 657 map = new HashMap<String, Object>(); 658 map.put("img", R.drawable.emo044); 659 map.put("text", "<emo044>"); 660 mEmoList.add(map); 661 map = new HashMap<String, Object>(); 662 map.put("img", R.drawable.emo045); 663 map.put("text", "<emo045>"); 664 mEmoList.add(map); 665 map = new HashMap<String, Object>(); 666 map.put("img", R.drawable.emo046); 667 map.put("text", "<emo046>"); 668 mEmoList.add(map); 669 map = new HashMap<String, Object>(); 670 map.put("img", R.drawable.emo047); 671 map.put("text", "<emo047>"); 672 mEmoList.add(map); 673 map = new HashMap<String, Object>(); 674 map.put("img", R.drawable.emo048); 675 map.put("text", "<emo048>"); 676 mEmoList.add(map); 677 map = new HashMap<String, Object>(); 678 map.put("img", R.drawable.emo049); 679 map.put("text", "<emo049>"); 680 mEmoList.add(map); 681 map = new HashMap<String, Object>(); 682 map.put("img", R.drawable.emo050); 683 map.put("text", "<emo050>"); 684 mEmoList.add(map); 685 map = new HashMap<String, Object>(); 686 map.put("img", R.drawable.emo051); 687 map.put("text", "<emo051>"); 688 mEmoList.add(map); 689 map = new HashMap<String, Object>(); 690 map.put("img", R.drawable.emo052); 691 map.put("text", "<emo052>"); 692 mEmoList.add(map); 693 map = new HashMap<String, Object>(); 694 map.put("img", R.drawable.emo053); 695 map.put("text", "<emo053>"); 696 mEmoList.add(map); 697 map = new HashMap<String, Object>(); 698 map.put("img", R.drawable.emo054); 699 map.put("text", "<emo054>"); 700 mEmoList.add(map); 701 map = new HashMap<String, Object>(); 702 map.put("img", R.drawable.emo055); 703 map.put("text", "<emo055>"); 704 mEmoList.add(map); 705 map = new HashMap<String, Object>(); 706 map.put("img", R.drawable.emo056); 707 map.put("text", "<emo056>"); 708 mEmoList.add(map); 709 map = new HashMap<String, Object>(); 710 map.put("img", R.drawable.emo057); 711 map.put("text", "<emo057>"); 712 mEmoList.add(map); 713 map = new HashMap<String, Object>(); 714 map.put("img", R.drawable.emo058); 715 map.put("text", "<emo058>"); 716 mEmoList.add(map); 717 map = new HashMap<String, Object>(); 718 map.put("img", R.drawable.emo059); 719 map.put("text", "<emo059>"); 720 mEmoList.add(map); 721 map = new HashMap<String, Object>(); 722 map.put("img", R.drawable.emo060); 723 map.put("text", "<emo060>"); 724 mEmoList.add(map); 725 726 /** 727 * 上述添加表情效率高,但是代码太冗余,下面的方式代码简单,但是效率较低 728 */ 729 /* 730 HashMap<String, Integer> map; 731 for(int i = 0; i < 100; i++){ 732 map = new HashMap<String, Integer>(); 733 Field field=R.drawable.class.getDeclaredField("image"+i); 734 int resourceId=Integer.parseInt(field.get(null).toString()); 735 map.put("img", resourceId); 736 mEmoList.add(map); 737 } 738 */ 739 return new SimpleAdapter(this, mEmoList, R.layout.grid_view_item, 740 new String[]{"img"}, new int[]{R.id.imageView}); 741 } 742 743 744 long waitTime = 2000; 745 long touchTime = 0; 746 @Override 747 public boolean onKeyDown(int keyCode, KeyEvent event) { 748 if(event.getAction() == KeyEvent.ACTION_DOWN && KeyEvent.KEYCODE_BACK == keyCode) { 749 long currentTime = System.currentTimeMillis(); 750 if((currentTime-touchTime)>=waitTime) { 751 Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show(); 752 touchTime = currentTime; 753 }else { 754 finish(); 755 } 756 return true; 757 } 758 return super.onKeyDown(keyCode, event); 759 } 760 761 }
TaskService.java:
1 package cn.com.farsgiht.bluetoothdemo.task; 2 3 import java.io.BufferedInputStream; 4 import java.io.BufferedOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.io.UnsupportedEncodingException; 9 import java.util.ArrayList; 10 import java.util.Calendar; 11 import java.util.HashMap; 12 import java.util.UUID; 13 import android.app.Service; 14 import android.bluetooth.BluetoothAdapter; 15 import android.bluetooth.BluetoothDevice; 16 import android.bluetooth.BluetoothServerSocket; 17 import android.bluetooth.BluetoothSocket; 18 import android.content.ContentValues; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.database.sqlite.SQLiteDatabase; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.util.Log; 26 import android.widget.ArrayAdapter; 27 import android.widget.Toast; 28 import cn.com.farsgiht.bluetoothdemo.ChatActivity; 29 import cn.com.farsgiht.bluetoothdemo.UI.ChatListViewAdapter; 30 import cn.com.farsgiht.bluetoothdemo.protocol.DataProtocol; 31 import cn.com.farsgiht.bluetoothdemo.protocol.Message; 32 import cn.com.farsgiht.bluetoothdemo.sound.SoundEffect; 33 34 /** 35 * 任务处理服务 36 * @author Administrator 37 */ 38 public class TaskService extends Service { 39 public static final int BT_STAT_WAIT = 0; 40 public static final int BT_STAT_CONN = 1; 41 public static final int BT_STAT_ONLINE = 2; 42 public static final int BT_STAT_UNKNOWN = 3; 43 44 45 public static final String DEVICE_NAME = "device_name"; 46 47 private final String TAG = "TaskService"; 48 private TaskThread mThread; 49 50 private BluetoothAdapter mBluetoothAdapter; 51 52 private AcceptThread mAcceptThread; 53 private ConnectThread mConnectThread; 54 private ConnectedThread mCommThread; 55 56 private boolean isServerMode = true; 57 58 private static Handler mActivityHandler; 59 60 // 任务队列 61 private static ArrayList<Task> mTaskList = new ArrayList<Task>(); 62 63 private Handler mServiceHandler = new Handler() { 64 @Override 65 public void handleMessage(android.os.Message msg) { 66 switch (msg.what) { 67 case Task.TASK_GET_REMOTE_STATE: 68 android.os.Message activityMsg = mActivityHandler.obtainMessage(); 69 activityMsg.what = msg.what; 70 if (mAcceptThread != null && mAcceptThread.isAlive()) { 71 activityMsg.obj = "等待连接..."; 72 activityMsg.arg1 = BT_STAT_WAIT; 73 } else if (mCommThread != null && mCommThread.isAlive()) { 74 activityMsg.obj = mCommThread.getRemoteName() + "[在线]"; 75 activityMsg.arg1 = BT_STAT_ONLINE; 76 } else if (mConnectThread != null && mConnectThread.isAlive()) { 77 SoundEffect.getInstance(TaskService.this).play(3); 78 activityMsg.obj = "正在连接:" 79 + mConnectThread.getDevice().getName(); 80 activityMsg.arg1 = BT_STAT_CONN; 81 } else { 82 activityMsg.obj = "未知状态"; 83 activityMsg.arg1 = BT_STAT_UNKNOWN; 84 SoundEffect.getInstance(TaskService.this).play(2); 85 // 重新等待连接 86 mAcceptThread = new AcceptThread(); 87 mAcceptThread.start(); 88 isServerMode = true; 89 } 90 91 mActivityHandler.sendMessage(activityMsg); 92 break; 93 default: 94 break; 95 } 96 super.handleMessage(msg); 97 } 98 }; 99 100 101 102 @Override 103 public void onCreate() { 104 super.onCreate(); 105 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 106 if (mBluetoothAdapter == null) { 107 Log.e(TAG, "Your device is not support Bluetooth!"); 108 return; 109 } 110 mThread = new TaskThread(); 111 mThread.start(); 112 } 113 114 115 public static void start(Context c, Handler handler){ 116 mActivityHandler = handler; 117 Intent intent = new Intent(c, TaskService.class); 118 c.startService(intent); 119 } 120 121 public static void stop(Context c){ 122 Intent intent = new Intent(c, TaskService.class); 123 c.stopService(intent); 124 } 125 126 127 128 public static void newTask(Task target) { 129 synchronized (mTaskList) { 130 mTaskList.add(target); 131 } 132 } 133 134 private class TaskThread extends Thread { 135 private boolean isRun = true; 136 private int mCount = 0; 137 138 public void cancel() { 139 isRun = false; 140 } 141 142 @Override 143 public void run() { 144 Task task; 145 while (isRun) { 146 147 // 有任务 148 if (mTaskList.size() > 0) { 149 synchronized (mTaskList) { 150 // 获得任务 151 task = mTaskList.get(0); 152 doTask(task); 153 } 154 } else { 155 try { 156 Thread.sleep(200); 157 mCount++; 158 } catch (InterruptedException e) { 159 } 160 // 每过10秒钟进行一次状态检查 161 if (mCount >= 50) { 162 mCount = 0; 163 // 检查远程设备状态 164 android.os.Message handlerMsg = mServiceHandler 165 .obtainMessage(); 166 handlerMsg.what = Task.TASK_GET_REMOTE_STATE; 167 mServiceHandler.sendMessage(handlerMsg); 168 } 169 } 170 } 171 } 172 173 } 174 //对应三个线程,其中mCommThread是在mConnectThread的run()方法中new出来的 175 private void doTask(Task task) { 176 switch (task.getTaskID()) { 177 case Task.TASK_START_ACCEPT: 178 mAcceptThread = new AcceptThread(); 179 mAcceptThread.start(); 180 isServerMode = true; 181 break; 182 case Task.TASK_START_CONN_THREAD: 183 if (task.mParams == null || task.mParams.length == 0) { 184 break; 185 } 186 BluetoothDevice remote = (BluetoothDevice) task.mParams[0]; 187 mConnectThread = new ConnectThread(remote); 188 mConnectThread.start(); 189 isServerMode = false; 190 break; 191 case Task.TASK_SEND_MSG: 192 boolean sucess = false; 193 if (mCommThread == null || !mCommThread.isAlive() 194 || task.mParams == null || task.mParams.length == 0) { 195 Log.e(TAG, "mCommThread or task.mParams null"); 196 }else{ 197 byte[] msg = null; 198 try { 199 200 msg = DataProtocol.packMsg((String) task.mParams[0]); 201 sucess = mCommThread.write(msg); 202 203 } catch (UnsupportedEncodingException e) { 204 sucess = false; 205 } 206 } 207 if (!sucess) { 208 android.os.Message returnMsg = mActivityHandler.obtainMessage(); 209 returnMsg.what = Task.TASK_SEND_MSG_FAIL; 210 returnMsg.obj = "消息发送失败"; 211 mActivityHandler.sendMessage(returnMsg); 212 } 213 break; 214 } 215 216 // 移除任务 217 mTaskList.remove(task);//每次保证任务列表里面只有一个任务,task = mTaskList.get(0); 218 } 219 220 @Override 221 public void onDestroy() { 222 super.onDestroy(); 223 mThread.cancel(); 224 } 225 226 private final String UUID_STR = "00001101-0000-1000-8000-00805F9B34FB"; 227 228 /** 229 * 等待客户端连接线程 230 * 231 * @author Administrator 232 */ 233 private class AcceptThread extends Thread { 234 private final BluetoothServerSocket mmServerSocket; 235 private boolean isCancel = false; 236 237 public AcceptThread() { 238 Log.d(TAG, "AcceptThread"); 239 BluetoothServerSocket tmp = null; 240 try { 241 tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord( 242 "MT_Chat_Room", UUID.fromString(UUID_STR)); 243 } catch (IOException e) { 244 } 245 mmServerSocket = tmp; 246 } 247 248 public void run() { 249 BluetoothSocket socket = null; 250 while (true) { 251 try { 252 // 阻塞等待 253 socket = mmServerSocket.accept(); 254 } catch (IOException e) { 255 if (!isCancel) { 256 try { 257 mmServerSocket.close(); 258 } catch (IOException e1) { 259 } 260 mAcceptThread = new AcceptThread(); 261 mAcceptThread.start(); 262 isServerMode = true; 263 } 264 break; 265 } 266 if (socket != null) { 267 manageConnectedSocket(socket); 268 try { 269 mmServerSocket.close(); 270 } catch (IOException e) { 271 } 272 mAcceptThread = null; 273 break; 274 } 275 } 276 } 277 278 public void cancel() { 279 try { 280 Log.d(TAG, "AcceptThread canceled"); 281 isCancel = true; 282 isServerMode = false; 283 mmServerSocket.close(); 284 mAcceptThread = null; 285 if (mCommThread != null && mCommThread.isAlive()) { 286 mCommThread.cancel(); 287 } 288 } catch (IOException e) { 289 } 290 } 291 } 292 293 /** 294 * 作为客户端连接指定的蓝牙设备线程 295 * 296 * @author Administrator 297 */ 298 private class ConnectThread extends Thread { 299 private final BluetoothSocket mmSocket; 300 private final BluetoothDevice mmDevice; 301 302 public ConnectThread(BluetoothDevice device) { 303 304 Log.d(TAG, "ConnectThread"); 305 306 if (mAcceptThread != null && mAcceptThread.isAlive()) { 307 mAcceptThread.cancel(); 308 } 309 310 if (mCommThread != null && mCommThread.isAlive()) { 311 mCommThread.cancel(); 312 } 313 314 // Use a temporary object that is later assigned to mmSocket, 315 // because mmSocket is final 316 BluetoothSocket tmp = null; 317 mmDevice = device; 318 try { 319 tmp = device.createRfcommSocketToServiceRecord(UUID 320 .fromString(UUID_STR)); 321 } catch (IOException e) { 322 Log.d(TAG, "createRfcommSocketToServiceRecord error!"); 323 } 324 325 mmSocket = tmp; 326 } 327 328 public BluetoothDevice getDevice() { 329 return mmDevice; 330 } 331 332 public void run() { 333 // Cancel discovery because it will slow down the connection 334 mBluetoothAdapter.cancelDiscovery(); 335 try { 336 // Connect the device through the socket. This will block 337 // until it succeeds or throws an exception 338 mmSocket.connect(); 339 } catch (IOException connectException) { 340 // Unable to connect; close the socket and get out 341 Log.e(TAG, "Connect server failed"); 342 try { 343 mmSocket.close(); 344 } catch (IOException closeException) { 345 } 346 mAcceptThread = new AcceptThread(); 347 mAcceptThread.start(); 348 isServerMode = true; 349 return; 350 } // Do work to manage the connection (in a separate thread) 351 manageConnectedSocket(mmSocket); 352 } 353 354 public void cancel() { 355 try { 356 mmSocket.close(); 357 } catch (IOException e) { 358 } 359 mConnectThread = null; 360 } 361 } 362 363 private void manageConnectedSocket(BluetoothSocket socket) { 364 // 启动子线程来维持连接 365 mCommThread = new ConnectedThread(socket); 366 mCommThread.start(); 367 } 368 369 private class ConnectedThread extends Thread { 370 private final BluetoothSocket mmSocket; 371 private final InputStream mmInStream; 372 private final OutputStream mmOutStream; 373 private BufferedOutputStream mmBos; 374 private byte[] buffer; 375 376 public ConnectedThread(BluetoothSocket socket) { 377 Log.d(TAG, "ConnectedThread"); 378 mmSocket = socket; 379 InputStream tmpIn = null; 380 OutputStream tmpOut = null; 381 try { 382 tmpIn = socket.getInputStream(); 383 tmpOut = socket.getOutputStream(); 384 } catch (IOException e) { 385 } 386 mmInStream = tmpIn; 387 mmOutStream = tmpOut; 388 mmBos = new BufferedOutputStream(mmOutStream); 389 } 390 391 public OutputStream getOutputStream() { 392 return mmOutStream; 393 } 394 395 public boolean write(byte[] msg) { 396 if (msg == null) 397 return false; 398 try { 399 mmBos.write(msg); 400 mmBos.flush(); 401 402 mActivityHandler.obtainMessage(Task.TASK_SEND_MSG, -1, -1, new String(msg)).sendToTarget(); 403 System.out.println("Write:" + msg); 404 } catch (IOException e) { 405 return false; 406 } 407 return true; 408 } 409 410 public String getRemoteName() { 411 return mmSocket.getRemoteDevice().getName(); 412 } 413 414 415 416 417 public void cancel() { 418 try { 419 mmSocket.close(); 420 } catch (IOException e) { 421 } 422 mCommThread = null; 423 } 424 425 public void run() { 426 try { 427 write(DataProtocol.packMsg(mBluetoothAdapter.getName() 428 + "已经上线\n"));//获取本地蓝牙适配器的蓝牙名称,这一条消息默认发送出去啦, 429 //但消息记录里面不应该有这条消息,消息记录里面记录按过发送键的消息 430 } catch (UnsupportedEncodingException e2) { 431 } 432 int size; 433 Message msg; 434 android.os.Message handlerMsg; 435 buffer = new byte[1024]; 436 437 BufferedInputStream bis = new BufferedInputStream(mmInStream); 438 // BufferedReader br = new BufferedReader(new 439 // InputStreamReader(mmInStream)); 440 HashMap<String, Object> data; 441 while (true) { 442 try { 443 size = bis.read(buffer); 444 msg = DataProtocol.unpackData(buffer); 445 if (msg == null) 446 continue; 447 448 if (mActivityHandler == null) { 449 return; 450 } 451 452 msg.remoteDevName = mmSocket.getRemoteDevice().getName();//得到对方设备的名字 453 if (msg.type == DataProtocol.TYPE_FILE) { 454 // 文件接收处理忽略 455 456 } else if (msg.type == DataProtocol.TYPE_MSG) { 457 data = new HashMap<String, Object>(); 458 System.out.println("Read data."); 459 data.put(ChatListViewAdapter.KEY_ROLE, 460 ChatListViewAdapter.ROLE_TARGET); 461 data.put(ChatListViewAdapter.KEY_NAME, 462 msg.remoteDevName); 463 data.put(ChatListViewAdapter.KEY_TEXT, msg.msg); 464 465 // 通过Activity更新到UI上 466 handlerMsg = mActivityHandler.obtainMessage(); 467 handlerMsg.what = Task.TASK_RECV_MSG; 468 handlerMsg.obj = data; 469 mActivityHandler.sendMessage(handlerMsg); 470 } 471 } catch (IOException e) { 472 try { 473 mmSocket.close(); 474 } catch (IOException e1) { 475 } 476 mCommThread = null; 477 if (isServerMode) { 478 // 检查远程设备状态 479 handlerMsg = mServiceHandler.obtainMessage(); 480 handlerMsg.what = Task.TASK_GET_REMOTE_STATE; 481 mServiceHandler.sendMessage(handlerMsg); 482 SoundEffect.getInstance(TaskService.this).play(2); 483 mAcceptThread = new AcceptThread(); 484 mAcceptThread.start(); 485 } 486 break; 487 } 488 } 489 } 490 } 491 492 // ================================================================ 493 494 @Override 495 public IBinder onBind(Intent intent) { 496 // TODO Auto-generated method stub 497 return null; 498 } 499 500 }
实现效果: