Saturday, April 28, 2012

android.os.NetworkOnMainThreadException

Refer to my old exercise "Read Text file from internet, using Java code": it a simple exercise to read something from internet. It can be download here in project form.

It work as expected, to display the text file from internet, for android:minSdkVersion="9" or older. But fail with android:minSdkVersion="10" or higher. It's a strange and interesting issue for me.

OK for android:minSdkVersion='9' or older



Fail for android:minSdkVersion='10' or higher


After investigated into the logcat, I found that it's Caused by: android.os.NetworkOnMainThreadException!

android.os.NetworkOnMainThreadException is a exception that is thrown when an application attempts to perform a networking operation on its main thread.

This is only thrown for applications targeting the Honeycomb SDK or higher (actually it fail in my exercise with API level 10). Applications targeting earlier SDK versions are allowed to do networking on their main event loop threads, but it's heavily discouraged.

The solution is to move the internet accessing code to a background thread, AsyncTask in my exercise.

package com.exercise.AndroidInternetTxt;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.TextView;

public class AndroidInternetTxt extends Activity {
 
 TextView textMsg, textPrompt;
 final String textSource = "http://sites.google.com/site/androidersite/text.txt";


   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       textPrompt = (TextView)findViewById(R.id.textprompt);
       textMsg = (TextView)findViewById(R.id.textmsg);
      
       textPrompt.setText("Wait...");
      
       new MyTask().execute();
       
       /*
       URL textUrl;

       try {
        textUrl = new URL(textSource);

        BufferedReader bufferReader 
         = new BufferedReader(new InputStreamReader(textUrl.openStream()));
        
        String StringBuffer;
        String stringText = "";
        while ((StringBuffer = bufferReader.readLine()) != null) {
         stringText += StringBuffer;   
        }
        bufferReader.close();

        textMsg.setText(stringText);
       } catch (MalformedURLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        textMsg.setText(e.toString());   
       } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        textMsg.setText(e.toString());   
       }
  
       textPrompt.setText("Finished!");    
       */
   }
   
   private class MyTask extends AsyncTask<Void, Void, Void>{
    
    String textResult;
    
    @Override
    protected Void doInBackground(Void... params) {
     
        URL textUrl;

        try {
         textUrl = new URL(textSource);

         BufferedReader bufferReader 
          = new BufferedReader(new InputStreamReader(textUrl.openStream()));
         
         String StringBuffer;
         String stringText = "";
         while ((StringBuffer = bufferReader.readLine()) != null) {
          stringText += StringBuffer;   
         }
         bufferReader.close();

         textResult = stringText;
        } catch (MalformedURLException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         textResult = e.toString();   
        } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         textResult = e.toString();   
        }

     return null;
     
    }
    
    @Override
    protected void onPostExecute(Void result) {
     
     textMsg.setText(textResult);
     textPrompt.setText("Finished!");  
     
     super.onPostExecute(result);   
    }

   }
}


Download the files.

Another un-recommended approach: StrictMode.setThreadPolicy and StrictMode.ThreadPolicy.Builder

More example:
- Load Bitmap from internet in background thread using AsyncTask


17 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. You can fix this error by
    add two line
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);

    setContentView(R.layout.main);

    ReplyDelete
  3. Thank you very much manh_hoang.
    It was very helpfull for me.

    ReplyDelete
  4. Thank you very much manh_hoang.
    It was helpful for me too.

    ReplyDelete
  5. FYI, this workaround using permitAll() on StrictMode is heavily discouraged. It is OK for small apps, I suppose, but it may cause your app to be unresponsive (or even crash) because the UI thread will be blocked by heavy operations which should instead be performed in a AsyncTask.

    Additionally, you can visit the following Android Developers blog entry on threading issues, very clear on how to circumvent the issue properly :
    http://android-developers.blogspot.be/2009/05/painless-threading.html

    ReplyDelete
  6. @Android Er...
    I have tried to use this code as inspiration on my project to debug this very problem. I have come across a problem on my end though. I used the "new MyTask().execute();" and the MyTask extends AsyncTask lines, reorganized original code and have this one pesky error. On the new MyTask it says it cannot be resolved to a type and wants me to create a .java file. I downloaded your zip file to see if you had one , but it was not there. how do I fix this? I am very new to Java and understand it wants a new file, but the code I need to execute is below in my setOnClickListener. I am a dude in small town Nebraska with nobody to mentor me, so I have to ask for the help of people like you. Thanks...

    JJ

    ReplyDelete
  7. hello JJ,

    Better you can post your code.

    ReplyDelete
  8. I was unable to fit the code in this comment. It exceeded the limit. So I posted to your google+ account. If you have a way of posting it on this blog so others might benefit, feel free. Thanks.

    JJ

    ReplyDelete
  9. My Result:
    Hello Android!
    Finished!

    ReplyDelete
  10. Thank you very much. It was very helpful for me.

    ReplyDelete
  11. A very helpful link to understand the NetworkOnMainThread Exception.
    1. Make Manifest file changes
    2. Inner class to extend AsyncTask and override the methods to code the functionality

    ReplyDelete
  12. Hello,
    The link to download the files is not available anymore. Please help. :/

    ReplyDelete
  13. package com.example.cctv_app;

    import java.io.BufferedReader;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.MalformedURLException;
    import java.net.Socket;
    import java.net.URL;

    import com.example.cctv_app.*;

    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.view.Menu;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;

    public class MainActivity extends Activity {
    EditText textOut;
    TextView textIn;
    Socket socket = null;
    DataOutputStream dataOutputStream = null;
    DataInputStream dataInputStream = null;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textOut = (EditText) findViewById(R.id.textout);
    Button buttonSend = (Button) findViewById(R.id.send);
    textIn = (TextView) findViewById(R.id.textin);
    buttonSend.setOnClickListener(buttonSendOnClickListener);

    }

    @SuppressLint("ShowToast")
    Button.OnClickListener buttonSendOnClickListener = new Button.OnClickListener() {

    @Override
    public void onClick(View arg0) {

    new MyTask().execute();

    /* try{
    socket = new Socket("10.0.2.2",9000);
    dataOutputStream = new DataOutputStream(socket.getOutputStream());
    String s=textOut.getText().toString();
    dataOutputStream.writeUTF(s);

    dataInputStream = new DataInputStream(socket.getInputStream());

    //data=dataInputStream.readUTF();
    textIn.setText(dataInputStream.readUTF());
    //Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show();


    }catch(Exception e)

    {
    e.printStackTrace();
    }

    */

    }
    };

    private class MyTask extends AsyncTask{

    String textResult="";

    @Override
    protected Void doInBackground(Void... params) {

    // URL textUrl;

    try {
    socket = new Socket("10.80.248.127",8888);
    dataOutputStream = new DataOutputStream(socket.getOutputStream());
    String s=textOut.getText().toString();
    dataOutputStream.writeUTF(s);

    dataInputStream = new DataInputStream(socket.getInputStream());

    String data=dataInputStream.readUTF();
    textIn.setText(data);
    //Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show();


    }catch (MalformedURLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    textResult = e.toString();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    textResult = e.toString();
    }


    return null;

    }

    @Override
    protected void onPostExecute(Void result) {

    //textMsg.setText(textResult);
    //textPrompt.setText("Finished!");

    super.onPostExecute(result);
    }
    }
    }

    ReplyDelete
  14. Hi,
    why this code is actually twice implemented (in onClick and in doInBackground)?
    The right way is ONLY in doInBackground?
    Thank you

    ReplyDelete
  15. hello Aurelian,

    Yes, the onClick one is used to show the error.

    ReplyDelete