在蓝牙聊天应用程序中处理线程之间的消息

时间:2015-03-19 13:59:08

标签: android multithreading android-activity bluetooth handler

我正在开发一个蓝牙聊天应用程序,这是SDK附带的示例中的一个扩展版本。它看起来更像是WhatsApp。我有主要的Conversations活动(基本上是包含用户聊天的列表视图),以及实际的聊天活动(BluetoothChatActivity),文本输入和发送按钮的文本视图等。我有BluetoothChatService类,就像在示例中一样但问题是会话活动和聊天活动都应该使用它(ConversationsActivity用于连接,BluetoothChatActivity用于消息传递)。每个活动都有自己的处理程序,例如当我尝试使用BluetoothChatActivity类编写消息时,ConversationsActivity处理程序就是处理它的人。我所做的是为每个活动设置一个BluetoothChatService成员,在它的构造函数中,我传递了活动的处理程序,一次在ConversationsActivity中,一次在BluetoothChatActivity中。 我长期坚持这个问题而且我不知道该怎么办......如果有人能帮助我,我会很高兴。

谢谢:)

添加代码(它不是everthing,只是相关部分):

ConversationsActivity:

public class ConversationsActivity extends ListActivity implements OnItemClickListener {

private static final String TAG = "ConversationsActivity";
public static final String DIRECTORY_PATH = Environment.getExternalStorageDirectory()+"/ChatApp";

// Message types sent from the BluetoothChatService Handler
public static final int START_CONVERSATION = 7;

public final Handler messagesHandler = new Handler() {
    @Override
    public void handleMessage(Message receivedMessage) {
        Log.d(TAG, "Inside Handle message of conversation activity");
        Log.d(TAG, String.valueOf(receivedMessage.what));
        Log.d(TAG, String.valueOf(chatService.getState()));
        switch (receivedMessage.what) {
            case START_CONVERSATION:
                String address = receivedMessage.getData().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                String deviceName = receivedMessage.getData().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
                Log.d(TAG, "address = " + address + " deviceName = " + deviceName);

                if (conversations.getUserByAddress(address) == null) // If the device is not inside the conversation array list
                { 
                    // Then add it to the conversation list
                    conversations.addConversation(new User(deviceName, address));
                    refreshConversationsList();
                }
                Log.e("Inside Connect Device", "YAY");
                // Get the device MAC address

                Intent chatIntent = new Intent(getBaseContext(), BluetoothChatActivity.class);
                chatIntent.putExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS, address);
                chatIntent.putExtra(DeviceListActivity.EXTRA_DEVICE_NAME, deviceName);
                startActivity(chatIntent);
                break;
            case BluetoothChatActivity.MESSAGE_BE_SERVER:
                chatService.stop();
                chatService.start();
                break;
        }
    }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initializeComponenets();

}

// Initialize neeed data
private void initializeComponenets() {
    // TODO Auto-generated method stu
    chatService = new BluetoothChatService(this, messagesHandler);
}
@Override
protected void onResume() {
    super.onResume();
    Log.e(TAG, "onResume");

    if (chatService != null) {
        // Only if the state is STATE_NONE, do we know that we haven't
        // started already. Of course we check that BT is enabled
        System.out.println(chatService.getState());
        if (chatService.getState() == BluetoothChatService.STATE_NONE && bluetoothAdapter.isEnabled()) {
            // Start the Bluetooth chat services
            Log.d(String.valueOf(chatService.getState()), "IN RESUME");
            chatService.start();
        }
    }

    if (conversations.size() != 0)
        refreshConversationsList();
}    

BluetoothChatActivity:

