본문 바로가기

프로젝트/블루투스 무드등

[비전공자도 만들 수 있는 블루투스 무드등] 10. 안드로이드 앱 part 2-4, 블루투스 페어링되지 않은 기기 탐색

안녕하세요? 닉네임간편입니다. 이번 시간에는 페어링 되지 않은 기기를 탐색하는 법에 대해 알아보겠습니다.


전체 소스는 여기에 있습니다.

https://github.com/creativeduck/MyLED

앱을 미리 사용해보고 계신 분들은, 이 링크를 타고 설치해주시면 됩니다.

https://play.google.com/store/apps/details?id=com.mybest.myled


1.  변수 정의

Set<BluetoothDevice> unpairedDevices = new HashSet<>();
List<String> unpairedList = new ArrayList<>();
ArrayAdapter<String> adapter;

 

1) Set<BluetoothDevice> unpairedDevices

페어링 되지 않은 블루투스 기기 집합을 정의합니다.

 

2) List<String> unpairedList

페어링 되지 않은 기기들의 이름을 요소로 갖는 리스트를 정의하고 초기화합니다.

 

3) ArrayAdapter<String> adapter

ArrayAdapter <T>는 리스트뷰나 스피너와 같은 항목을 나열해서 보여주는 위젯에 사용되며, T 타입의 객체들을 저장해 해당 객체들을 문자열로 바꾸어 위젯에 제공합니다. 예를 들어 ArrayAdapter <String>에 블루투스 기기 이름들을 저장하고, 리스트뷰에 이 어댑터를 설정하면 원하는 항목들이 리스트뷰에 나열되어 보이는 것입니다.

즉, 데이터와 위젯(본 예제에선 다이얼로그)의 중간을 이어주는 역할을 한다고 생각하시면 되겠습니다.


본 예제에서는 페어링되지 않은 기기 이름이 다이얼로그에 나열되도록 할 것이므로, 기기 이름이 저장될 ArrayAdapter를 미리 정의합니다.

2. 브로드캐스트 수신자

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
	@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) {
                    if(device.getName() != null) {
                        add(device);
                    }
                }
            }
        }
    };

 

브로드캐스트는 모든 앱이 수신할 수 있는 메시지입니다. 안드로이드 시스템은 다양한 이벤트에 대해서 브로드캐스트를 전달하고, 이를 수신해서 필요한 동작을 수행할 수 있습니다.

예를 들어서 기기가 충전될 때 시스템이 브로드캐스트를 전달하며, 이를 수신해 화면에 특정 이미지를 띄우거나 하는 이벤트를 처리할 수 있습니다.

 

안드로이드 시스템은 원격 기기를 찾을 때 브로드캐스트를 전달합니다. 블루투스 기기를 검색하여 장치 이름을 전달받기 위해선 브로드캐스트를 수신해서 특정 동작을 취해야 하기 때문에, 이를 만들어줍니다. 그리고 onReceive() 메서드를 통해 브로드캐스트를 수신했을 때 필요한 동작을 수행하도록 합니다.

 

1) BluetoothDevice.ACTION_FOUND.equals(action)

먼저 intent.getAction() 메서드를 통해 수신된 브로드캐스트가 어떤 유형인지 가져옵니다.

만일 수신된 브로드캐스트가 ACTION_FOUND 브로드캐스트와 같다면, 다음 동작을 수행합니다.

ACTION_FOUND는 안드로이드 시스템이 원격 기기를 찾았을 때 전달하는 브로드캐스트입니다.

즉, 현재 수신된 브로드캐스트가 원격 기기(본 예제에서 블루투스 기기)를 찾았다는 브로드캐스트라면 다음 동작을 수행합니다.

 

2) 블루투스 기기 가져오기

getParcelableExtra() 메서드를 통해 검색한 블루투스 기기를 가져옵니다. 앞서 수신된 브로드캐스트의 인텐트에는 다양한 엑스트라 필드가 포함되어있습니다. 여기선 그중 블루투스 기기가 포함되어있는 EXTRA_DEVICE를 이용해 블루투스 기기를 가져옵니다.

 

