参考:
http://blog.csdn.net/zhanghua1121/article/details/9331363
http://blog.csdn.net/wyzxk888/article/details/7543364
http://blog.chinaunix.net/uid-7552018-id-173419.html
BluetoothChat.java
例程的主 Activity 。
BluetoothChat:
主界面,
显示聊天信息BluetoothChatService:
里面有3个主要线程类:
AcceptThread:蓝牙服务端socket监听线程.。
ConnectThread:蓝牙socket连接线程。
ConnectedThread:连接后的通信线程DeviceListActivity:
蓝牙扫描选择界面,负责传回选择连接的设备BluetoothChat:
定义了很多常量,用于处理消息和请求:
1、BluetoothChat的成员变量
其中Debugging部分则将用于我们在调试程序时通过log打印日志用,其他部分我们都加入了注释,需要说明的是BluetoothChatService ,它是我们自己定义的一个用来管理蓝牙的端口监听,链接,管理聊天的程序,后面我们会介绍。在这里需要说明一点,这些代码都出自google的员工之手,大家在学习时,可以借鉴很多代码编写的技巧和风格,这都将对我们有非常大的帮助。
2、onCreate()
1、main及自定义title布局
1、onCreate函数中对窗口进行了设置,窗口风格设置为自定义风格,并且指定了自定义title布局为custom_title.xml
[html] view
plaincopy
// Set up the window layout
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.main);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
1、该布局将title设置为一个相对布局RelativeLayout,其中包含了两个TextView,一个在左边一个在右边,分别用于显示应用程序的标题title和当前的蓝牙配对链接名称,如下图所示。
其中左边显示为应用程序名称"BluetoothChat",右边显示一个connected:scort,则表示当前配对成功正在进行聊天的链接名称
2、整个聊天界面的布局在main.xml中实现:采用一个线性布局LinearLayout,其中包含了另一个ListView(用于显示聊天的对话信息)和另外一个线性布局来实现一个发送信息的窗口,发送消息发送框有一个输入框和一个发送按钮构成。整个界面如下图所示。
2、设置自定义title布局
我们就需要对界面进行一些设置,如下代码将用来设置我们自定义的标题title需要显示的内容:
[html] view
plaincopy
1.// 设置自定义title布局
2. mTitle = (TextView) findViewById(R.id.title_left_text);
3. mTitle.setText(R.string.app_name);
4. mTitle = (TextView) findViewById(R.id.title_right_text);
左边的TextView被设置为显示应用程序名称,右边的则需要我们在链接之后在设置更新,目前则显示没有链接字样,所以这里我们暂不设置
3、蓝牙适配器BluetoothAdapter
进一步就需要获取本地蓝牙适配器BluetoothAdapter了,因为对于有关蓝牙的任何操作都需要首先获得该蓝牙适配器,获取代码非常简单,如下
getDefaultAdapter()函数用于获取本地蓝牙适配器,然后检测是否为null,如果为null则表示没有蓝牙设备的支持,将通过toast告知用户。
onStart()
在onStart()函数中,我们将检测蓝牙是否被打开,如果没有打开,则请求打开,否则就可以设置一些聊天信息的准备工作,代码如下
3、控件
一个显示连接到某个设备的TextView,
一个用于显示对话的ListView,
一个用于输入聊天内容的EditText,
一个发送按钮Button
1、 在EditText注册一个监听器(另外)
实现了软键盘按回车return键发送消息(一般我们在EditText里按return键是换行)
[html] view
plaincopy
01.// 用于监听EditText的一个return键事件
02.private TextView.OnEditorActionListener mWriteListener =
- new TextView.OnEditorActionListener() {
- public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
- // If the action is a key-up event on the return key, send the message
- if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
- String message = view.getText().toString();
- sendMessage(message);
- }
- if(D) Log.i(TAG, "END onEditorAction");
- return true;
- }
13.};
得到本地 BluetoothAdapter 设备,检查是否支持。
onStart() 中检查是否启用蓝牙,并请求启用,然后执行 setupChat() 。
setupChat() 中先对界面中的控件进行初始化增加点击监听器等,然创建 BluetoothChatService 对象,该对象在整个应用过程中存在,并执行蓝牙连接建立、消息发送接受等实际的行为。
首先,程序启动进入BluetoothChat,在onCreate函数中对窗口进行了设置,代码如下:
// 设置窗口布局
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.main);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
[java] view
plaincopy
// 设置窗口布局 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
这里可以看到将窗口风格设置为自定义风格了,并且指定了自定义title布局为custom_title,其定义代码如下:
view
plaincopy
to clipboardprint?
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
>
<TextView android:id="@+id/title_left_text"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:singleLine="true"
style="?android:attr/windowTitleStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_right_text"
android:layout_alignParentRight="true"
android:ellipsize="end"
android:singleLine="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="#fff"
android:layout_weight="1"
/>
</RelativeLayout>
[java] view
plaincopy
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" > <TextView android:id="@+id/title_left_text" android:layout_alignParentLeft="true" android:ellipsize="end" android:singleLine="true" style="?android:attr/windowTitleStyle" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" /> <TextView android:id="@+id/title_right_text" android:layout_alignParentRight="true" android:ellipsize="end" android:singleLine="true" android:layout_width="wrap_content" android:layout_height="match_parent" android:textColor="#fff" android:layout_weight="1" /> </RelativeLayout>
该布局将title设置为一个相对布局RelativeLayout,其中包含了两个TextView,一个在左边一个在右边,分别用于显示应用程序的标题title和当前的蓝牙配对链接名称,如下图所示。
其中左边显示为应用程序名称"BluetoothChat",右边显示一个connected:scort则表示当前配对成功正在进行聊天的链接名称。整个聊天界面的布局在main.xml中实现,代码如下:
view
plaincopy
to clipboardprint?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- 显示设备列表 -->
<ListView android:id="@+id/in"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true"
android:transcriptMode="alwaysScroll"
android:layout_weight="1"
/>
<!-- 显示发送消息的编辑框 -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText android:id="@+id/edit_text_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="bottom"
/>
<Button android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send"
/>
</LinearLayout>
</LinearLayout>
[java] view
plaincopy
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- 显示设备列表 --> <ListView android:id="@+id/in" android:layout_width="match_parent" android:layout_height="match_parent" android:stackFromBottom="true" android:transcriptMode="alwaysScroll" android:layout_weight="1" /> <!-- 显示发送消息的编辑框 --> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/edit_text_out" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="bottom" /> <Button android:id="@+id/button_send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/send" /> </LinearLayout> </LinearLayout>
整个界面的布局将是一个线性布局LinearLayout,其中包含了另一个ListView(用于显示聊天的对话信息)和另外一个线性布局来实现一个发送信息的窗口,发送消息发送框有一个输入框和一个发送按钮构成。整个界面如下图所示。
布局好界面,下面我们需要进入编码状态,首先看BluetoothChat所要那些成员变量,如下代码所示:
view
plaincopy
to clipboardprint?
public class BluetoothChatextends Activity
{
// Debugging
private static final String
TAG ="BluetoothChat";
private staticfinalboolean D
=true;
//从BluetoothChatService Handler发送的消息类型
public staticfinalint MESSAGE_STATE_CHANGE
=1;
public staticfinalint MESSAGE_READ
=2;
public staticfinalint MESSAGE_WRITE
=3;
public staticfinalint MESSAGE_DEVICE_NAME
=4;
public staticfinalint MESSAGE_TOAST
=5;
// 从BluetoothChatService Handler接收消息时使用的键名(键-值模型)
public staticfinal String
DEVICE_NAME ="device_name";
public staticfinal String
TOAST ="toast";
// Intent请求代码(请求链接,请求可见)
private staticfinalint REQUEST_CONNECT_DEVICE
=1;
private static finalint REQUEST_ENABLE_BT
=2;
// Layout Views
private TextView
mTitle;
private ListView
mConversationView;
private EditText
mOutEditText;
private Button
mSendButton;
// 链接的设备的名称
private String
mConnectedDeviceName =null;
// Array adapter for the conversation thread
private ArrayAdapter<String>
mConversationArrayAdapter;
// 将要发送出去的字符串
private StringBuffer
mOutStringBuffer;
// 本地蓝牙适配器
private BluetoothAdapter
mBluetoothAdapter =null;
// 聊天服务的对象
private BluetoothChatService
mChatService =null;
//......
[java] view
plaincopy
public class BluetoothChat extends Activity { // Debugging private static final String TAG = "BluetoothChat"; private static final boolean D = true; //从BluetoothChatService Handler发送的消息类型 public static final int MESSAGE_STATE_CHANGE = 1; public static final int MESSAGE_READ = 2; public static final int MESSAGE_WRITE = 3; public static final int MESSAGE_DEVICE_NAME = 4; public static final int MESSAGE_TOAST = 5; // 从BluetoothChatService Handler接收消息时使用的键名(键-值模型) public static final String DEVICE_NAME = "device_name"; public static final String TOAST = "toast"; // Intent请求代码(请求链接,请求可见) private static final int REQUEST_CONNECT_DEVICE = 1; private static final int REQUEST_ENABLE_BT = 2; // Layout Views private TextView mTitle; private ListView mConversationView; private EditText mOutEditText; private Button mSendButton; // 链接的设备的名称 private String mConnectedDeviceName = null; // Array adapter for the conversation thread private ArrayAdapter<String> mConversationArrayAdapter; // 将要发送出去的字符串 private StringBuffer mOutStringBuffer; // 本地蓝牙适配器 private BluetoothAdapter mBluetoothAdapter = null; // 聊天服务的对象 private BluetoothChatService mChatService = null; //......
其中Debugging部分则将用于我们在调试程序时通过log打印日志用,其他部分我们都加入了注释,需要说明的是BluetoothChatService ,它是我们自己定义的一个用来管理蓝牙的端口监听,链接,管理聊天的程序,后面我们会介绍。在这里需要说明一点,这些代码都出自google的员工之手,大家在学习时,可以借鉴很多代码编写的技巧和风格,这都将对我们有非常大的帮助。
然后,我们就需要对界面进行一些设置,如下代码将用来设置我们自定义的标题title需要显示的内容:
view
plaincopy
to clipboardprint?
// 设置自定义title布局
mTitle = (TextView) findViewById(R.id.title_left_text);
mTitle.setText(R.string.app_name);
mTitle = (TextView) findViewById(R.id.title_right_text);
[java] view
plaincopy
// 设置自定义title布局 mTitle = (TextView) findViewById(R.id.title_left_text); mTitle.setText(R.string.app_name); mTitle = (TextView) findViewById(R.id.title_right_text);
左边的TextView被设置为显示应用程序名称,右边的则需要我们在链接之后在设置更新,目前则显示没有链接字样,所以这里我们暂不设置,进一步就需要获取本地蓝牙适配器BluetoothAdapter了,因为对于有关蓝牙的任何操作都需要首先获得该蓝牙适配器,获取代码非常简单,如下:
view
plaincopy
to clipboardprint?
// 得到一个本地蓝牙适配器
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 如果适配器为null,则不支持蓝牙
if (mBluetoothAdapter
==null) {
Toast.makeText(this, "Bluetooth
is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
getDefaultAdapter()函数用于获取本地蓝牙适配器,然后检测是否为null,如果为null则表示没有蓝牙设备的支持,将通过toast告知用户。
在onStart()函数中,我们将检测蓝牙是否被打开,如果没有打开,则请求打开,否则就可以设置一些聊天信息的准备工作,代码如下:
@Override
public void onStart()
{
super.onStart();
if(D)
Log.e(TAG,"++ ON START ++");
// 如果蓝牙没有打开,则请求打开
// setupChat() will then be called during onActivityResult
if (!mBluetoothAdapter.isEnabled())
{
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
// 否则,设置聊天会话
} else {
if (mChatService
== null) setupChat();
}
}
[java] view
plaincopy
// 得到一个本地蓝牙适配器 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 如果适配器为null,则不支持蓝牙 if (mBluetoothAdapter == null) { Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); finish(); return; } getDefaultAdapter()函数用于获取本地蓝牙适配器,然后检测是否为null,如果为null则表示没有蓝牙设备的支持,将通过toast告知用户。 在onStart()函数中,我们将检测蓝牙是否被打开,如果没有打开,则请求打开,否则就可以设置一些聊天信息的准备工作,代码如下: @Override public void onStart() { super.onStart(); if(D) Log.e(TAG, "++ ON START ++"); // 如果蓝牙没有打开,则请求打开 // setupChat() will then be called during onActivityResult if (!mBluetoothAdapter.isEnabled()) { Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BT); // 否则,设置聊天会话 } else { if (mChatService == null) setupChat(); } }
onStart()函数_检测蓝牙
如果蓝牙没有打开,我们则通过BluetoothAdapter.ACTION_REQUEST_ENABLE来请求打开蓝牙,REQUEST_ENABLE_BT则是我们自己定义的用于请求打开蓝牙的Intent代码,最后当我们调用startActivityForResult来执行请求时,就会在onActivityResult函数中得到一个反馈,如果当前蓝牙已经打开,那么就可以调用setupChat函数来准备蓝牙聊天相关的工作,稍后分析该函数的具体实现。
下面我们分析一下请求打开蓝牙之后,在onActivityResult 中得到的反馈信息,我们传递了REQUEST_ENABLE_BT代码作为请求蓝牙打开的命令,因此在onActivityResult 中,需要会得到一个请求代码为REQUEST_ENABLE_B的消息,对于其处理如下代码所示:
view
plaincopy
to clipboardprint?
case REQUEST_ENABLE_BT:
// 在请求打开蓝牙时返回的代码
if (resultCode
== Activity.RESULT_OK) {
// 蓝牙已经打开,所以设置一个聊天会话
setupChat();
} else {
// 请求打开蓝牙出错
Log.d(TAG, "BT not enabled");
Toast.makeText(this,
R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
finish();
}
[java] view
plaincopy
case REQUEST_ENABLE_BT: // 在请求打开蓝牙时返回的代码 if (resultCode == Activity.RESULT_OK) { // 蓝牙已经打开,所以设置一个聊天会话 setupChat(); } else { // 请求打开蓝牙出错 Log.d(TAG, "BT not enabled"); Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show(); finish(); }
在请求时,如果返回代码为Activity.RESULT_OK,则表示请求打开蓝牙成功,那么我们就可以和上面的操作进度一样,调用setupChat来设置蓝牙聊天相关信息,如果返回其他代码,则表示请求打开蓝牙失败,这时我们同样通过一个Toast来告诉用户,同时也需要调用finish()函数来结束应用程序。
setupChat设置
如果打开蓝牙无误,那么下面我们开始进入setupChat的设置,其代码实现如下:
view
plaincopy
to clipboardprint?
private void setupChat()
{
Log.d(TAG, "setupChat()");
// 初始化对话进程
mConversationArrayAdapter = new ArrayAdapter<String>(this,
R.layout.message);
// 初始化对话显示列表
mConversationView = (ListView) findViewById(R.id.in);
// 设置话显示列表源
mConversationView.setAdapter(mConversationArrayAdapter);
// 初始化编辑框,并设置一个监听,用于处理按回车键发送消息
mOutEditText = (EditText) findViewById(R.id.edit_text_out);
mOutEditText.setOnEditorActionListener(mWriteListener);
// 初始化发送按钮,并设置事件监听
mSendButton = (Button) findViewById(R.id.button_send);
mSendButton.setOnClickListener(new OnClickListener()
{
public void onClick(View
v) {
// 取得TextView中的内容来发送消息
TextView view = (TextView) findViewById(R.id.edit_text_out);
String message = view.getText().toString();
sendMessage(message);
}
});
// 初始化BluetoothChatService并执行蓝牙连接
mChatService = new BluetoothChatService(this,
mHandler);
// 初始化将要发出的消息的字符串
mOutStringBuffer = new StringBuffer("");
}
[java] view
plaincopy
private void setupChat() { Log.d(TAG, "setupChat()"); // 初始化对话进程 mConversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message); // 初始化对话显示列表 mConversationView = (ListView) findViewById(R.id.in); // 设置话显示列表源 mConversationView.setAdapter(mConversationArrayAdapter); // 初始化编辑框,并设置一个监听,用于处理按回车键发送消息 mOutEditText = (EditText) findViewById(R.id.edit_text_out); mOutEditText.setOnEditorActionListener(mWriteListener); // 初始化发送按钮,并设置事件监听 mSendButton = (Button) findViewById(R.id.button_send); mSendButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { // 取得TextView中的内容来发送消息 TextView view = (TextView) findViewById(R.id.edit_text_out); String message = view.getText().toString(); sendMessage(message); } }); // 初始化BluetoothChatService并执行蓝牙连接 mChatService = new BluetoothChatService(this, mHandler); // 初始化将要发出的消息的字符串 mOutStringBuffer = new StringBuffer(""); }
首先构建一个对话进程mConversationArrayAdapter,然后从xml中取得用于显示对话信息的列表mConversationView,最后将列表的数据来源Adapter设置为mConversationArrayAdapter,这里我们可以看到mConversationArrayAdapter所指定的资源为message.xml,其定义实现如下:
view
plaincopy
to clipboardprint?
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
/>
[java] view
plaincopy
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:padding="5dp" />
很简单,就包含了一个TextView用来显示对话内容即可,这里设置了文字标签的尺寸大小textSize和padding属性。
然后我们取得了编辑框mOutEditText,用于输入聊天内容的输入框,并对其设置了一个事件监听mWriteListener,其监听函数的实现如下:
view
plaincopy
to clipboardprint?
// The action listener for the EditText widget, to listen
for the return key
private TextView.OnEditorActionListener
mWriteListener =
new TextView.OnEditorActionListener()
{
public boolean onEditorAction(TextView
view,int actionId,
KeyEvent event) {
// 按下回车键并且是按键弹起的事件时发送消息
if (actionId
== EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
String message = view.getText().toString();
sendMessage(message);
}
if(D)
Log.i(TAG, "END onEditorAction");
return true;
}
};
[java] view
plaincopy
// The action listener for the EditText widget, to listen for the return key private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { // 按下回车键并且是按键弹起的事件时发送消息 if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) { String message = view.getText().toString(); sendMessage(message); } if(D) Log.i(TAG, "END onEditorAction"); return true; } };
首先在其监听中实现了onEditorAction函数,我们通过判断其参数actionId来确定事件触发的动作,其中的"EditorInfo.IME_NULL"在Ophone中表示回车键消息,然后再加上KeyEvent.ACTION_UP,则表示当用户按下回车键并弹起时才触发消息的处理,处理过程也很简单,将输入框中内容取出到变量message中,然后调用sendMessage函数来发送一条消息,具体的发送细节,我们稍后分析。
setupChat函数_发送消息
在setupChat函数中,我们还对发送消息的按钮进行的初始化,同样为其设置了事件监听(setOnClickListener),监听的内容则也是取得输入框中的信息,然后调用sendMessage函数来发送消息,和用户按回车键来发送消息一样。
最后一个重要的操作就是初始化了BluetoothChatService对象mChatService用来管理蓝牙的链接,聊天的操作,并且设置了其Handler对象mHandler来负责数据的交换和线程之间的通信。另外还准备了一个空的字符串对象mOutStringBuffer,用于当我们在发送消息之后,对输入框的清理。
应用菜单用option_menu.xml
该应用程序除了这些界面的布局之外,我们还为其设置了一个菜单,菜单包括了"扫描设备"和"使设备可见(能够被其他设备所搜索到)",创建菜单的方式有很多种,这里gogole的员工,比较喜欢和推崇使用option_menu.xml布局(将界面和逻辑分开),所以我们首先看一下对于该应用程序通过xml所定义的菜单布局,代码如下:
view
plaincopy
to clipboardprint?
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 扫描菜单 -->
<item android:id="@+id/scan"
android:icon="@android:drawable/ic_menu_search"
android:title="@string/connect" />
<!-- 可见操作 -->
<item android:id="@+id/discoverable"
android:icon="@android:drawable/ic_menu_mylocation"
android:title="@string/discoverable" />
</menu>
[java] view
plaincopy
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 扫描菜单 --> <item android:id="@+id/scan" android:icon="@android:drawable/ic_menu_search" android:title="@string/connect" /> <!-- 可见操作 --> <item android:id="@+id/discoverable" android:icon="@android:drawable/ic_menu_mylocation" android:title="@string/discoverable" /> </menu>
onOptionsItemSelected函数、onCreateOptionsMenu函数_菜单的处理与装载
这样的定义的确非常的清晰,我们可以随意向这个Menu中添加菜单选项(itme),这里就定义了上面我们所说的两个菜单。然后再程序中通过onCreateOptionsMenu函数中来装载该菜单布局,遂于菜单的点击可以通过onOptionsItemSelected函数的不同参数来辨别,下面是该应用程序中对菜单选项的处理和装载菜单布局:
view
plaincopy
to clipboardprint?
//创建一个菜单
@Override
public boolean onCreateOptionsMenu(Menu
menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.option_menu, menu);
return true;
}
//处理菜单事件
@Override
public boolean onOptionsItemSelected(MenuItem
item) {
switch (item.getItemId())
{
case R.id.scan:
// 启动DeviceListActivity查看设备并扫描
Intent serverIntent = new Intent(this,
DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
return true;
case R.id.discoverable:
// 确保设备处于可见状态
ensureDiscoverable();
return true;
}
return false;
}
[java] view
plaincopy
//创建一个菜单 @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.option_menu, menu); return true; } //处理菜单事件 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.scan: // 启动DeviceListActivity查看设备并扫描 Intent serverIntent = new Intent(this, DeviceListActivity.class); startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); return true; case R.id.discoverable: // 确保设备处于可见状态 ensureDiscoverable(); return true; } return false; }
装载菜单布局的时候使用了MenuInflater对象,整个过程很简单,大家可以参考上面的代码实现,在处理菜单事件的时候,通过item.getItemId()我们可以得到当前选择的菜单项的ID,首先是扫描设备(R.id.scan),这里我们有启动了另外一个Activity来专门处理扫描设备DeviceListActivity,如果扫描之后我们将通过startActivityForResult函数来请求链接该设备,
onActivityResult函数_扫描反馈信息
同样我们也会在onActivityResult函数中收到一个反馈信息,命令代码为REQUEST_CONNECT_DEVICE,如果反馈的请求代码为Activity.RESULT_OK,则表示扫描成功(扫描过程我们稍后介绍),那么下面就可以开始准备链接了,实现代码如下:
view plaincopy
to clipboardprint?
case REQUEST_CONNECT_DEVICE:
// 当DeviceListActivity返回设备连接
if (resultCode
== Activity.RESULT_OK) {
// 从Intent中得到设备的MAC地址
String address = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// 得到蓝牙设备对象
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// 尝试连接这个设备
mChatService.connect(device);
}
break;
[java] view
plaincopy
case REQUEST_CONNECT_DEVICE: // 当DeviceListActivity返回设备连接 if (resultCode == Activity.RESULT_OK) { // 从Intent中得到设备的MAC地址 String address = data.getExtras() .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); // 得到蓝牙设备对象 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); // 尝试连接这个设备 mChatService.connect(device); } break;
首先我们可以通过DeviceListActivity.EXTRA_DEVICE_ADDRESS来取得设备的Mac地址,然后通过Mac地址使用蓝牙适配器mBluetoothAdapter的getRemoteDevice函数来查找到该地址的设备BluetoothDevice,查询到之后我们可以通过mChatService对象的connect来链接该设备。
ensureDiscoverable函数_使设备处于可见状态
上面我们说的是扫描蓝牙设备并链接的过程,一般蓝牙设备在打开之后都需要设置可见状态,下面我们来看一下另一个菜单选项的实现,用于使设备处于可见状态,其菜单项的ID为R.id.discoverable,具体实现过程则位于ensureDiscoverable函数中,其实现如下代码:
view
plaincopy
to clipboardprint?
private void ensureDiscoverable()
{
if(D)
Log.d(TAG, "ensure discoverable");
//判断扫描模式是否为既可被发现又可以被连接
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);
}
}
[java] view
plaincopy
private void ensureDiscoverable() { if(D) Log.d(TAG, "ensure discoverable"); //判断扫描模式是否为既可被发现又可以被连接 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); } }
这里首先通过mBluetoothAdapter.getScanMode()函数取得该蓝牙的扫描模式,然后通过BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE设置可见属性,在这里我们加入一个附加属性BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,用来设置可见状态的时间,表示在指定的时间中蓝牙处于可见状态,设置好之后通过startActivity来执行即可。
onResume()函数_端口监听
这里忧一个需要注意的问题,在链接某个设备之前,我们需要开启一个端口监听,该应用程序将其放在onResume()函数中来处理了,代码如下:
view
plaincopy
to clipboardprint?
@Override
public synchronizedvoid onResume()
{
super.onResume();
if(D)
Log.e(TAG, "+ ON RESUME +");
// Performing this check in onResume() covers
the case in which BT was
// not enabled during onStart(), so we were paused
to enable it...
// onResume() will be called when ACTION_REQUEST_ENABLE
activity returns.
if (mChatService
!=null) {
// 如果当前状态为STATE_NONE,则需要开启蓝牙聊天服务
if (mChatService.getState()
== BluetoothChatService.STATE_NONE) {
// 开始一个蓝牙聊天服务
mChatService.start();
}
}
}
[java] view
plaincopy
@Override public synchronized void onResume() { super.onResume(); if(D) Log.e(TAG, "+ ON RESUME +"); // Performing this check in onResume() covers the case in which BT was // not enabled during onStart(), so we were paused to enable it... // onResume() will be called when ACTION_REQUEST_ENABLE activity returns. if (mChatService != null) { // 如果当前状态为STATE_NONE,则需要开启蓝牙聊天服务 if (mChatService.getState() == BluetoothChatService.STATE_NONE) { // 开始一个蓝牙聊天服务 mChatService.start(); } } }
首先检测mChatService是否被初始化,然后检测其状态是否为STATE_NONE,STATE_NONE表示初始化之后处于等待的状态,当我们在setupChat函数中初始时,其实就已经将其状态设置为STATE_NONE了(该操作是在BluetoothChatService的构造函数中处理的),所以这里就可以通过一个start函数来启动一个进程即可,实际上就是启动了一个端口监听进程,当有设备连接时,该监听进程结束,然后转向链接进程,链接之后同样又将转换到一个聊天管理进程,对于这些链接过程和通信模块看来只有下一篇文章介绍了.
总结
本文试图完成一个蓝牙聊天程序的分析和实现,但是我发现只要涉及网络通信模块的程序分析过程都比较复杂,可能是因为分析太细的原因,并没有分析完该程序,因此,下一篇文章我们继续分析,同时下一篇要介绍的也是最核心的内容,网络通信过程,希望能得到大家的支持。!