public class BluetoothChatActivity extends Activity implements OnClickListener 
{

// The Handler that gets information back from the BluetoothChatService
public final Handler messagesHandler = new Handler() {
    @Override
    public void handleMessage(Message receivedMessage) {
        Log.d(TAG, "Message has obtained: " + String.valueOf(receivedMessage.what));
        switch (receivedMessage.what) {
        case MESSAGE_STATE_CHANGE:
            switch (receivedMessage.arg1) {
            case BluetoothChatService.STATE_CONNECTED:
                Log.d(TAG, "state changed - STATE_CONNECTED");
                conversationMessagesArrayAdapter.clear();
                break;
            case BluetoothChatService.STATE_CONNECTING:
                Log.d(TAG, "state changed - STATE_CONNECTING");
                actionBar.setSubtitle(getString(R.string.conversation_title_connecting));
                break;
            case BluetoothChatService.STATE_LISTEN:
                Log.d(TAG, "state changed - STATE_LISTEN");
            case BluetoothChatService.STATE_NONE:
                Log.d(TAG, "state changed - STATE_NONE");
                actionBar.setSubtitle(getString(R.string.conversation_title_not_connected));
                break;
            }
            break;
        case MESSAGE_WRITE:
            Log.d(TAG, "Inside MESSAGE_WRITE");
            byte[] writeBuf = (byte[]) receivedMessage.obj;
            // construct a string from the buffer
            String writeMessage = new String(writeBuf);
            conversationMessagesArrayAdapter.add("Me:  " + writeMessage);
            refreshConversationChat();
            break;
        case MESSAGE_READ:
            Log.d(TAG, "MESSAGE_READ");
            byte[] readBuf = (byte[]) receivedMessage.obj;
            // construct a string from the valid bytes in the buffer
            String readMessage = new String(readBuf, 0, receivedMessage.arg1);
            Log.e(TAG, "message received = " + readMessage);
            conversationMessagesArrayAdapter.add(connectedDeviceName + ":  " + readMessage);
            refreshConversationChat();
            break;
        case MESSAGE_DEVICE_NAME:
            // save the connected device's name
            connectedDeviceName = receivedMessage.getData().getString(DEVICE_NAME);
            actionBar.setSubtitle(getString(R.string.conversation_title_connected_to) + " " + connectedDeviceName);
            Toast.makeText(getApplicationContext(), "Connected to "
                           + connectedDeviceName, Toast.LENGTH_SHORT).show();
            break;
        case MESSAGE_TOAST:
            Toast.makeText(getApplicationContext(), receivedMessage.getData().getString(TOAST),
                           Toast.LENGTH_SHORT).show();
            break;
        case MESSAGE_CANCEL_CONNECTION:
            cancelConnection();
            break;
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bluetooth_chat);
    initializeComponents();
}

private void initializeComponents() {
    chatService = new BluetoothChatService(this, messagesHandler);
    connectDevice(getIntent());
}    

BluetoothChatService:

public class BluetoothChatService {

private final BluetoothAdapter bluetoothAdapter;
private final Handler messagesHandler; // A handler to send messages back to the UI activity
private AcceptThread acceptThread;
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private int currentState;
private boolean isServer = false;

public static final String DEVICE_NAME = "deviceName";
public static final String DEVICE_ADDRESS = "deviceAddress";

// For Debugging
private static final String TAG = "BluetoothChatService";

public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device

private static final String NAME = "BluetoothChat";

private static UUID APP_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); // Default UUID at the beggining

public BluetoothChatService(Context context, Handler handler)
{
    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    messagesHandler = handler;
    currentState = STATE_NONE;
}

// this method sets the current state of the chat connection
private synchronized void setState(int state)
{
    Log.d(TAG, "setState");
    currentState = state;
    // Give the new state to the Handler so the UI Activity can update
    String stateDebug = "";
    if (currentState == STATE_LISTEN)
        isServer = true;
    else
        isServer = false;
    messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
}

// This method returns the current connection state
public synchronized int getState()
{
    String stateDebug = "";
    switch (currentState)
    {
        case 0:
            stateDebug = "STATE_NONE";
            break;
        case 1:
            stateDebug = "STATE_LISTEN";
            break;
        case 2:
            stateDebug = "STATE_CONNECTING";
            break;
        case 3: 
            stateDebug = "STATE_CONNECTED";
            break;
    }
    Log.d(TAG, stateDebug);
    return currentState;
}

/*
 * This method starts the chat service. It initiates the Accept Thread in order to listen
 * for incoming requests
 */

public synchronized void start()
{
    Log.d(TAG, "start");
    // Cancel any thread attempting to make a connection
    if (connectThread != null)
    {
        connectThread.cancel();
        connectThread = null;
    }

    // Cancel any thread currently running a connection
    if (connectThread != null)
    {
        connectThread.cancel();
        connectThread = null;
    }

    setState(STATE_LISTEN);

    if (acceptThread == null)
    {
        acceptThread = new AcceptThread();
        acceptThread.start();

    }
    isServer = true;
}

/*
 * This method starts the ConnectThread to make a connection to a remote device
 * This method receives as paramter the bluetooth device to connect to
 */

public synchronized void connect(BluetoothDevice remoteDevice)
{
    Log.d(TAG, "connect");
    // Cancel any thread attempting to make a connection
    if (currentState == STATE_CONNECTING)
    {
        if (connectThread != null)
        {
            connectThread.cancel();
            connectThread = null;
        }
    }

    // Cancel any thread currently running a connection
    // TODO: make the connected thread
    if (connectedThread != null) {
        connectedThread.cancel();
        connectedThread = null;
    }

    // Start the thread to connect to the given device
    connectThread = new ConnectThread(remoteDevice);
    connectThread.start();
    isServer = false;
    setState(STATE_CONNECTING);
}

public synchronized void connected (BluetoothSocket socket, BluetoothDevice remoteDevice, 
        final String socketType)
{
    Log.d(TAG, "connected");
    // Cancel the thread that completed the connection
        if (connectThread != null) {
            connectThread.cancel();
            connectThread = null;
        }
            // Cancel any thread currently running a connection
        if (connectedThread != null) {
            connectedThread.cancel();
            connectedThread = null;
        }

        // Cancel the accept thread because we only want to connect to one
        // device
        if (acceptThread != null) {
            acceptThread.cancel();
            acceptThread = null;
        }
        // Start the thread to manage the connection and perform transmissions
        connectedThread = new ConnectedThread(socket, socketType);
        connectedThread.start();            


        if (currentState != STATE_CONNECTED)
        {
            getState();
            // Send the name of the connected device back to the UI Activity
            if (!isServer)
            {
                Message msg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_DEVICE_NAME);
                Bundle bundle = new Bundle();
                bundle.putString(BluetoothChatActivity.DEVICE_NAME, remoteDevice.getName());
                msg.setData(bundle);
                messagesHandler.sendMessage(msg);
            }
            else
            {
                Message msg = messagesHandler.obtainMessage(ConversationsActivity.START_CONVERSATION);
                Bundle bundle = new Bundle();
                bundle.putString(DeviceListActivity.EXTRA_DEVICE_NAME, remoteDevice.getName());
                bundle.putString(DeviceListActivity.EXTRA_DEVICE_ADDRESS, remoteDevice.getAddress());
                Log.e(TAG, "address = " + DeviceListActivity.EXTRA_DEVICE_ADDRESS + remoteDevice.getAddress() + " Name = " + DeviceListActivity.EXTRA_DEVICE_NAME + remoteDevice.getName());
                msg.setData(bundle);
                messagesHandler.sendMessage(msg);
            }

            setState(STATE_CONNECTED);
            Log.d(TAG, "Message state change");
            messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
        }
}



/*
 * This method stops all of the threads
 */
public synchronized void stop()
{
    Log.d(TAG, "stop");
    if (connectThread != null)
    {
        connectThread.cancel();
        connectThread = null;
    }

    if (acceptThread != null)
    {
        acceptThread.cancel();
        acceptThread = null;
    }

    messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_CANCEL_CONNECTION).sendToTarget();

    // TODO: continue with the connected thread
    setState(STATE_NONE);
}

/*
 * This method indicates that the connection to the device has failed and it notifies the UI activity
 */

public void connectionFailed()
{
    Log.d(TAG, "connectionFailed");
    // Send a failure message back to the Activity
    setState(STATE_NONE);
    messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
    Message failedMsg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(BluetoothChatActivity.TOAST, "Unable to connect devices");
    failedMsg.setData(bundle);
    messagesHandler.sendMessage(failedMsg);

    // Start the service over to restart listening mode
    BluetoothChatService.this.start();
    setState(STATE_NONE);
}

/*
 * This method indicates that the connection to the device has failed and it notifies the UI activity
 */

public void connectionLost()
{
    Log.d(TAG, "connectionLost");
    // Send a failure message back to the activity
    setState(STATE_NONE);
    //messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
    Message lostMsg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST);
    Bundle bundle = new Bundle();
    bundle.putString(BluetoothChatActivity.TOAST, "Device connection was lost");
    lostMsg.setData(bundle);
    messagesHandler.sendMessage(lostMsg);

    // Start the service over to restart listening mode
    BluetoothChatService.this.start();
}

public void write(byte[] out) {
    Log.d(TAG, "Write()");
    // Create temporary object
    ConnectedThread tmp;
    // Synchronize a copy of the ConnectedThread
    synchronized (this) {
        if (currentState != STATE_CONNECTED)
            return;
        tmp = connectedThread;
    }
    // Perform the write unsynchronized
    tmp.write(out);
}


/*
 * This thread runs while listening for incoming connections. It behaves
 * like a server-side client. It runs until a connection is accepted (or
 * until cancelled).
 */

private class AcceptThread extends Thread
{
    // The local server socket
    private final BluetoothServerSocket bluetoothServerSocket;

    private String socketType;

    public AcceptThread()
    {
        // A temporary object that later will be assigned to bluetoothServerSocket,
        // Because it is final
        BluetoothServerSocket tmpSocket = null;
        try
        {
            tmpSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, APP_UUID);
        }
        catch (IOException e)
        {
            Log.e("Failed", "Temp server");
        }
        finally
        {
            bluetoothServerSocket = tmpSocket;
        }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        BluetoothSocket bluetoothSocket = null;

        setName("AcceptThread" + socketType);

