0

I keep getting the NetworkOnMainThreadException at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303) on a ObjectOutputStream.writeObject() call even though it is done on a separate thread from the main. I create the ObjectOutputStream from a socket and have tried creating the socket and ObjectOutputStream in several different places that I thought were not on the main thread but that didn't seem to help.

I saw a suggestion to change the Thread Policy here, but I would rather not, especially if it turns out to be something small that I am just missing. Am I not creating the threads correctly?

public class WifiService extends Service {
    private ArrayList<ClientThread> clientThreads=new ArrayList<>();
    private ServerThread serverThread;
    private int localPort;

    public WifiService() {
    }

    public void send(String info){
        for (ClientThread c: clientThreads) {
            c.send(info);
        }
    }

    public void startServer(String hostName){
        if(serverThread==null) {
            serverThread = new ServerThread(hostName);
            new Thread(serverThread).start();
        }
    }

    public void connectToServer(InetAddress address, int port){
        ClientThread clientThread=new ClientThread(address,port);
        clientThread.start();
        clientThreads.add(clientThread);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mybinder;
    }

    public final IBinder mybinder = new LocalBinder();
    public class LocalBinder extends Binder {
        public WifiService getService(){
            return WifiService.this;
        }
    }

    @Override
    public int onStartCommand(Intent intent,int flags, int startId){ //called when service first started. Starts service in background forever unless stopself() called.
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    private class ServerThread extends Thread{
        private ServerSocket serverSocket;
        private String hostName;

        public ServerThread(String hostName){
            this.hostName=hostName;
        }

        public void tearDown(){
            stopSelf();
        }

        @Override
        public void run(){
            try {
                serverSocket=new ServerSocket(0);
                localPort=serverSocket.getLocalPort();
                startBroadcasting(hostName);
            } catch (IOException e) {
                e.printStackTrace();
            }

            while (!Thread.currentThread().isInterrupted()){
                Socket clientSocket = null;
                try {
                    clientSocket = serverSocket.accept();
                    ClientThread clientThread=new ClientThread(clientSocket);
                    clientThread.start();
                    clientThreads.add(clientThread);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class ClientThread extends Thread{
        protected Socket socket=null;
        private ObjectInputStream input;
        private ObjectOutputStream output;
        private InetAddress address;
        private int port;

        public ClientThread(Socket socket){
            this.socket=socket;
            this.address=socket.getInetAddress();
            this.port=socket.getLocalPort();
        }

        public ClientThread(InetAddress address, int port){
            this.address=address;
            this.port=port;
        }

        public void tearDown(){
            stopSelf();
        }

        @Override
        public void run(){
            Log.d("ClientThread", "port: "+port);
            if(socket==null){
                try {
                    socket=new Socket(address, port);
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }

            try {
                output=new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
                input=new ObjectInputStream(socket.getInputStream());
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        CommunicationTemplate received_CT=(CommunicationTemplate)input.readObject();
                        MessageMainActivity(received_CT);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                input.close();
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void send(String info){
            CommunicationTemplate ct=new CommunicationTemplate(1,"fromPlayer","toPlayer", 100L, info);
            try {
                output.writeObject(ct);
                output.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void MessageMainActivity(CommunicationTemplate communicationTemplate){ //sends bundle to MainActivity to interact with UI
        Bundle messageBundle = new Bundle();
        messageBundle.putSerializable("msg",communicationTemplate);
        Intent intent=new Intent();
        intent.setAction("message");
        intent.putExtra("message",messageBundle);
        sendBroadcast(intent);
    }

    private void commMainActivity(HostInfo hostInfo){ //sends bundle to MainActivity to interact with UI
        Bundle messageBundle = new Bundle();
        messageBundle.putParcelable("host",hostInfo);
        Intent intent=new Intent();
        intent.setAction("host");
        intent.putExtra("host",messageBundle);
        Log.d("commMainActivity", "sending hostInfo to MainActivity...");
        sendBroadcast(intent);
    }
}

And the error, at ClientThread > send() > output.writeObject() :

06-25 01:22:29.864 15175-15175/com.example.admin.bluetoothcomms E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.admin.bluetoothcomms, PID: 15175
android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303)
    at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
    at java.net.SocketOutputStream.write(SocketOutputStream.java:157)
    at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
    at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
    at java.io.ObjectOutputStream$BlockDataOutputStream.flush(ObjectOutputStream.java:1889)
    at java.io.ObjectOutputStream.flush(ObjectOutputStream.java:731)
    at com.example.admin.bluetoothcomms.WifiService$ClientThread.send(WifiService.java:151)
    at com.example.admin.bluetoothcomms.WifiService.send(WifiService.java:33)
    at com.example.admin.bluetoothcomms.MainActivity.send(MainActivity.java:178)
    at com.example.admin.bluetoothcomms.MainActivity.onClick(MainActivity.java:126)
    at android.view.View.performClick(View.java:6207)
    at android.widget.TextView.performClick(TextView.java:11094)
    at android.view.View$PerformClick.run(View.java:23639)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6688)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)   

EDIT: I forgot to mention, this class is used for both Client and Server. The server is started by calling startServer() and client by calling connectToServer(). The Socket Communication works when sent from a client, but crashes with the aforementioned error when attempting to send from the server.

Ethan
  • 97
  • 1
  • 6
  • @ReazMurshed That is a most improper request. Questions asked here just be complete in themselves, and the OP may not be able to, probably isn't able to, provide his entire code for cooyright reasons, – user207421 Jun 25 '17 at 10:03
  • I understand and removed the comment. – Reaz Murshed Jun 25 '17 at 11:53

2 Answers2

0

The problem is, that Service run on Main thread by default. So, when you use Binder of that service to call send(), itis still called from main thread. You should call this method from within ClientThread. In order to achieve that try using ExecutorService framework - it has concurrent queue for pending tasks (because wifi may work slower than you want while sending). You can create threads in ExecutorService for every connection and accept task if host names of thread and job are the same.

Alex Shutov
  • 3,217
  • 2
  • 13
  • 11
  • I am checking out ExecutorService, but if it is an issue with send() being called from the main thread, why would the same code work when a client sends to server? – Ethan Jun 25 '17 at 08:15
  • Because ServerThread calls it from loop of its .run() method - i.e. from background (server) thread – Alex Shutov Jun 25 '17 at 08:43
  • You can try to use IntentService - all messages will be saved in Intents and queued by Android. Unfortunately, there is only one thread running, so for multiple client thread ExecutorService is a better option. – Alex Shutov Jun 25 '17 at 08:44
0

Caution: A service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise. If your service is going to perform any CPU-intensive work or blocking operations, such as MP3 playback or networking, you should create a new thread within the service to complete that work. By using a separate thread, you can reduce the risk of Application Not Responding (ANR) errors, and the application's main thread can remain dedicated to user interaction with your activities.

You can read full info about service from google android developers page https://developer.android.com/guide/components/services.html