안드로이드가 개발한 블루투스 통신

반년 만에 블루투스 개발을 만났습니다. 예전에는 블루투스 연결 프린트와 관련된 부분이었는데 이번에는 블루투스 배합 데이터 전송이 필요합니다. 이리저리 뒤척이는 것도 그렇습니다. 이 지식을 체계적으로 정리하기로 결심했습니다.
블루투스 개발 필수 기본기
Bluetooth 사용 권한
응용 프로그램에서 블루투스 기능을 사용하기 위해서는 블루투스 권한을 설명해야 합니다.연결을 요청하고 연결을 받아들이고 데이터를 전송하는 등 블루투스 통신을 수행할 수 있는 권한이 필요합니다.응용 프로그램 시작 장치에서 블루투스 설정을 발견하거나 조종하려면 블루투스 설정을 신고해야 합니다admin 라이센스.대부분의 응용 프로그램은 로컬 블루투스 장치를 발견하는 능력에만 이 권한을 필요로 한다.이 권한이 부여한 다른 권한은 사용할 수 없습니다. 프로그램이 '전원 관리자' 가 아니면 사용자가 요청한 블루투스 설정을 수정합니다.주의: 블루투스를 사용하면관리자 허가, 그럼 너도 블루투스 허가가 있어야 해.
<manifest ... >
  <uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

Bluetooth 설정
응용 프로그램이 블루투스를 통해 통신할 수 있기 전에 블루투스 장치에서 지원되는지 확인해야 합니다. 만약 그렇다면, 블루투스 장치가 활성화되었는지 확인하십시오.만약 블루투스가 지원되지 않는다면, 어떤 블루투스 기능도 우아하게 비활성화해야 합니다.만약 블루투스가 지원되지만, 사용하지 않는다면, 사용자는 블루투스를 사용하도록 요구할 수 있으며, 응용 프로그램을 떠나지 않을 것이다.이 설정은 블루투스 어댑터를 사용하는 두 단계로 이루어집니다.
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    //         
}

다음 단계에서는 블루투스 기능이 활성화되었는지 확인해야 합니다.isenabled () 는 블루투스가 현재 활성화되어 있는지 확인합니다.이 방법이 오류를 반환하면 블루투스를 사용하지 않습니다.블루투스 활성화, startactivityforresult() 및 actionrequest_enable 작업 의도.이것은 블루투스가 시스템 설정을 통과하도록 요청할 것입니다. (응용 프로그램을 막지 않습니다.)예를 들면 다음과 같습니다.
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

면맞춤 장치 조회
Bluetooth 어댑터 Bluetooth Adapter를 사용하면 조회 목록을 통해 원격 Bluetooth 장치 쌍을 찾을 수 있습니다.장치 발견은 검색 프로그램으로 로컬 지역의 블루투스 기능을 검색한 장치입니다. 그리고 일부 정보를 요구합니다. (때로는 '발견', '조회' 또는 '스캔' 이라고 불리기도 합니다.) 그러나 랜 내의 블루투스 장치는 발견 요청에 응답할 수 있는 것만 발견됩니다.만약 장치가 발견될 수 있다면, 장치 이름, 클래스, 그리고 독특한 MAC 주소로 검색 요청에 응답하고 정보를 공유할 것입니다.이 정보를 사용하여 검색된 장치를 실행하면 검색된 장치에 연결을 시작할 수 있습니다.
한 연결이 처음으로 원격 장치와 연결되면, 짝짓기 요청이 자동으로 사용자에게 제출됩니다.장치가 연결되면 장치의 기본 정보 (예를 들어 장치 이름, 클래스, 주소) 가 저장되고 블루투스 이어폰을 사용할 수 있습니다.원격 장치의 알려진 맥 주소를 사용하면 언제든지 연결을 시작할 수 있습니다. (장치가 범위 내에 있다고 가정합니다.)
짝짓기와 연결된 것 사이의 차이가 있다는 것을 명심해라.짝짓기는 두 장치가 서로의 존재를 알고 있다는 것을 의미한다. 공유된 체인 키가 있어 인증에 사용할 수 있고 암호화된 상호 연결을 구축할 수 있다.연결하려면 현재 장치가 RFCOMM 채널을 공유하여 서로 데이터를 전달할 수 있음을 의미한다.현재 안드로이드 블루투스 API의 장치는 RFCOMM에 짝을 지어 연결할 수 있어야 한다.(블루투스 인터페이스와의 암호화 연결을 시작할 때 자동으로 짝짓기를 실행합니다.
BluetoothDevices는 본 컴퓨터의 블루투스와 연결된 원격 블루투스 정보를 가져옵니다. BluetoothDevice 클래스의 실례로 되돌아옵니다. 블루투스가 켜지면 이 함수는 빈 집합을 되돌려줍니다.
Set<BluetoothDevice> 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() + "
"
+ 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() + "
"
+ 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);