3) 장치 목록 추가

해당 디바이스의 이름이 존재한다면 add() 메서드를 통해 해당 기기를 페어링 되지 않은 기기 목록과 기기 이름 목록에 추가합니다. add() 메서드는 직접 정의한 메서드로써, 아래에서 설명하겠습니다.

 

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*
        중략
        */
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);
        /*
        중략
        */
}

 

IntentFilter는 수신하고자 하는 인텐트 유형을 나타냅니다. 쉽게 말하면 어떤 메시지(브로드캐스트)를 받을 것인지를 설정하는 것입니다.


ACTION_FOUND는 원격 기기를 찾았을 때 브로드캐스트를 보냅니다. 이를 통해 블루투스 기기를 검색하고 기기를 찾았을 때 특정 동작을 수행하도록 합니다.
따라서 ACTION_FOUND 유형의 브로드캐스트를 수신하도록 하는 인텐트 필터를 만듭니다.

 

이후 registerReceiver() 메서드를 통해 메인 액티비티에 수신자를 등록합니다.

이때 파라미터로 앞서 만들어놓았던 브로드캐스트 수신자 mReceiver와 인텐트 필터 filter를 전달합니다.

 

이제 ACTION_FOUND 유형의 브로드캐스트가 수신되면, mReceiver에 재정의했던 onReceive() 메서드가 호출되면서 특정 동작을 수행하게 됩니다. 이는 수신자가 등록된 메인 액티비티가 유효할 때까지 유효합니다. 즉, 메인 액티비티가 종료되지 않고 계속 활동하고 있다면 계속해서 브로드캐스트를 수신하고 특정 동작을 수행할 수 있습니다.

 

이러한 과정은 메인 스레드에서 진행되지만, Context.registerReceiver() 메서드를 사용해 다른 스레드에서 작업하도록 할 수도 있습니다. 다만, 이 부분은 다소 복잡하므로 여기선 다루지 않겠습니다.

 

onCreate() 메서드 내부에서 인텐트 필터를 만들어줍니다. 따라서 앱이 활동을 시작할 때 인텐트 필터가 만들어집니다.

 

@Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }

 

마지막으로 onDestroy() 메서드를 재정의해 앱이 종료할 때 앞서 등록했던 수신자를 해제합니다.

3. 기기 및 기기이름 추가

private void add(BluetoothDevice device) {
        if(!(pairedDevices.contains(device))) {
            if(unpairedDevices.add(device)) {
                unpairedList.add(device.getName());
            }
        }
        adapter.notifyDataSetChanged();
        Toast.makeText(this, device.getName()+" 검색", Toast.LENGTH_SHORT).show();
    }

 

1) !(pairedDevices.contains(device))

페어링 되지 않은 기기 목록에 페어링 된 기기가 있으면 안될 것입니다. 따라서 기기를 추가하기 전에 페어링된 기기 목록에 해당 기기가 있는지 먼저 검사합니다. 페어링된 기기 목록에 해당 기기가 없다면 이후 동작을 수행합니다.

 

2) unpairedDevices.add(device)

Set은 중복된 요소가 추가되지 않으므로, 만일 요소가 추가된다면 add() 메서드는 true를 반환합니다.

즉, unpairedDevices.add(device)가 true라면 해당 기기는 중복되지 않은 것이므로, 페어링 되지 않은 기기 목록에 해당 기기를 추가합니다.

 

3) unpairedList.add(device.getName())

해당 기기의 이름을 페어링되지 않은 기기 이름 목록에 추가합니다.

 

4) adapter.notifyDataSetChanged();

어댑터 항목에 변화가 있음을 알려줍니다. 이후 메서드에서 unpairedList의 요소들이 adapter에 전달될 것입니다. 이때 add 메서드를 통해 새로운 요소가 추가된다면 이것이 adapter에도 반영이 되어야 사용자가 보는 다이얼로그에서도 항목이 추가될 것입니다. 따라서 notifyDataSetChanged() 메서드를 통해 adapter에 생긴 변화를 인지하고 이를 반영하도록 합니다.

 

5) 토스트 메시지

