0

I have inherited an Android application and am updating it for the latest API. I am getting the NetworkOnMainThreadException when I create the socket. I have read a number of solutions that say to use AsyncTask. However AsyncTask should only be used for short running Tasks. This is a standard TCP connection that will last for the life of the running application. It is bi-directional.

How dod I update the class using Threads so that I don't get the exception and am able to send data over the socket?

public class TcpConnection
{
    enum State
{
    Closed,
    Closing,
    Open
};

State _state;    
Socket _socket;
//data stream from the head-unit
DataInputStream _inputStream;
//data stream to the head-unit
DataOutputStream _outputStream;

Receiver _receiver;
Thread _thread;

OnTcpDataReceivedListener _onTcpDataReceived;

public TcpConnection()
{                 
     _state = State.Closed;
}

// Listen to this to be informed of what data has been received from a TCP / IP socket.
public void setOnTcpDataReceivedListener(OnTcpDataReceivedListener listener)   
{
    _onTcpDataReceived = listener;
}

// Used to inform the listener that data has been received from a TCP / IP socket.
public interface OnTcpDataReceivedListener
{
    public void onTcpDataReceived(byte[] buffer, int length);
}

// Try connecting to a given TCP / IP connection.
// Notes:
// Blocks until a connection is created.
// if connected
public synchronized void connect(String ipAddress, int port) 
throws IOException
{
    if (_state != State.Closed)
        return;

    try
    {
        _state = State.Open;

        _socket = new Socket(ipAddress, port);
        _inputStream = new DataInputStream(_socket.getInputStream());
        _outputStream = new DataOutputStream(_socket.getOutputStream());            
    }
    catch(IOException ex)
    {            
        //TODO: do better error handling
        ex.printStackTrace();

        if (_socket != null) {
            _socket.close();
            _socket = null;             
        }

        if (_inputStream != null) {
            _inputStream.close();
            _inputStream = null;
        }

        if (_outputStream != null) {
            _outputStream.close();
            _outputStream = null;
        }

        throw ex;
    }

    _receiver = new Receiver();

    _thread = new Thread(_receiver);
    _thread.setName("TcpConnection.Receiver");
    _thread.start();
}

public void write(byte[] buffer)
{
    if (_state != State.Open)
        return;

    try
    {
        _outputStream.write(buffer);
    }
    catch(IOException ex)
    {            
        //TODO: do better error handling
        ex.printStackTrace();            
    } 
}

public synchronized void close()
{
    _state = State.Closing;

    if (_socket != null && !_socket.isClosed())
    {
        try
        {
            _socket.close();
        }
        catch(IOException ex)
        {
            //TODO: do better error handling
            ex.printStackTrace();
        }
    }         
}

private class Receiver implements Runnable
{                                  
    @Override
    public void run() 
    {                        
        try 
        {
            byte[] buffer = new byte[512];
            int read = 0;

            while(_state == State.Open)
            {                    
                read = _inputStream.read(buffer);

                if (read > 0)
                {
                    if (_onTcpDataReceived != null)
                    {
                        _onTcpDataReceived.onTcpDataReceived(buffer, read);
                    }
                }
            }
        }
        catch (SocketException ex)
        {
            if (    !(_state != State.Open 
                    && ex.getMessage() != null 
                    && ex.getMessage().equalsIgnoreCase("socket closed")))
            {
                ex.printStackTrace();
            }
        }
        catch (IOException ex) 
        {
            //TODO: need error handling
            ex.printStackTrace();
        }            
        finally 
        {
            if (_socket != null && !_socket.isClosed())
            {
                try
                {
                    _socket.close();
                }
                catch (IOException ex) 
                {
                    //TODO: need error handling
                    ex.printStackTrace();
                }                    
            }
        }

        _state = State.Closed;
     }
}   

}

Ahmad
  • 69,608
  • 17
  • 111
  • 137
John
  • 103
  • 7
  • That is the same symptom. I have not been able to find a current example of creating a socket that is not on the main thread. Even recent examples use older API versions to avoid the exception. It would be nice to have an example that doesn't skirt the issue. – John May 13 '13 at 21:35
  • The top answer to the linked question above includes an example. – Chris Stratton May 13 '13 at 21:36
  • It does have an example of using AsyncTask. The documentation on AsyncTask says it should be used for short tasks, a few seconds in length. So I have been looking for an example that does not use AsyncTask but uses threads. – John May 13 '13 at 21:43

1 Answers1

0

You have a couple of choices:

  1. Update the actual Android class that uses TcpConnection to wrap all calls to TcpConnection in a separate thread: you could create an Executor and use that.
  2. Add an Executor instance to TcpConnection that handles all transmission and reception. I expect that your issue is with the connect function: maybe try wrapping that code in a separate Runnable and launching that on a thread?
Femi
  • 64,273
  • 8
  • 118
  • 148
  • I have made the TcpConnection class implement Runnable and removed the inner Receiver class. Changed the connect function to store the address and port and moved the existing contentes of connect to the start of the run function. I no longer get the exceptions and can send and receive information over the socket. – John May 13 '13 at 21:31
  • That's it: essentially you can't perform any network activity at all, connecting/listening/sending directly in line with any UI activity (so not from any button presses or Handlers or anything else). – Femi May 13 '13 at 21:36