1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="com.example.android.BluetoothChat" 3 android:versionCode="1" 4 android:versionName="1.0"> 5 <uses-sdk minSdkVersion="6" /> 6 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 7 <uses-permission android:name="android.permission.BLUETOOTH" /> 8 9 <application android:label="@string/app_name" 10 android:icon="@drawable/app_icon" > 11 <activity android:name=".BluetoothChat" 12 android:label="@string/app_name" 13 android:configChanges="orientation|keyboardHidden"> 14 <intent-filter> 15 <action android:name="android.intent.action.MAIN" /> 16 <category android:name="android.intent.category.LAUNCHER" /> 17 </intent-filter> 18 </activity> 19 <activity android:name=".DeviceListActivity" 20 android:label="@string/select_device" 21 android:theme="@android:style/Theme.Dialog" 22 android:configChanges="orientation|keyboardHidden" /> 23 </application> 24 </manifest>
第13行、第22行:android:configChanges="orientation|keyboardHidden"
在androidmanifest.xml中加入配置android:configChanges="orientation|keyboardHidden|navigation“,這樣在程序中. Activity就不會重復的調用onCreate(),甚至不會調用onPause、onResume,只會調用一個onConfigurationChanged(Configuration newConfig)的方法。
如果只是橫屏,或者只支持豎屏模式,直接在AndroidManifest.xml的Activity元素加入屬性android:screenOrientation=”landscape”。(landscape是橫向,portrait是縱向)
第21行:android:theme="@android:style/Theme.Dialog"
參考:Android風格與主題(style and theme)
menu/option_menu.xml
1 <menu xmlns:android="http://schemas.android.com/apk/res/android"> 2 <item android:id="@+id/scan" 3 android:icon="@android:drawable/ic_menu_search" 4 android:title="@string/connect" /> 5 <item android:id="@+id/discoverable" 6 android:icon="@android:drawable/ic_menu_mylocation" 7 android:title="@string/discoverable" /> 8 </menu>
layout/main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:orientation="vertical" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 > 6 <ListView android:id="@+id/in" 7 android:layout_width="fill_parent" 8 android:layout_height="fill_parent" 9 android:stackFromBottom="true" 10 android:transcriptMode="alwaysScroll" 11 android:layout_weight="1" 12 /> 13 <LinearLayout 14 android:orientation="horizontal" 15 android:layout_width="fill_parent" 16 android:layout_height="wrap_content" 17 > 18 <EditText android:id="@+id/edit_text_out" 19 android:layout_width="wrap_content" 20 android:layout_height="wrap_content" 21 android:layout_weight="1" 22 android:layout_gravity="bottom" 23 /> 24 <Button android:id="@+id/button_send" 25 android:layout_width="wrap_content" 26 android:layout_height="wrap_content" 27 android:text="@string/send" 28 /> 29 </LinearLayout> 30 </LinearLayout>
custom_title.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_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="fill_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="fill_parent"
android:textColor="#fff"
android:layout_weight="1"
/>
</RelativeLayout>
在onCreate方法之中
自定義標題欄
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
獲取BluetoothAdapter對象
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Activity生命周期日志
1. 啟動BluetoothChat程序
10-30 22:01:03.983: ERROR/BluetoothChat(5133): +++ ON CREATE +++
10-30 22:01:04.003: ERROR/BluetoothChat(5133): ++ ON START ++
10-30 22:01:04.013: DEBUG/BluetoothChat(5133): setupChat()
10-30 22:01:04.013: ERROR/BluetoothChat(5133): + ON RESUME +
10-30 22:01:04.033: INFO/BluetoothChat(5133): MESSAGE_STATE_CHANGE: 1
2. 屏幕進入待機狀態(或打開藍牙權限請求對話框)
10-30 22:02:45.863: ERROR/BluetoothChat(5133): - ON PAUSE -
3. 屏幕從待機狀態恢復過來
10-30 22:03:24.743: ERROR/BluetoothChat(5133): + ON RESUME +
4.1 點擊返回按鍵
10-30 22:04:38.793: ERROR/BluetoothChat(5133): - ON PAUSE -
10-30 22:04:38.903: ERROR/BluetoothChat(5133): -- ON STOP --
10-30 22:04:38.903: ERROR/BluetoothChat(5133): --- ON DESTROY ---
10-30 22:04:38.913: INFO/BluetoothChat(5133): MESSAGE_STATE_CHANGE: 0
4.2 點擊HOME鍵
10-30 22:23:53.223: ERROR/BluetoothChat(5133): - ON PAUSE -
10-30 22:23:53.353: ERROR/BluetoothChat(5133): -- ON STOP --
-> 長摁HOME鍵 點擊BluetoothChat應用圖標
10-30 22:25:17.093: ERROR/BluetoothChat(5133): ++ ON START ++
10-30 22:25:17.093: ERROR/BluetoothChat(5133): + ON RESUME +
在onStart方法中
如果藍牙已經打開 直接調用setupChat方法 如果藍牙未打開 先打開藍牙 再調用setupChat方法
打開 藍牙權限請求 對話框
if (!mBluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
在setupChat方法中
獲取ListView 設置Adapter
ArrayAdapter mConversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message);
ListView mConversationView = (ListView) findViewById(R.id.in);
mConversationView.setAdapter(mConversationArrayAdapter);
layout/message.xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
/>
設置TextView.OnEditorActionListener
EditText mOutEditText = (EditText) findViewById(R.id.edit_text_out);
mOutEditText.setOnEditorActionListener(mWriteListener);
創建BluetoothChatService對象
mChatService = new BluetoothChatService(this, mHandler);
在onResume方法中
調用service的start方法
if (mChatService != null) {
if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
mChatService.start();
}
}
在onDestroy方法中
調用service的stop方法
if (mChatService != null) mChatService.stop();
BluetoothChatService
在BluetoothChatService構造方法中
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
在BluetoothChatService的start方法中
10-30 23:09:01.753: DEBUG/BluetoothChatService(7263): start
創建AcceptThread線程對象 調用線程的start方法
AcceptThread mAcceptThread = new AcceptThread();
mAcceptThread.start();
在AcceptThread構造方法中
創建BluetoothServerSocket對象
BluetoothServerSocket mmServerSocket = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
10-30 23:09:01.773: DEBUG/BluetoothChatService(7263): BEGIN mAcceptThreadThread[Thread-10,5,main]
10-30 23:09:01.773: DEBUG/BluetoothChatService(7263): setState() 0 [STATE_NONE] -> 1 [STATE_LISTEN]
在AcceptThread的run方法中
創建BluetoothSocket對象
while (mState != STATE_CONNECTED) {
BluetoothSocket socket = mmServerSocket.accept();
如果socket不為空
1. 如果mState為STATE_LISTEN、STATE_CONNECTING
調用 connected(socket, socket.getRemoteDevice());
2. 如果mState為STATE_NONE、STATE_CONNECTED
調用 socket.close();
}
在connected(BluetoothSocket socket, BluetoothDevice device)方法中
if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
創建ConnectedThread線程對象 調用線程的start方法
ConnectedThread mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
通過handler消息 發送建立好藍牙連接的設備的名稱
Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
msg.setData(bundle);
mHandler.sendMessage(msg);
在ConnectedThread構造方法中
獲取InputStream和OutputStream
InputStream mmInStream = socket.getInputStream();
OutputStream mmOutStream = socket.getOutputStream();
在ConnectedThread的run方法中
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
}
在connectionLost方法中
// Send a failure message back to the Activity
Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChat.TOAST, "Device connection was lost");
msg.setData(bundle);
mHandler.sendMessage(msg);
在BluetoothChat.sendMessage(String message)方法中
if (message.length() > 0) {
// Get the message bytes and tell the BluetoothChatService to write
byte[] send = message.getBytes();
mChatService.write(send);
}
在BluetoothChatService.write(byte[] out)方法中
調用 mConnectedThread.write(out);
在ConnectedThread.write(byte[] buffer)方法中
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget();
obtainMessage得到的Handler對象不是自己創建的,而是從MessagePool 拿的,省去了創建對象申請內存的開銷。
所以以后使用的時候盡量使用 Message msg = handler.obtainMessage();的形式創建Message,不要自己New Message。至於message產生之后你使用sendToTarget或者是sendMessage效率影響並不大。
在AcceptThread.cancel方法中
調用 mmServerSocket.close();
在ConnectedThread.cancel方法中
調用 mmSocket.close();
在ConnectThread.cancel方法中
調用 mmSocket.close();
在BluetoothChat.onCreateOptionsMenu(Menu menu)方法中
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.option_menu, menu);
menu/option_menu.xml
<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>
在BluetoothChat.onOptionsItemSelected(MenuItem item)方法中
switch (item.getItemId()) {
case R.id.scan:
// Launch the DeviceListActivity to see devices and do scan
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
return true;
case R.id.discoverable:
// Ensure this device is discoverable by others
ensureDiscoverable();
return true;
}
在BluetoothChat.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);
}
在DeviceListActivity.onCreate方法中
setContentView(R.layout.device_list);
layout/device_list.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/title_paired_devices"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/title_paired_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/paired_devices"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_new_devices"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/title_other_devices"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/new_devices"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="2"
/>
<Button android:id="@+id/button_scan"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/button_scan"
/>
</LinearLayout>
PairedListView及其ArrayAdapter
ArrayAdapter mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
newDevicesListView及其ArrayAdapter
ArrayAdapter mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
創建DeviceListActivity.mReceiver對象
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// If it's already paired, skip it, because it's been listed already
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
// When discovery is finished, change the Activity title
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.select_device);
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
在DeviceListActivity.onCreate方法中
注冊mReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
獲取pairedDevices
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
將pairedDevices添加到pairedListView中
for (BluetoothDevice device : pairedDevices) {
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
如果pairedDevices.size為0,顯示No devices have been paired
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
在DeviceListActivity.doDiscovery()方法中
setProgressBarIndeterminateVisibility(true);
setTitle(R.string.scanning);
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
mBtAdapter.startDiscovery();
在DeviceListActivity.onCreate方法中
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
在DeviceListActivity.onDestroy()方法中
mBtAdapter.cancelDiscovery();
this.unregisterReceiver(mReceiver);
在mDeviceClickListener.onItemClick方法中
mBtAdapter.cancelDiscovery();
// Get the device MAC address, which is the last 17 chars in the View
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17);
// Create the result Intent and include the MAC address
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
// Set result and finish this Activity
setResult(Activity.RESULT_OK, intent);
finish();
在BluetoothChatService中使用到關鍵字synchronized的地方有
1. private synchronized void setState(int state)
2. public synchronized int getState()
3. public synchronized void start()
4. public synchronized void connect(BluetoothDevice device)
5. public synchronized void connected(BluetoothSocket socket, BluetoothDevice device)
6. public synchronized void stop()
7. 在BluetoothChatService.write(byte[] out)方法中用到
synchronized (this)
8. 在AcceptThread.run()方法中用到
synchronized (BluetoothChatService.this)
9. 在ConnectThread.run()方法中用到
synchronized (BluetoothChatService.this)