선생님, 개발을 잘하고 싶어요.

안드로이드 - 죽지않는 블루투스 스캐닝 본문

개발/android 개발

안드로이드 - 죽지않는 블루투스 스캐닝

알고싶은 승민 2018. 11. 22. 13:43

항상 블루투스를 스캐닝 하는 어플리케이션을 만들 일이 생겼다.


필요한 사항으론

 1. 블루투스를 "항상" 스캐닝 한다.

 2. 특정 블루투스가 감지되는지 안되는지 여부를 판단해 lost / found를 설정한다.


이를 위해 프로그램 개발 방향은

 0. 블루투스 권한 및 위치 권한 확보

 1. "항상" 스캐닝 할 수 있도록 service 를 구성한다.

 2. service 가 꺼지지 않도록 한다.

 3. lost / found 상태를 타이머를 통해 설정한다.


0.

메니페스트에 권한 설정을 해주고


1
2
3
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
cs



onCreate 시에 블루투스 확인과 위치권한 확인을 동적으로 수행해주자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
final BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getApplicationContext(),
        Manifest.permission.ACCESS_COARSE_LOCATION)
        != PackageManager.PERMISSION_GRANTED {
    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.ACCESS_COARSE_LOCATION)) {
        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.
    } else {
        // No explanation needed, we can request the permission.
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1234);
        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}
cs


1 - 2.

서비스 클래스를 만들고, onStartCommand 에 다음과 같이 service를 foreground 로 실행시켜준다. (foreground 로 실행하면 서비스가 강제 task killer에 의해 강제종료 되지 않는다.)


1
2
3
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null) {
        startForeground(1new Notification());
cs


3. 

 스캔을 쭉 받다가 found / ready 상태일때 일정시간 동안 스캔이 없으면 lost 감지한다. lost 상태에서 스캔을 받게되면 found 상태로 변경 시킨다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private BluetoothAdapter.LeScanCallback mLeScanCallbackWithTimer =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                    if (device.getAddress().equals(mTargetAddress)) {
                        if (mScanState == 0 || mScanState == 1) {
                            // 발견하면
                            didFoundBeacon();
                        }
                        // 타이머를 끄고
                        if (mMissingTimerTask != null) {
                            mScanMissing = 0;
                            mMissingTimerTask.cancel();
                        }
                        mMissingTimerTask = createMissingTimerTask();
                        // 타이머를 킨다.
                        mMissingTimer.schedule(mMissingTimerTask, MISSING_PERIOD);
                    }
                }
            };
 
private TimerTask createMissingTimerTask() {
        return new TimerTask() {
            @Override
            public void run() {
                mScanMissing++;
                addLogText(mScanMissing + " missing !");
                if (mScanMissing == mScanDepth) {
                    // 잃어버림
                    didLostBeacon();
                } else {
                    // 스캔을 다시시작
                    scanLeDeviceWithTimer(false);
                    mMissingTimerTask.cancel();
                    mMissingTimerTask = createMissingTimerTask();
                    mMissingTimer.schedule(mMissingTimerTask, MISSING_PERIOD);
                    scanLeDeviceWithTimer(true);
                }
            }
        };
    }
cs



mScanDepth 횟수 만큼 재시도를 하고, 그럼에도 실패했다면 lost 처리 한다.

해당 스캔이 발견될 당시 lost 나 ready 상태였다면 바로 found 처리를 해서 found 는 신호가 오는 즉시 발동하도록 하였다.


15번 줄에 TimerTask를 새로 만들어 준 부분은 타이머 cancel 하고 해당 객체를 사용해 다시 타이머를 등록하는게 불가능하더라; 그래서 멤버변수로 쓰려고 했던 MissingTimerTask를 그냥 TimerTask를 생성하는 함수로 만들고 항상 새로운 객체를 스케쥴링 하도록 제작 하였다.


<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

안드로이드 9 부터 위의 권한도 설정해주어야 한다. 다들 설정하자.


Comments