Android藍牙測試—發送一文件到另一藍牙設備


該測試程序是根據網上代碼更改的,用於向另一藍牙設備發送一圖片文件。本文截圖測試的是向PC上發送一指定圖片(如果與要連接的設備未配對,會提示配對的)。

需要注意以下幾個方面:
1. 傳統的UUID方法(也是網絡上流行的)連接其它藍牙設備的方式根本行不通,在網絡上搜索了很久終於找到一個替代的方法是可以工作的(詳細見代碼)
2. 關於藍牙設置的兩個屬性:“開啟關閉”與“設置可見”,這是兩個獨立設置選項,但M9手機將它們設置成關聯了,即打開了藍牙設備就自動設置為可見了,而設 置為可見后藍牙設備也就打開了(手機UI設置里面無法單獨操作“設置可見”,但代碼可以),所以這里也糾結了一段時間,相關代碼部分有說明

先看程序截圖:
1. M9手機截圖:開啟藍牙並搜索設備后


2. PC端截圖:手機端點擊搜索到的設備"wzc-0"后,PC端會提示權限操作


3. PC端截圖:當PC端允許操作后,接收從M9傳來的圖片(ubuntu系統藍牙接收到文件會默認存儲到:/home/yourname/下載)


下面看代碼:
首先看AndroidManifest.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.testBlueTooth"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".testBlueTooth" android:label="@string/app_name"
android:launchMode="singleTask" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>  

需要注意兩點:
1. 最后兩行的權限申明
2. <activity>里面的強制豎屏屬性設置:android:launchMode="singleTask" android:screenOrientation="portrait",避免縱橫屏轉換時程序異常退出!

然后看布局文件main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:id="@+id/LinearLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content">
<TextView android:id="@+id/TextView01" android:layout_height="wrap_content"
android:text="手機藍牙開關" android:layout_width="100dip"></TextView>
<ToggleButton android:layout_height="wrap_content" android:text="藍牙開關"
android:layout_width="wrap_content" android:id="@+id/tbtnSwitch"></ToggleButton>
<Button android:layout_height="wrap_content" android:text="本機藍牙可見"
android:id="@+id/btnDis" android:layout_width="160dip"></Button>
</LinearLayout>
<LinearLayout android:id="@+id/LinearLayout02" android:layout_width="wrap_content" android:layout_height="wrap_content">
<Button android:layout_height="wrap_content" android:id="@+id/btnSearch"
android:text="搜索設備" android:layout_width="160dip"></Button>
<Button android:layout_height="wrap_content" android:id="@+id/btnExit"
android:text="退出程序" android:layout_width="160dip"></Button>
</LinearLayout>
<ListView android:layout_width="fill_parent" android:id="@+id/lvDevices"
android:layout_height="fill_parent">
</ListView>
</LinearLayout>

主要就是兩個嵌套<LinearLayout>和一個<ListView>。

最后是主測試程序testBlueTooth.java:

package com.testBlueTooth;
 
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
//import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;
 
public class testBlueTooth extends Activity {
//static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
static final String FilePath = "file:///sdcard/test.jpg";
Button btnSearch, btnDis, btnExit;
ToggleButton tbtnSwitch;
ListView lvBTDevices;
ArrayAdapter<String> adtDevices;
List<String> lstDevices = new ArrayList<String>();
BluetoothAdapter btAdapt;
public static BluetoothSocket btSocket;
 
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Button 設置
btnSearch = (Button) this.findViewById(R.id.btnSearch);
btnSearch.setOnClickListener(new ClickEvent());
btnExit = (Button) this.findViewById(R.id.btnExit);
btnExit.setOnClickListener(new ClickEvent());
btnDis = (Button) this.findViewById(R.id.btnDis);
btnDis.setOnClickListener(new ClickEvent());
 
// ToogleButton設置
tbtnSwitch = (ToggleButton) this.findViewById(R.id.tbtnSwitch);
tbtnSwitch.setOnClickListener(new ClickEvent());
 
// ListView及其數據源 適配器
lvBTDevices = (ListView) this.findViewById(R.id.lvDevices);
adtDevices = new ArrayAdapter<String>(testBlueTooth.this, android.R.layout.simple_list_item_1, lstDevices);
lvBTDevices.setAdapter(adtDevices);
lvBTDevices.setOnItemClickListener(new ItemClickEvent());
 
// 初始化本機藍牙功能
btAdapt = BluetoothAdapter.getDefaultAdapter();
 
/*
* 關於開關指示按鈕,就是兩裝狀態之間的切換:
* 打開: 表示當前服務狀態為開啟,而不是在點擊之后才開啟
* 關閉: 表示當前服務狀態為關閉,而不是在點擊之后才關閉
* 如果理解反了,則下面的false/true設置也會反的!
*/

if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 讀取藍牙狀態並顯示
tbtnSwitch.setChecked(false);
else if (btAdapt.getState() == BluetoothAdapter.STATE_ON)
tbtnSwitch.setChecked(true);
 
// 注冊Receiver來獲取藍牙設備相關的結果
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver來取得搜索結果
intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(searchDevices, intent);
}
 
/*
* 搜索藍牙設備列表
*/

private BroadcastReceiver searchDevices = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle b = intent.getExtras();
Object[] lstName = b.keySet().toArray();
 
