마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

초보 Thread내부에서 alertdialog띄우는법 질문드립니다

0 추천

어찌저찌 인터넷자료들을 참고하여 아두이노 보드와 블루투스 통신을하는 코드는 어영부영 짜기는했습니다. 그래서 특정블루투스 신호를 받으면 핸드폰에서 알림을 띄우고 싶었는데 Thread안에서는 알림을 띄울수 없다고 하더라구요... 이게 mainactivity.java에서 다음과같이 thread를 실행시키고

package com.example.testapplication;

import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    String TAG = "MainActivity";
    UUID BT_MODULE_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); // "random" unique identifier

    TextView textStatus;
    Button btnParied, btnSearch, btnSend;
    ListView listView;

    BluetoothAdapter btAdapter;
    Set<BluetoothDevice> pairedDevices;
    ArrayAdapter<String> btArrayAdapter;
    ArrayList<String> deviceAddressArray;

    private final static int REQUEST_ENABLE_BT = 1;
    BluetoothSocket btSocket = null;
    ConnectedThread connectedThread;

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

        // Get permission
        String[] permission_list = {
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
        };
        ActivityCompat.requestPermissions(MainActivity.this, permission_list, 1);

    ...

            // start bluetooth communication
            if(flag){
                textStatus.setText("connected to "+name);
                connectedThread = new ConnectedThread(btSocket, MainActivity.this);
                connectedThread.start();

            }

        }
    }

    private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
        try {
            final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", UUID.class);
            return (BluetoothSocket) m.invoke(device, BT_MODULE_UUID);
        } catch (Exception e) {
            Log.e(TAG, "Could not create Insecure RFComm Connection",e);
        }
        return  device.createRfcommSocketToServiceRecord(BT_MODULE_UUID);
    }
}

이 밑의 코드가 ConnectedThread.java파일입니다.

package com.example.testapplication;

import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    private Context mcontext = null;

    public ConnectedThread(BluetoothSocket socket, Context context) {
        mmSocket = socket;
        this.mcontext = context;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
        }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    @Override
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.available();
                if (bytes != 0) {
                    buffer = new byte[1024];
                    SystemClock.sleep(100); //pause and wait for rest of data. Adjust this depending on your sending speed.
                    bytes = mmInStream.available(); // how many bytes are ready to be read?
                    bytes = mmInStream.read(buffer, 0, bytes); // record how many bytes we actually read


                    AlertDialog.Builder builder = new AlertDialog.Builder(mcontext);
                    builder.setTitle("Title");
                    builder.setMessage("Context");
                    builder.setPositiveButton("Yes", null);
                    builder.setNegativeButton("No", null);
                    builder.setNeutralButton("Cancel", null);
                    builder.create().show();
                }
            } catch (IOException e) {
                e.printStackTrace();

                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(String input) {
        byte[] bytes = input.getBytes();           //converts entered String into bytes
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) {
        }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
        }
    }
}

여기 run()내부에서 블루투스 신호를 읽어들이면 Alertdialog를 띄우려고했는데 에러로 앱이 뻗어버리더군요 ㅠㅠㅠ... 3~4시간동안 구글링을 동원해보았지만 Handler를 사용해야한다는것 같은데 감이 잡히질 않습니다. 도와주시며 정말 감사드리겠습니다.

상도동날다람쥐 (120 포인트) 님이 2022년 5월 15일 질문

1개의 답변

0 추천
 

화면을 업데이트 하는 건 메인쓰레드만 가능합니다. 백그라운드 쓰레드에서 메인쓰레드로 전환할 때는 Handler 또는 runOnUiThread(액티비티)를 사용하면 됩니다. 액티비티일 경우는 Handler가 큐에 집어넣고 다른 작업이 있을 경우 대기를 하는 반면, runOnUiThread는 대기없이 바로 실행되기 때문에, 액티비티라면 runOnUiThread를 사용하는 것이 조금 더 나은 옵션일 것 같습니다.

먼저, 아래처럼 콜백을 받을 수 있도록 Listener인터페이스를 선언하시고, 생성자에서 Context 제거하고 대신에 Listener인터페이스의 인스턴스를 setListener를 통해 받습니다. 그리고 데이터를 받으면 리스너를 호출합니다.

public class ConnectedThread extends Thread {

    interface Listener {
        void onMessageReceived(String message);
    }

    ...
    private Listener listener;
     
    public ConnectedThread(BluetoothSocket socket) {
        
        ...
    }

   public void setListener(Listener listener)) {
      this.listener = listener;
   }

    @Override
    public void run() {
        ...
        while (true) {
            try {
                ..
                if (bytes != 0) {
                   ...
 
                    if (this.listener != null) {
                        this.listener.onMessageReceived(new String(buffer));
                    }
                }
            } catch (IOException e) {
               ...
            }
        }
    }
 
   ...
}

 

이제 액티비티를 ConnectedThread.Listener를 구현하도록 합니다.

public class MainActivity extends AppCompatActivity implements ConnectedThread.Listener { //---> ConnectedThread.Listener 구현
 
      ...

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        
    ...
 
            // start bluetooth communication
            if(flag){
                ..
                connectedThread = new ConnectedThread(btSocket);
                ..

            }
 
        }
    }

   @Override
    public void onMessageReceived(String message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                showMessageReceivedAlert(message);    
            }
        });
    }

    private void showMessageReceivedAlert(String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Title");
        builder.setMessage(message);
        builder.setPositiveButton("Yes", null);
        builder.setNegativeButton("No", null);
        builder.setNeutralButton("Cancel", null);
        builder.create().show();
    }
}

 

ConnectedThread에 리스너를 할당하는 건, Activity가 화면에 보이지 않게 되는 경우를 위해(라이프싸이클 처리) onStart에서 하고 onStop에서는 해제를 합니다. 그래야 앱이 백그라운드에 있을 때 쓸데없이 dialog 보여주지 않게 되겠죠.

@Override
protected void onStart() {
   super.onStart();
   if (connectedThread != null) {
       connectedThread.setListener(this);
   }
}

@Override
protected void onStop() {
   super.onStop();
   if (connectedThread != null) {
       connectedThread.setListener(null);
   }
}

 

spark (227,510 포인트) 님이 2022년 5월 15일 답변
spark님이 2022년 5월 15일 수정
아직 스레드가 헷갈려서 그런가 좀 더 공부가 필요할것같네요... 코드보고 공부해보겠습니다 정말 감사합니다!!!!!
...