정보나눔

오픈소스하드웨어 프로젝트에 대한 다양한 정보를 나누는 공간입니다.

안드로이드 블루투스 연결 실패 문제입니다..
냠냠냠 | 2018-10-09
 
 
 
 
블루투스 통신하는 어플을 제작하고 싶어 https://m.blog.naver.com/PostView.nhn?blogId=2hyoin&logNo=220391696574&proxyReferer=https%3A%2F%2Fm.blog.naver.com%2FPostView.nhn%3FblogId%3Dcosmosjs%26logNo%3D220829796657%26proxyReferer%3Dhttps%253A%252F%252Fwww.google.co.kr%252F 글을 참고했는데 블루투스 연결실패 오류가 발생합니다. 원인이 궁금해서 글올려봅니다.  코드는 다음과 같습니다
package com.acontech.bluetoothtest;
 
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
public class MainActivity extends Activity {
    // 사용자 정의 함수로 블루투스 활성 상태의 변경 결과를 App으로 알려줄때 식별자로 사용됨(0보다 커야함) 
    static final int REQUEST_ENABLE_BT = 10;
    int mPariedDeviceCount = 0;
    Set<BluetoothDevice> mDevices;
   
 // 폰의 블루투스 모듈을 사용하기 위한 오브젝트.
    BluetoothAdapter mBluetoothAdapter;
   
 /** 
     BluetoothDevice 로 기기의 장치정보를 알아낼 수 있는 자세한 메소드 및 상태값을 알아낼 수 있다.
     연결하고자 하는 다른 블루투스 기기의 이름, 주소, 연결 상태 등의 정보를 조회할 수 있는 클래스.
     현재 기기가 아닌 다른 블루투스 기기와의 연결 및 정보를 알아낼 때 사용.
     */     
    BluetoothDevice mRemoteDevie;
    
// 스마트폰과 페어링 된 디바이스간 통신 채널에 대응 하는 BluetoothSocket
    BluetoothSocket mSocket = null;
    OutputStream mOutputStream = null;
    InputStream mInputStream = null;
    String mStrDelimiter = "\n";
    char mCharDelimiter =  '\n';
    
    
    Thread mWorkerThread = null;
    byte[] readBuffer;
    int readBufferPosition;
    
    EditText mEditReceive, mEditSend;
    Button mButtonSend;
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mEditReceive = (EditText)findViewById(R.id.receiveString);
        mEditSend = (EditText)findViewById(R.id.sendString);
        mButtonSend = (Button)findViewById(R.id.sendButton);
        
        mButtonSend.setOnClickListener(new OnClickListener(){
 
            @Override
            public void onClick(View v) {
                // 문자열 전송하는 함수(쓰레드 사용 x)
                sendData(mEditSend.getText().toString());
                mEditSend.setText("");
            }
        });
        
        // 블루투스 활성화 시키는 메소드
        checkBluetooth();
    }
    
    // 블루투스 장치의 이름이 주어졌을때 해당 블루투스 장치 객체를 페어링 된 장치 목록에서 찾아내는 코드. 
    BluetoothDevice getDeviceFromBondedList(String name) {
        // BluetoothDevice : 페어링 된 기기 목록을 얻어옴. 
        BluetoothDevice selectedDevice = null;
        // getBondedDevices 함수가 반환하는 페어링 된 기기 목록은 Set 형식이며, 
// Set 형식에서는 n 번째 원소를 얻어오는 방법이 없으므로 주어진 이름과 비교해서 찾는다. 
        for(BluetoothDevice deivce : mDevices) {
            // getName() : 단말기의 Bluetooth Adapter 이름을 반환
            if(name.equals(deivce.getName())) {
                selectedDevice = deivce;
                break;
            }
        }
        return selectedDevice;
    }
    
    // 문자열 전송하는 함수(쓰레드 사용 x)
    void sendData(String msg) {
        msg += mStrDelimiter;  // 문자열 종료표시 (\n)
        try{
            // getBytes() : String을 byte로 변환
            // OutputStream.write : 데이터를 쓸때는 write(byte[]) 메소드를 사용함. 
// byte[] 안에 있는 데이터를 한번에 기록해 준다.   
            mOutputStream.write(msg.getBytes());  // 문자열 전송.
        }catch(Exception e) {  // 문자열 전송 도중 오류가 발생한 경우
            Toast.makeText(getApplicationContext(), "데이터 전송중 오류가 발생"
Toast.LENGTH_LONG).show();
            finish();  // App 종료
        }
    }
 
    //  connectToSelectedDevice() : 원격 장치와 연결하는 과정을 나타냄.