// 顯示所有收到的消息及其細節
for (int i = 0; i < lstName.length; i++) {
String keyName = lstName[i].toString();
Log.e(keyName, String.valueOf(b.get(keyName)));
}
//搜索設備時,取得設備的MAC地址
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String str= device.getName() + "|" + device.getAddress();
// 防止重復添加
if (lstDevices.indexOf(str) == -1){
// 獲取設備名稱和mac地址
lstDevices.add(str);
}
adtDevices.notifyDataSetChanged();
}
}
};
 
/*
* 退出程序
*/

protected void onDestroy() {
this.unregisterReceiver(searchDevices);
super.onDestroy();
// 自毀進程
android.os.Process.killProcess(android.os.Process.myPid());
}
 
/*
* 想其它藍牙設備發送一圖片文件
*/

class ItemClickEvent implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
btAdapt.cancelDiscovery();
String str = lstDevices.get(arg2);
String[] values = str.split("\\|");
String address=values[1];
Log.e("address: ",values[1]);
 
BluetoothDevice btDev = btAdapt.getRemoteDevice(address);
 
try {
/*
* 網絡流傳的通過UUID連接的方式是不工作的,經過了好一陣搜索終於找到了下面的替換方法
*/

//UUID uuid = UUID.fromString(SPP_UUID);
//btSocket = btDev.createRfcommSocketToServiceRecord(uuid);
 
Method m = null;
try {
m = btDev.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
btSocket = (BluetoothSocket) m.invoke(btDev, 1);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
 
btSocket.connect();
 
/*
* 該段落是通過btsocket發送字符串給其它藍牙設備,接收端應該需要做相應接收處理才能收到
* 所以尚未測試,暫且留着吧
*/

// OutputStream outStream = null;
// outStream = btSocket.getOutputStream();
// String message = "Hello message from client to server.";
// byte[] msgBuffer = message.getBytes();
// try {
// outStream.write(msgBuffer);
// } catch (IOException e) {
// e.printStackTrace();
// }
// btSocket.close();
 
 
/*
* 發送一指定的文件到其它藍牙設備
*/

ContentValues cv = new ContentValues();
cv.put("uri", FilePath);
cv.put("destination", address);
cv.put("direction", 0);
Long ts = System.currentTimeMillis();
cv.put("timestamp", ts);
getContentResolver().insert(Uri.parse("content://com.android.bluetooth.opp/btopp"), cv);
btSocket.close();
 
/*
* 下面一段是通過intent的方式發送文件
* 與上面一段的不同在於,該方式會打開一個數據分享方式列表,如藍牙,短信,Email 等
* 選擇藍牙方式后也是可以發送到其它藍牙設備的
* 只不過偶爾也會拋出一個ioException異常,所以健壯性還有待加強,如添加try/catch模塊
*/

// Intent intent = new Intent();
// intent.setAction(Intent.ACTION_SEND);
// intent.setType("image/jpg");
// intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File("/sdcard/test.jpg")) );
// startActivity(intent);
 
 
} catch (IOException e) {
e.printStackTrace();
}
}
}
 
 
class ClickEvent implements View.OnClickListener {
public void onClick(View v) {
// 搜索藍牙設備,在BroadcastReceiver顯示結果
if (v == btnSearch)
{
// 如果藍牙還沒開啟,這提示開啟
if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {
Toast.makeText(testBlueTooth.this, "請先打開藍牙", 1000).show();
return;
}
setTitle("本機藍牙地址:" + btAdapt.getAddress());
lstDevices.clear();
btAdapt.startDiscovery();
// 本機藍牙啟動/關閉
} else if (v == tbtnSwitch) {
if (tbtnSwitch.isChecked() == true)
btAdapt.enable();
else if (tbtnSwitch.isChecked() == false)
btAdapt.disable();
/*
* 設置本機藍牙可檢測性,即可被他人搜索到。
* 這里稍微說明一下:藍牙設備一般都會支持“開啟關閉”和“設置可見”兩種行為:
* 開啟關閉:當然是打開/關閉藍牙設備了
* 設置可見:開啟可檢測性,以便其它藍牙設備可以搜索到本機
* 本人使用M9測試的,M9的“開啟關閉”和“設置可見”默認被綁定了,即開啟后可見屬性也啟用了,
* 同樣啟用可見后(UI上不允許這么操作,通過下面的代碼可以)藍牙也開啟了
*
* 所以為了避免重復操作,下面做了些判斷:
* 1. 如果是藍牙開啟,則不再需要啟用可見
* 2. 如果藍牙未開啟,這啟用可見后,將藍牙開啟的指示按鈕設置為開啟狀態
*
* 用於自己測試時應根據自己的手機情況稍做改動!
* 如有的手機在設置藍牙時需要對“開啟關閉”和“設置可見“兩個選項分別單獨操作,
* 這時只需要else里面部分就行了,即每次都執行設置可檢測性操作。
*/

} else if (v == btnDis)
{
if(tbtnSwitch.isChecked() == true){
Toast.makeText(testBlueTooth.this, "藍牙已經可見", 1000).show();
}
else{
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
tbtnSwitch.setChecked(true);
}
} else if (v == btnExit) {
try {
if (btSocket != null)
btSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
testBlueTooth.this.finish();
}
}
}
}

里面都有詳細的說明。

注意:用戶測試時需更改一下發送文件的路徑,及FilePath變量的值!

參考鏈接:
[1] stackoverflow.com/questions/3397071/andr...ery-failed-exception
[2] stackoverflow.com/questions/4921384/how-...roid-programatically
[3] android.tgbus.com/Android/tutorial/201103/346657.shtml
[4] www.mikenimer.com/?p=373


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM