[Android] BLE#1 搜尋BLE Device並連線

本篇將介紹如何在Android手機上搜尋BLE Device並建立連線,BLE相較於傳統BL較不一樣,需使用另外BLE專屬的API才能找到BLE設備,此範例使用的Android版本為

4.4,據所知5.0版本以上 BLE已使用新的API,未來在補充5.0版本以上的BLE方法。

既然需使用到藍牙相關,也當然就須在manifests中加入藍牙的使用權限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

需要注意的是,相較於一般藍牙使用的聲明,多了uses-feature...............bluetooth_le此項。

其大概Layout如上圖,兩個Button分別為開始搜尋BLE Device與停止搜尋,下面為一個ListView,將搜尋到的BLE添加到ListView顯示。

    private TextView textView;
    private ListView scanlist;
    private ArrayList<String> deviceName;
    private ListAdapter listAdapter;
    private boolean mScanning=false;

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private static final int REQUEST_ENABLE_BT=1;
    private static final int SCAN_TIME=10000;
    private ArrayList<BluetoothDevice> mBluetoothDevices=new ArrayList<BluetoothDevice>();

    private Handler mHandler; //該Handler用來搜尋Devices10秒後,自動停止搜尋

以上將使用到的元件定義出來,找到BLE設備的最主要兩個為BluetoothManager與BluetoothAdapter兩項,再將找到的Devices存到ArrayList<BluetoothDevice>當中。