        // Keep listening until exception occurs or a socket is returned
        while (true)
        {
            try
            {
                bluetoothSocket = bluetoothServerSocket.accept();
            }
            catch (IOException e)
            {
                Log.e("FAILED", "Socket Type: " + bluetoothSocket
                        + "accept() failed");
                break;
            }

            // If a connection was accepted
            if (bluetoothSocket != null) 
            {
                // Do work to manage the connection (in a separate thread)
                synchronized (BluetoothChatService.this) 
                {
                    switch (currentState) 
                    {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                        // Situation normal. Start the connected thread.
                            Log.e(TAG, "Calling connected");
                        connected(bluetoothSocket, bluetoothSocket.getRemoteDevice(), socketType);
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                        // Either not ready or already connected. Terminate
                        // new socket.
                            try 
                            {
                                bluetoothSocket.close();
                            } 
                            catch (IOException e) 
                            {
                                Log.e("FAILED", "Could not close unwanted socket");
                            }
                            break;
                    }
                }
            }
        }
    }

    /* Will cancel the listening socket, and cause the thread to finish */
    public void cancel() {
        try {
            bluetoothServerSocket.close();
        } catch (IOException e) { }
    }
}

/*
 * This thread runs while attempting to make an outgoing connection with a
 * device. It runs straight through; the connection either succeeds or
 * fails.
 */

private class ConnectThread extends Thread
{
    private final BluetoothSocket bluetoothSocket;
    private final BluetoothDevice remoteDevice;
    private String socketType;

    public ConnectThread(BluetoothDevice remoteDevice)
    {
        this.remoteDevice = remoteDevice;
        // Use a temporary object that is later assigned to bluetoothSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;

         // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = remoteDevice.createRfcommSocketToServiceRecord(APP_UUID);
            Log.d(TAG, tmp.getRemoteDevice().getAddress());
        } 
        catch (IOException e) 
        { 
            Log.e("FAILED", "RFC Comm");
        }
        bluetoothSocket = tmp;
    }

    @Override
    public void run() {
        // Cancel discovery because it will slow down the connection
        bluetoothAdapter.cancelDiscovery();

        try 
        {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            bluetoothSocket.connect();
        } 
        catch (IOException connectException) 
        {
            // Unable to connect; close the socket and get out
            try 
            {
                bluetoothSocket.close();
            } 
            catch (IOException closeException) 
            { 
                Log.e("FAILED", "Connect");
            }
            connectionFailed();
            return;
        }

        // Reset the ConnectThread because we're done
        synchronized (BluetoothChatService.this) 
        {
            connectThread = null;
        }
        // Start the connected thread
        Log.e(TAG, "Calling connected");
        connected(bluetoothSocket, remoteDevice, socketType);
    }

    public void cancel() {
        try {
            bluetoothSocket.close();
        } catch (IOException e) { }
    }
}

private class ConnectedThread extends Thread
{
    private final BluetoothSocket bluetoothSocket;
    private final InputStream chatInputStream;
    private final OutputStream chatOutputStream;

    public ConnectedThread(BluetoothSocket connectedSocket, String socketType)
    {
        this.bluetoothSocket = connectedSocket;
        InputStream tmpInputStream = null;
        OutputStream tmpOutputStream = null;

        // Get the bluetooth socket input and output streams
        try
        {
            tmpInputStream = bluetoothSocket.getInputStream();
            tmpOutputStream = bluetoothSocket.getOutputStream();
        }
        catch (IOException e)
        {
            Log.e(TAG, "Connected Thread Constructor");
        }
        chatInputStream = tmpInputStream;
        chatOutputStream = tmpOutputStream;

    }

    @Override
    public void run() {
        byte[] buffer = new byte[1024];
        int bytes;

        // Keep listening to the InputStream while connected
        while (true)
        {
            try
            {
                // Read from input stream
                bytes = chatInputStream.read(buffer);
                Log.d(TAG, "MESSAGE_READ");
                // Send the obtained bytes to the UI Activity
                messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
                Log.d(TAG, "Message read has sent by handler");
            }
            catch (IOException e)
            {
                Log.e(TAG, "disconnected", e);
                BluetoothChatService.this.stop();
                connectionLost();
                // Start the service over to restart listening mode
                break;
            }
        }
    }

    public void write(byte[] buffer) {
        try {
            chatOutputStream.write(buffer);

            // Share the sent message back to the UI Activity
            messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
        } catch (IOException e) {
            Log.e("Failed", "Exception during write");
        }
    }

    public void cancel() {
        try {
            bluetoothSocket.close();
        } catch (IOException e) {
            Log.e("Failed", "close() of connect socket failed", e);
        }
    }
}

}

1 个答案:

答案 0 :(得分:0)

好吧,经过多次尝试后我意识到了问题 - 聊天服务实例化了两次,所以我把它变成了单身。