//  실제 데이터 송수신을 위해서는 소켓으로부터 입출력 스트림을 얻고 입출력 스트림을 이용하여 이루어 진다. 
    void connectToSelectedDevice(String selectedDeviceName) {
        // BluetoothDevice 원격 블루투스 기기를 나타냄.
        mRemoteDevie = getDeviceFromBondedList(selectedDeviceName);
        // java.util.UUID.fromString : 자바에서 중복되지 않는 Unique 키 생성.
        UUID uuid = java.util.UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
        
        try {
            // 소켓 생성, RFCOMM 채널을 통한 연결. 
            // createRfcommSocketToServiceRecord(uuid) : 이 함수를 사용하여 원격 블루투스 장치와 
// 통신할 수 있는 소켓을 생성함. 
            // 이 메소드가 성공하면 스마트폰과 페어링 된 디바이스간 통신 채널에 대응하는 
// BluetoothSocket 오브젝트를 리턴함. 
            mSocket = mRemoteDevie.createRfcommSocketToServiceRecord(uuid);
            mSocket.connect(); // 소켓이 생성 되면 connect() 함수를 호출함으로써 두기기의 연결은 완료된다. 
            
            // 데이터 송수신을 위한 스트림 얻기. 
            // BluetoothSocket 오브젝트는 두개의 Stream을 제공한다. 
            // 1. 데이터를 보내기 위한 OutputStrem
            // 2. 데이터를 받기 위한 InputStream
            mOutputStream = mSocket.getOutputStream();
            mInputStream = mSocket.getInputStream();
            
            // 데이터 수신 준비. 
            beginListenForData();
            
        }catch(Exception e) { // 블루투스 연결 중 오류 발생
            Toast.makeText(getApplicationContext(), 
"블루투스 연결 중 오류가 발생했습니다.", Toast.LENGTH_LONG).show();
            finish();  // App 종료
        }
    }
    
    // 데이터 수신(쓰레드 사용 수신된 메시지를 계속 검사함)
    void beginListenForData() {
        final Handler handler = new Handler();
        
        readBufferPosition = 0;                 // 버퍼 내 수신 문자 저장 위치. 
        readBuffer = new byte[1024];            // 수신 버퍼.
        
        // 문자열 수신 쓰레드. 
        mWorkerThread = new Thread(new Runnable() 
        {
            @Override
            public void run() {
      // interrupt() 메소드를 이용 스레드를 종료시키는 예제이다. 
// interrupt() 메소드는 하던 일을 멈추는 메소드이다.  
      // isInterrupted() 메소드를 사용하여 멈추었을 경우 반복문을 나가서 스레드가 종료하게 된다. 
                while(!Thread.currentThread().isInterrupted()) {
                    try {
      // InputStream.available() : 다른 스레드에서 blocking 하기 전까지 읽은 수 있는 문자열 개수를 반환함. 
                        int byteAvailable = mInputStream.available();   // 수신 데이터 확인
                        if(byteAvailable > 0) {                        // 데이터가 수신된 경우. 
                            byte[] packetBytes = new byte[byteAvailable];
                            // read(buf[]) : 입력스트림에서 buf[] 크기만큼 읽어서 저장 없을 경우에 -1 리턴.
                            mInputStream.read(packetBytes);
                            for(int i=0; i<byteAvailable; i++) {
                                byte b = packetBytes[i];
                                if(b == mCharDelimiter) {
                                    byte[] encodedBytes = new byte[readBufferPosition];
                  //  System.arraycopy(복사할 배열, 복사시작점, 복사된 배열, 붙이기 시작점, 복사할 개수)
                   //  readBuffer 배열을 처음 부터 끝까지 encodedBytes 배열로 복사. 
                              System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
                                    
                                    final String data = new String(encodedBytes, "US-ASCII");
                                    readBufferPosition = 0;
                                    
                                    handler.post(new Runnable(){
                                        // 수신된 문자열 데이터에 대한 처리. 
                                        @Override
                                        public void run() {
                                            // mStrDelimiter = '\n';
                                            mEditReceive.setText(mEditReceive.getText().toString() + data+ mStrDelimiter);
                                        }
                                        
                                    });
                                }
                                else {
                                    readBuffer[readBufferPosition++= b;
                                }
                            }
                        }
                        
                    } catch (Exception e) {    // 데이터 수신 중 오류 발생. 
                        Toast.makeText(getApplicationContext(), "데이터 수신 중 오류가 발생 했습니다.", Toast.LENGTH_LONG).show();
                        finish();            // App 종료.        
                    }
                }
            }
            
        });
    
    }
    
    // 블루투스 지원하며 활성 상태인 경우.
    void selectDevice() {
        // 블루투스 디바이스는 연결해서 사용하기 전에 먼저 페어링 되어야만 한다
        // getBondedDevices() : 페어링된 장치 목록 얻어오는 함수.  
        mDevices = mBluetoothAdapter.getBondedDevices();
        mPariedDeviceCount = mDevices.size();
        
        if(mPariedDeviceCount == 0 ) { // 페어링된 장치가 없는 경우. 
            Toast.makeText(getApplicationContext(), "페어링된 장치가 없습니다.", Toast.LENGTH_LONG).show();
            finish(); // App 종료. 
        }
        // 페어링된 장치가 있는 경우.
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("블루투스 장치 선택");
        
        // 각 디바이스는 이름과(서로 다른) 주소를 가진다. 페어링 된 디바이스들을 표시한다. 
        List<String> listItems = new ArrayList<String>();
        for(BluetoothDevice device : mDevices) {
            // device.getName() : 단말기의 Bluetooth Adapter 이름을 반환. 
            listItems.add(device.getName());
        }
        listItems.add("취소");  // 취소 항목 추가. 
        
        
        // CharSequence : 변경 가능한 문자열. 
        // toArray : List형태로 넘어온것 배열로 바꿔서 처리하기 위한 toArray() 함수.
        final CharSequence[] items = listItems.toArray(new CharSequence[listItems.size()]);
          // toArray 함수를 이용해서 size만큼 배열이 생성 되었다. 
          listItems.toArray(new CharSequence[listItems.size()]);
          
          builder.setItems(items, new DialogInterface.OnClickListener() {
 
            @Override
            public void onClick(DialogInterface dialog, int item) {
                // TODO Auto-generated method stub
                if(item == mPariedDeviceCount) { // 연결할 장치를 선택하지 않고 '취소' 를 누른 경우. 
                    Toast.makeText(getApplicationContext(), "연결할 장치를 선택하지 않았습니다.", Toast.LENGTH_LONG).show();
                    finish();
                }
                else { // 연결할 장치를 선택한 경우, 선택한 장치와 연결을 시도함. 
                    connectToSelectedDevice(items[item].toString());
                }
            }
              
          });
          
          builder.setCancelable(false);  // 뒤로 가기 버튼 사용 금지. 
          AlertDialog alert = builder.create();
          alert.show();
    }
 
    
    void checkBluetooth() {
        /** 
         * getDefaultAdapter() : 만일 폰에 블루투스 모듈이 없으면 null 을 리턴한다. 
                               이경우 Toast를 사용해 에러메시지를 표시하고 앱을 종료한다.
         */ 
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if(mBluetoothAdapter == null ) {  // 블루투스 미지원
            Toast.makeText(getApplicationContext(), "기기가 블루투스를 지원하지 않습니다.", Toast.LENGTH_LONG).show();
            finish();  // 앱종료
        }
        else { // 블루투스 지원
            /** isEnable() : 블루투스 모듈이 활성화 되었는지 확인.
             *               true : 지원 ,  false : 미지원
             */            
            if(!mBluetoothAdapter.isEnabled()) { // 블루투스 지원하며 비활성 상태인 경우.
                Toast.makeText(getApplicationContext(), "현재 블루투스가 비활성 상태입니다.", Toast.LENGTH_LONG).show();
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 
                // REQUEST_ENABLE_BT : 블루투스 활성 상태의 변경 결과를 App 으로 알려줄 때 식별자로 사용(0이상)
                /** 
                  startActivityForResult 함수 호출후 다이얼로그가 나타남 
                  "예" 를 선택하면 시스템의 블루투스 장치를 활성화 시키고 
                  "아니오" 를 선택하면 비활성화 상태를 유지 한다.
                  선택 결과는 onActivityResult 콜백 함수에서 확인할 수 있다. 
                  */
                startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
            }
            else // 블루투스 지원하며 활성 상태인 경우.
                selectDevice();
        }
    }
    
    
 
 
    // onDestroy() : 어플이 종료될때 호출 되는 함수.
    //               블루투스 연결이 필요하지 않는 경우 입출력 스트림 소켓을 닫아줌. 
    @Override
    protected void onDestroy() {
        try{
            mWorkerThread.interrupt(); // 데이터 수신 쓰레드 종료
            mInputStream.close();
            mSocket.close();
        }catch(Exception e){}
        super.onDestroy();
    }
    
    
  // onActivityResult : 사용자의 선택결과 확인 (아니오, 예)
  // RESULT_OK: 블루투스가 활성화 상태로 변경된 경우. "예"
  // RESULT_CANCELED : 오류나 사용자의 "아니오" 선택으로 비활성 상태로 남아 있는 경우  RESULT_CANCELED
/** 
사용자가 request를 허가(또는 거부)하면 안드로이드 앱의 onActivityResult 
메소도를 호출해서 request의 허가/거부를 확인할수 있다. 
첫번째 requestCode: startActivityForResult 에서 사용했던 요청 코드. REQUEST_ENABLE_BT 값
두번째 resultCode : 종료된 액티비티가 setReuslt로 지정한 결과 코드. 
RESULT_OK, RESULT_CANCELED 값중 하나가 들어감. 
세번째 data   : 종료된 액티비티가 인테트를 첨부했을 경우, 그 인텐트가 들어있고 첨부하지 않으면 null
*/
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   // startActivityForResult 를 여러번 사용할 땐 이런 식으로 
// switch 문을 사용하여 어떤 요청인지 구분하여 사용함. 
        switch(requestCode) {
        case REQUEST_ENABLE_BT:
            if(resultCode == RESULT_OK) { // 블루투스 활성화 상태
                selectDevice();
            }
            else if(resultCode == RESULT_CANCELED) { // 블루투스 비활성화 상태 (종료)
                Toast.makeText(getApplicationContext(), "블루투수를 사용할 수 없어 프로그램을 종료합니다",
 Toast.LENGTH_LONG).show();
                finish();
            }
            break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    
}
이전글   |    투명한 디스플레이 2018-10-09
다음글   |    자이로 센서-모터-초음파 센서 연결에 관해 질문 드립니다. ... 2018-10-09