編輯:關於Android編程
為了在您的應用程序中使用藍牙功能,您必須聲明藍牙權限藍牙。您需要此權限來執行任何藍牙通信,如請求一個連接、接受一個連接和傳輸數據。如果你想讓你的應用啟動設備發現或操縱藍牙設置,你也必須申報bluetooth_admin許可。大多數應用程序都需要此權限,僅用於發現本地藍牙設備的能力。此權限授予的其他權限不應被使用,除非應用程序是一個“電源管理器”,將修改用戶請求後的藍牙設置。注意:如果你使用bluetooth_admin許可,那麼你也必須有藍牙許可。
...
在應用程序可以通過藍牙進行通信之前,您需要驗證是否支持藍牙設備上的支持,如果是這樣的話,請確保它已啟用。如果不支持藍牙,那麼您應該優雅地禁用任何藍牙功能。如果藍牙是支持的,但禁用,那麼你可以要求用戶啟用藍牙,而不會離開你的應用程序。這個設置是在兩個步驟來完成,使用藍牙適配器。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// 設備不支持藍牙
}
下一步,您需要確保藍牙功能已啟用。isenabled()檢查藍牙目前是否啟用。如果此方法返回錯誤,則禁用藍牙。要求藍牙啟用,startactivityforresult()與action_request_enable動作意圖。這將發出一個請求,使藍牙通過系統設置(不阻止您的應用程序)。例如:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
使用藍牙適配器 BluetoothAdapter,您可以通過查詢名單發現遠程藍牙設備配對。
設備發現是一個掃描程序,搜索本地區域的藍牙功能的設備,然後要求一些信息(這是有時被稱為“發現”,“查詢”或“掃描”)然而,局域網內的藍牙設備將響應發現請求只有在能夠被發現。如果一個設備可以被發現,它會響應發現請求並共享一些信息,比如設備名稱、類別,並以其獨特的MAC地址。使用此信息,執行發現的設備可以選擇啟動一個連接到所發現的設備。
一旦一個連接是第一次與一個遠程設備,一個配對請求自動提交給用戶。當一個設備配對,該設備的基本信息(如設備名稱,類和地址)被保存,並可以讀取使用藍牙耳機。使用一個遠程設備的已知的mac地址,可以在任何時間啟動一個連接,而不進行發現(假設設備處於范圍內)。
記住,有一個配對和被連接之間的區別。要配對意味著兩個設備都知道彼此的存在,有一個共享的鏈路密鑰,可用於認證,並能夠建立一個加密的相互連接。要連接意味著目前的設備共享一個RFCOMM通道可以互相傳遞數據。目前安卓藍牙API的設備需要被配對在RFCOMM連接可以建立。(當您啟動與藍牙接口的加密連接時,自動執行配對)。
getBondedDevices獲取與本機藍牙所有綁定的遠程藍牙信息,以BluetoothDevice類實例返回,如果藍牙開啟,該函數會返回一個空集合
Set pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
開始發現設備,調用startdiscovery()。該進程是異步的,該方法將立即返回一個布爾值,指示是否已成功啟動。發現過程通常涉及一個約12秒的查詢掃描,然後通過一個頁面掃描每個發現的設備來檢索其藍牙名稱。
應用程序必須注冊ACTION_FOUND意圖接收有關每個設備發現BroadcastReceiver。我們通過注冊廣播接收器接受廣播,來處理一些事情:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
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);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
警告:執行設備發現是藍牙適配器的一個沉重的過程,並會消耗大量的資源。一旦你找到了一個設備連接,就調用canceldiscovery()取消之前的掃描。
發現設備的Intent意圖默認掃描時間120秒,一個應用程序可以設置最大持續時間為3600秒,0值表示設備總是發現。任何值低於0或高於3600自動設置為120秒)(可以這樣理解:藍牙設備運行在多少時間內可以被其他用於掃描到並連接)
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
關於藍牙與服務端、客戶端的鏈接涉及到BluetoothServerSocket 、BluetoothSocket ,沒啥好說的,稍後通過官方simple一觀即可。
相比較上面這塊,權限必須有以下兩項(4.3的低功耗藍牙的掃描startLeScan方法必須具備BLUETOOTH_ADMIN權限)
如果你確定你的應用程序是4.3以上的手機設備安裝,支持低功耗藍牙,可以在清單文件聲明如下
如果你不確定你的app使用的設備是否支持低功耗藍牙,但又想讓支持的設備使用低功耗藍牙,那麼你可以這樣做
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
或BluetoothAdapter可以通過BluetoothManager得到,因為在BluetoothManager被裝載時構造函數調用了BluetoothAdapter.getDefualtA..初始化了adapter對象
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
if (b != null) {
IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
sAdapter = new BluetoothAdapter(managerService);
} else {
Log.e(TAG, "Bluetooth binder is null");
}
}
return sAdapter;
}
藍牙設備的掃描也發生了變化,通過adapter.startScan stopScan控制掃描的開關,掃描的時間可以通過postDelayed設定掃描時間
/**
* Activity for scanning and displaying available BLE devices.
*/
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
...
}
通過LeScanCallback 回調接口的實現,來處理返回結果刷新UI等方面功能的實現。
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
BluetoothGatt BluetoothGattCallback等相關的Gat類都大同小異,根據google官方提供的simple過一遍即可。
藍牙開發調試如果你想使用adb命令可以參考https://developer.android.com/training/wearables/apps/bt-debugging.html
通過官網找到三個開源項目,但是貌似都不能下載,只能通過github檢索google的倉庫了
BluetoothAdvertisements
BluetoothLeGatt
BluetoothChat
先看android-BluetoothAdvertisements運行效果圖
從BluetoothAdvertisements開源項目首先get到一點,剝離生命周期!相關代碼塊如下
public class ScannerFragment extends ListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setRetainInstance(true);
// 這裡傳入的不是Activity,而是通過Activity獲取Application,讓其不再跟隨活動Activity的生命周期
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
LayoutInflater.from(getActivity()));
mHandler = new Handler();
}
掃描代碼塊並沒有用BluetoothAdapter.startScan,經過源碼查看發現依然是過時方法,simple裡面直接通過adapter獲取BluetoothLeScanner,調用了scanner的startScan
public void startScanning() {
// .........略............
mScanCallback = new SampleScanCallback();
mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
}
然而你如果想下載完這個開源項目就想運行看效果,伙計,不得不說你是絕對不會成功的,buildScanFilters裡面添加了掃描過濾規則,uuid無法匹配你是看不到任何東西的,根據代碼提示注釋掉下面的過濾條件可以看到所有的藍牙設備。
private List buildScanFilters() {
List scanFilters = new ArrayList<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
// 注釋掉下面的掃描過濾規則,才能掃描到(uuid不匹配沒法掃描到的)
// builder.setServiceUuid(Constants.Service_UUID);
//scanFilters.add(builder.build());
return scanFilters;
}
在低功耗模式下執行藍牙LE掃描。這是默認掃描模式,因為它消耗最小功率
private ScanSettings buildScanSettings() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
return builder.build();
}
掃描的結果回調我們可以根據自己的需求做具現
private class SampleScanCallback extends ScanCallback {
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
//批處理掃描結果
for (ScanResult result : results) {
mAdapter.add(result);
}
mAdapter.notifyDataSetChanged();
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//掃描到一個立即回調
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
//掃描失敗
}
}
掃描開始之前的邏輯判斷自行參考simple MainActivity.至於AdvertiserService相關的差不多的就略過啦、
android-BluetoothLeGatt這個simple個人覺得沒多少特別之處用到的核心上面都有提到,基於android4.3+版本的藍牙開發simple,mainfest可以看看,service個人感覺有點亂糟糟的。
再看BluetoothChat,首先需要讓藍牙被感知掃描鏈接,設置相應的模式和允許被掃描到的時長。
/**
* Makes this device discoverable.
*/
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);
}
}
藍牙適配器getRemoteDevice(address)後進行連接,核心代碼參考BluetoothChatService.ConnectThread線程部分。而數據的發送核心代碼只需要把發送的string message轉換byte[] ,調用ChatServiced的write方法
/**
* Write to the ConnectedThread in an unsynchronized manner
*
* @param out The bytes to write
* @see ConnectedThread#write(byte[])
*/
public void write(byte[] out) {
// Create temporary object
ConnectedThread r;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
if (mState != STATE_CONNECTED) return;
r = mConnectedThread;
}
// Perform the write unsynchronized
r.write(out);
}
最後提供一個工具類BluetoothHelper.java (這個工具類整理的並不完善,關於掃描和鏈接兼容都沒寫進去,具體開發鏈接參照官方simple的ChartService,LE相關的掃描參照simple吧,兼容主要是api11 、api18LE 、api21)
/**
* Created by idea on 2016/7/4.
*/
public class BluetoothHelper {
private static BluetoothHelper instance;
private BluetoothAdapter mBluetoothAdapter;
private Activity mContext;
/**
* 獲取BluetoothHelper實例,采用Application剝離Activity生命周期
* @param activity
* @return BluetoothHelper
*/
public static BluetoothHelper getInstance(Activity activity) {
if (instance == null) {
synchronized (BluetoothHelper.class) {
if (instance == null) {
instance = new BluetoothHelper(activity);
}
}
}
return instance;
}
/**
* 私有構造函數
* @param activity
* @hide
*/
private BluetoothHelper(Activity activity) {
this.mContext = activity;
mBluetoothAdapter = getAdapter();
}
/***
* 獲取BluetoothAdapter
*
* @return BluetoothAdapter
* @hide
*/
private BluetoothAdapter getAdapter() {
BluetoothAdapter mBluetoothAdapter;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter = ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
} else {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
return mBluetoothAdapter;
}
/**
* 獲取已經存在的adapter實例
* @return
*/
public BluetoothAdapter getBluetoothAdapter() {
return mBluetoothAdapter;
}
/**
* 判斷手機手否支持藍牙通信
*
* @return boolean
*/
public boolean checkSupperBluetooth() {
return mBluetoothAdapter == null;
}
/**
* 檢查藍牙狀態是否打開可用
*
* @return boolean
*/
public boolean checkBluetoothEnable() {
return mBluetoothAdapter.isEnabled();
}
/**
* 如果藍牙設備支持的情況下,並未打開藍牙,需要請求藍牙打開
*/
public void requestOpenBluetooth() {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
mContext.startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);
}
/**
* 判斷我們請求打開藍牙的結果
*
* @param requestCode
* @param resultCode
* @param onOpenBluetoothLisenter
*/
public void performResult(int requestCode, int resultCode, OnBluetoothListener.OnOpenBluetoothLisenter onOpenBluetoothLisenter) {
switch (requestCode) {
case Constants.REQUEST_ENABLE_BT:
if (onOpenBluetoothLisenter != null) {
if (resultCode == Activity.RESULT_OK) {
if (checkBluetoothEnable()) {
onOpenBluetoothLisenter.onSuccess();
} else {
onOpenBluetoothLisenter.onFail("藍牙不可用,會影響到相關功能的使用");
}
} else {
onOpenBluetoothLisenter.onFail("Error");
}
}
}
}
/***
* 是否支持藍牙低功耗廣播(4.3+)
*
* @return boolean
* @hide
*/
@Deprecated
public boolean isSupperBluetoothLE() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
/**
* 判斷是否支持LE
*
* @return boolean
* @hide
*/
@Deprecated
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public boolean isMultipleAdvertisementSupported() {
return mBluetoothAdapter.isMultipleAdvertisementSupported();
}
/**
* 檢測是否支持藍牙低功耗,檢查方式走版本分之
*
* @return boolean
*/
public boolean checkSupperBluetoothLE() {
final int version = Build.VERSION.SDK_INT;
if (version >= Build.VERSION_CODES.LOLLIPOP) {
return isMultipleAdvertisementSupported();
} else {
return isSupperBluetoothLE();
}
}
/**
* 允許藍牙在某段時間內被掃描鏈接到
*
* @param duration
*/
private void ensureDiscoverable(int duration) {
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration);
mContext.startActivity(discoverableIntent);
}
}
/**
* 基於APi21的掃描藍牙
* @param mScanCallback
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void startScanningApi21(ScanCallback mScanCallback){
mBluetoothAdapter.getBluetoothLeScanner().startScan(buildScanFilters(), buildScanSettings(),mScanCallback);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private List buildScanFilters() {
List scanFilters = new ArrayList<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
// 注釋掉下面的掃描過濾規則,才能掃描到(uuid不匹配沒法掃描到的)
// builder.setServiceUuid(Constants.Service_UUID);
//scanFilters.add(builder.build());
return scanFilters;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private ScanSettings buildScanSettings() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
return builder.build();
}
/**
* 注冊廣播用於接收掃描結果,Api11的方法需要用到
*/
public void registerReceiver(){
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
mContext.registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mContext.registerReceiver(receiver, filter);
}
/**
* 銷毀同步生命周期
*/
protected void onDestroy() {
if (mBluetoothAdapter != null) {
mBluetoothAdapter.cancelDiscovery();
}
mContext.unregisterReceiver(receiver);
}
/**
* Api11+都可以使用的藍顏掃描
*/
private void doDiscovery() {
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
/**
* The BroadcastReceiver that listens for discovered devices and changes the title when
* discovery is finished
*/
private final BroadcastReceiver receiver = new BroadcastReceiver() {
ArrayList results = new ArrayList<>();
@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) {
results.add(device);
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
}
}
};
}
梳理知識,掌握脈絡,萬變不離其宗。
1.案例效果圖 2.准備素材 progress1.png(78*78) progress2.png(78*78) 3.原理 采用一張圖片作為P
第1節 概述在安卓系統中,為了界面或者其中的組件在切換、改變的時候顯得自然生動、具有流動性的美感,就給它們添加了動畫的效果。例如圖片切換的時候,前一張圖片淡出,後一張圖片
EditView類繼承自TextView類,EditView與TextView最大的不同就是用戶可以對EditView控件進行編輯,
ListView是安卓中非常常用的一個控件。安卓設計使用Adapter來對ListView進行管理。但是系統提供的Adapter無法滿足一些復雜的顯示情況,這個時候我們就