在一開始onCreate當中,需檢查手機本身是否支持BLE? 以及BL,分別為下面兩式:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(!getPackageManager().hasSystemFeature(getPackageManager().FEATURE_BLUETOOTH_LE)){
            Toast.makeText(getBaseContext(),R.string.No_sup_ble,Toast.LENGTH_SHORT).show();
            finish();
        }//利用getPackageManager().hasSystemFeature()檢查手機是否支援BLE設備,否則利用finish()關閉程式。

       //試著取得BluetoothAdapter,如果BluetoothAdapter==null,則該手機不支援Bluetooth
       //取得Adapter之前,需先使用BluetoothManager,此為系統層級需使用getSystemService
        mBluetoothManager=(BluetoothManager)this.getSystemService(BLUETOOTH_SERVICE);
        mBluetoothAdapter=mBluetoothManager.getAdapter();

        if(mBluetoothAdapter==null){
            Toast.makeText(getBaseContext(),R.string.No_sup_Bluetooth,Toast.LENGTH_SHORT).show();
            finish();
            return;
        }//如果==null,利用finish()取消程式。

        textView=(TextView)findViewById(R.id.textViewID);
        scanlist=(ListView)findViewById(R.id.scanlistID);
        deviceName=new ArrayList<String>();   //此ArrayList屬性為String,用來裝Devices Name
        listAdapter=new ArrayAdapter<String>(getBaseContext(),android.R.layout.simple_expandable_list_item_1,deviceName);//ListView使用的Adapter,
        scanlist.setAdapter(listAdapter);//將listView綁上Adapter
        scanlist.setOnItemClickListener(new onItemClickListener()); //綁上OnItemClickListener,設定ListView點擊觸發事件
        mHandler=new Handler();
    }
 @Override
    protected void onResume() {
        super.onResume();
        //一般來說,只要使用到mBluetoothAdapter.isEnabled()就可以將BL開啟了,但此部分添加一個Result Intent
        //跳出詢問視窗是否開啟BL,因此該Intenr為BluetoothAdapter.ACTION.REQUEST_ENABLE
        if(!mBluetoothAdapter.isEnabled()){ 
            Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent,REQUEST_ENABLE_BT); //再利用startActivityForResult啟動該Intent
        }
        ScanFunction(true); //使用ScanFunction(true) 開啟BLE搜尋功能,該Function在下面部分
    }
   //這個Override Function是因為在onResume中使用了ActivityForResult,當使用者按了取消或確定鍵時,結果會
   //返回到此onActivvityResult中,在判別requestCode判別是否==RESULT_CANCELED,如果是則finish()程式
    @Override  
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(REQUEST_ENABLE_BT==1 && resultCode== Activity.RESULT_CANCELED){
            finish();
            return;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
   //此為ScanFunction,輸入函數為boolean,如果true則開始搜尋,false則停止搜尋
   private void ScanFunction(boolean enable){ 
       if(enable){ 
           mHandler.postDelayed(new Runnable() { //啟動一個Handler,並使用postDelayed在10秒後自動執行此Runnable()
               @Override
               public void run() {
                   mBluetoothAdapter.stopLeScan(mLeScanCallback);//停止搜尋
                   mScanning=false; //搜尋旗標設為false
                   textView.setText("Stop Scan"); 
                   Log.d(TAG,"ScanFunction():Stop Scan");
               }
           },SCAN_TIME); //SCAN_TIME為幾秒後要執行此Runnable,此範例中為10秒
           mScanning=true; //搜尋旗標設為true
           mBluetoothAdapter.startLeScan(mLeScanCallback);//開始搜尋BLE設備
           textView.setText("Scanning");
           Log.d(TAG, "Start Scan");
       }
       else {
           mScanning=false; 
           mBluetoothAdapter.stopLeScan(mLeScanCallback);
       }
   }
//注意,在此enable==true中的Runnable是在10秒後才會執行,因此是先startLeScan,10秒後才會執行Runnable內的stopLeScan
//在BLE Devices Scan中,使用的方法為startLeScan()與stopLeScan(),兩個方法都需填入callback,當搜尋到設備時,都會跳到
//callback的方法中
    //建立一個BLAdapter的Callback,當使用startLeScan或stopLeScan時,每搜尋到一次設備都會跳到此callback
    private BluetoothAdapter.LeScanCallback mLeScanCallback=new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
            runOnUiThread(new Runnable() { //使用runOnUiThread方法,其功能等同於WorkThread透過Handler將資訊傳到MainThread(UiThread)中,
                                           //詳細可進到runOnUiThread中觀察
                @Override
                public void run() {
                    if (!mBluetoothDevices.contains(device)) { //利用contains判斷是否有搜尋到重複的device
                        mBluetoothDevices.add(device);         //如沒重複則添加到bluetoothdevices中
                        deviceName.add(device.getName()+" rssi:"+rssi+"\r\n" + device.getAddress()); //將device的Name、rssi、address裝到此ArrayList<Strin>中
                        ((BaseAdapter)listAdapter).notifyDataSetChanged();//使用notifyDataSetChanger()更新listAdapter的內容
                    }
                }
            });
        }

    };
  //分別按下搜尋予停止搜尋button時的功能,分別為開始搜尋與停止搜尋
   public void btnClick(View v){
        switch (v.getId()){
            case R.id.scanbtnID:
                ScanFunction(true);
                mBluetoothAdapter.startLeScan(mLeScanCallback);
                break;
            case R.id.stopbtnID:
                ScanFunction(false);
                textView.setText("Stop Scan");
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
                break;
        }
    }
  //需要注意的是,需加入一個stopLeScan在onPause()中,當按返回鍵或關閉程式時,需停止搜尋BLE
  //否則下次開啟程式時會影響到搜尋BLE device
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause():Stop Scan");
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
   //以下為ListView ItemClick的Listener,當按下Item時,將該Item的BLE Name與Address包起來,將送到另一
   //Activity中建立連線
    private class onItemClickListener implements AdapterView.OnItemClickListener{

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            //mBluetoothDevices為一個陣列資料ArrayList<BluetoothDevices>,使用.get(positon)取得
            //Item位置上的BluetoothDevice
            final BluetoothDevice mBluetoothDevice=mBluetoothDevices.get(position);
            //建立一個Intent,將從此Activity進到ControlActivity中
            //在ControlActivity中將與BLE Device連線,並互相溝通
            Intent goControlIntent=new Intent(MainActivity.this,ControlActivity.class);
            //將device Name與address存到ControlActivity的DEVICE_NAME與ADDRESS,以供ControlActivity使用
            goControlIntent.putExtra(ControlActivity.DEVICE_NAME,mBluetoothDevice.getName());
            goControlIntent.putExtra(ControlActivity.DEVICE_ADDRESS,mBluetoothDevice.getAddress());
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
            startActivity(goControlIntent); 
        }
    }

以上為MainActivity大致寫法,整體流程為:

開啟程式後判斷是否支援BLE->onCreate內直接開始搜尋BLE Devices->將搜尋到的Devices存到mBluetoothDevices(ArrayList)內,名稱存到deviceName(ArrayList)內,並加到ListView中 ->點擊List Item將該device Name與address打包傳到ControlActivity內以供連線與控制。

下一篇將進行ControlActivity的BLE連線與溝通。