블루투스와 서비스 측, 클라이언트에 대한 링크는 Bluetooth ServerSocket, Bluetooth Socket과 관련이 있기 때문에 할 말이 없습니다. 잠시 후에 공식 단순을 통해 보면 됩니다.
Bluetooth 저전력 Android 기반 4.3
위와 비교하면 권한은 다음과 같은 두 가지가 있어야 한다(4.3의 저소모 블루투스 스캐닝 startLeScan 방법은 BLUETOOTH ADMIN 권한이 있어야 한다)
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

만약 응용 프로그램이 4.3 이상의 휴대전화 장치로 설치되어 있고 저소모 블루투스를 지원한다고 확신한다면 명세서 파일에서 다음과 같이 설명할 수 있다
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

만약 당신의 앱이 사용하는 장치가 저소모 블루투스를 지원하는지 확실하지 않지만, 지원하는 장치가 저소모 블루투스를 사용하도록 하려면, 당신은 이렇게 할 수 있습니다
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

Bluetooth Adapter는 Bluetooth Manager를 통해 얻을 수 있습니다. Bluetooth Manager가 불러올 때 구조 함수가 Bluetooth Adapter를 호출했기 때문입니다.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();
           }
       });
   }
};

Bluetooth Gatt Bluetooth Gatt Callback 등과 관련된 Gat류는 대동소이하며 구글이 공식적으로 제공한 단순에 따라 한 번 훑어보면 된다.블루투스 개발 디버깅https://developer.android.com/training/wearables/apps/bt-debugging.html
구글 공식 블루투스 오픈 프로젝트
홈페이지를 통해 세 개의 소스 오픈 프로젝트를 찾았지만 다운로드할 수 없을 것 같아서github를 통해 구글 창고를 검색할 수 밖에 없습니다.
  • BluetoothAdvertisements
  • BluetoothLeGatt
  • BluetoothChat

  • 먼저 android-Bluetooth Advertisements 실행 효과도를 보십시오
    Bluetooth Advertisements에서 프로젝트를 시작하려면 먼저 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();
    
        }
    

    스캔 코드 블록은 Bluetooth Adapter를 사용하지 않습니다.start Scan, 원본 코드를 보니 여전히 유행이 지난 방법입니다. 단순함에서 adapter를 통해 Bluetooth Le Scanner를 직접 가져오고, scanner의 start Scan을 호출했습니다.
    public void startScanning() {
      // ......... ............
    
      mScanCallback = new SampleScanCallback();
      mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
    }
    

    그러나 이 소스 항목을 다운로드하고 효과를 보려고 한다면, 점원은 절대로 성공하지 못할 것이라고 말할 수 밖에 없다. buildscanFilters에 스캔 필터 규칙이 추가되어 있어 uiid가 일치하지 않으면 아무것도 볼 수 없다. 코드 알림에 따라 아래의 필터 조건을 설명하면 모든 블루투스 장치를 볼 수 있다.
      private List<ScanFilter> buildScanFilters() {
            List<ScanFilter> scanFilters = new ArrayList<>();
    
            ScanFilter.Builder builder = new ScanFilter.Builder();
            //             ,     (uuid         )
           // builder.setServiceUuid(Constants.Service_UUID);
            //scanFilters.add(builder.build());
    
            return scanFilters;
        }

    저전력 모드에서 Bluetooth 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<ScanResult> 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);
              //    
            }
        }

    스캔이 시작되기 전의 논리적 판단은 단순 MainActivity를 참조합니다.Advertiser Service와 관련된 차이는 생략하겠습니다. android-Bluetooth LeGatt라는 단순함은 개인적으로 별로 특별하지 않다고 생각하는 핵심에 언급되어 있습니다. android 4.3+버전의 블루투스 개발 단순,mainfest를 보면 서비스 개인이 좀 엉망진창인 것 같아요.
    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 메시지를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를 제공합니다.자바(이 도구류는 정리가 완벽하지 않아서 스캔과 링크 호환에 대해 모두 쓰지 않았습니다. 구체적인 개발 링크는 공식 단순한ChartService를 참조하고 LE와 관련된 스캔은 단순을 참조하십시오. 호환은 주로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<ScanFilter> buildScanFilters() {
            List<ScanFilter> 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<BluetoothDevice> 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)) {
    
                }
            }
        };
    }

    소결
    지식을 정리하고 맥락을 파악하면 아무리 변해도 근본을 떠나지 않는다.
    참고 자료
    https://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

    좋은 웹페이지 즐겨찾기