이후 토스트 메시지를 통해 해당 기기가 검색되었음을 알려줍니다.

 

4. 기기 탐색

private void selectUnpairedDevice() {
        if(bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
        bluetoothAdapter.startDiscovery();

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("기기 검색");

        adapter = new ArrayAdapter<>(this,
                android.R.layout.simple_list_item_1, unpairedList);

        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                bluetoothAdapter.cancelDiscovery();
                String name = adapter.getItem(which);
                unpairedList.remove(name);
                connectDevice(name);
            }
        });
        AlertDialog dialog = builder.create();
        dialog.show();
    }

 

이 부분은 앞서 살펴보았던, 페어링 된 기기 검색에서 사용되는 메서드와 유사합니다. 그러나 다이얼로그를 화면에 띄운다는 것을 제외하곤 다르기에, 다른 부분만 설명드리겠습니다.

 

1) startDiscovery()

startDiscovery() 메서드를 통해 기기 검색을 시작합니다.

만일 이미 기기를 검색하고 있었다면 검색을 취소한 다음, 다시 기기를 검색하도록 합니다.

 

2) adapter

어댑터를 생성합니다.

 

a) 첫 번째 파라미터

현재 Context 객체가 들어가며, 현재 액티비티인 메인 액티비티, 즉 자기 자신(this)을 Context 객체로 전달해줍니다.

 

b) 두 번째 파라미터

어댑터에 저장된 항목들이 어떻게 보여질 지를 정하는 레이아웃을 전달합니다.

simple_list_item_1은 안드로이드에서 이미 만들어놓은 레이아웃으로, 텍스트뷰가 하나로 구성된 레이아웃입니다.

사용 방법은 코드에 적어놓은 것과 같으며, 본 예제에서는 이 레이아웃을 사용하겠습니다.

 

c) 세 번째 파라미터

위젯(다이얼로그)에 표시할 데이터들의 집합을 전달합니다.

지금은 unpairedList 리스트를 전달합니다.

 

3) builder.setAdapter

빌더에 어댑터를 설정합니다. 첫 번째 파라미터로 설정할 어댑터를 전달한 후,

DialogInterface.OnClickListener() 설정해 다이얼로그에서 항목을 선택했을 때 특정 동작을 수행하도록 합니다. 본 예제에서는 기기가 선택되면 해당 기기를 연결하도록 하겠습니다.

 

a) 검색 종료

기기를 연결하기 전에 기기 검색을 종료해야 하므로, cancelDiscovery() 메서드를 호출합니다.

기기 검색에는 블루투스 어댑터의 리소스가 많이 소모되며, 검색 과정 자체가 기기를 연결할 때 사용할 수 있는 대역폭을 크게 감소시키기 때문입니다.

쉽게 말하면 자원을 많이 쓰고 기기가 느려지므로 연결 전에 검색을 종료합니다.

 

b) 페어링 된 기기 이름 삭제

which는 선택된 항목의 인덱스를 반환합니다. adapter.getItem() 메서드를 통해 선택된 인덱스에 해당하는 항목(블루투스 기기)의 이름을 가져옵니다.

그리고 이 항목은 기기와 연결되면서 자동으로 페어링 될 것이므로(이는 안드로이드 시스템에서 해줍니다), remove 메서드를 통해 unpairedList에서 해당 이름을 지웁니다.

 

c) 기기 연결

기기를 연결하는 connectDevice() 메서드를 호출합니다.

이때 paired를 false로 설정해서 파라미터로 전달합니다. 그러면 연결할 블루투스 기기를 페어링되지 않은 기기 목록에서 가져옵니다. 이는 기기 연결 부분에서 다시 다루겠습니다.

 

페어링되지 않은 기기를 탐색하는 다이얼로그는 다음과 같이 보여집니다.

 

페어링되지 않은 기기 검색

5. 마무리

이번 시간에는 페어링 되지 않은 기기를 검색하는 법에 대해 알아보았습니다.

다음 시간에는 기기 연결에 대해 알아보겠습니다.

이번 시간에 알려드린 정보가 많은 도움이 되었길 바랍니다.

 

728x90
반응형