9

I am making network calls from an IntentService but still receiving a NetworkOnMainThreadException. My understanding is that an IntentService always runs on a worker thread, so I'm suprised to see this. The crucial piece may be that my IntentService is calling a static helper class that performs the network calls. The static helper class is instantiated in my main Application class.

I thought this would still execute on the worker thread of the IntentService. What am I missing?

Honestly I prefer heady informative discussions over a quick code fix. But if code is required, code shall be provided:

//MyApplication.java
public class MyApplication extends Application{

private static NetworkUtils utils;

    @Override
    public void onCreate() {
        super.onCreate();
        utils = new NetworkUtils(this);
        ...
    }
    ...
}

//NetworkUtils.java
public class NetworkUtils {

    private static Context context;
    private static final Gson gson = new Gson();

    public NetworkUtils(Context context) {
        this.context = context;
    }

    public static final DataResponse login(String email, String password) {
        //*** NetworkOnMainThreadException OCCURS HERE ***
        DataResponse response = HttpConnection.put(url, json);
        ...
        return response;
    }
    ...
}

//LoginService.java
public class LoginService extends IntentService {

    public LoginService() {
        super("LoginService");
    }

    @Override
    public void onStart(Intent intent, int startId) {
        onHandleIntent(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle bundle = new Bundle();
        DataResponse response = NetworkUtils.login(email, password);
        ...
        bundle.putBoolean(MyConstants.ExtraKeys.LOGGED, response.success);
        MainApplication.getApplicationInstance().sendBroadCast(MyConstants.Actions.LOGIN, bundle);
    }
}

//LoginActivity.java
public class LoginActivity extends ActionBarActivity implements IDialogClickListener {
    ...
    public void onLoginButtonPressed() {
        Intent intent = new Intent(MainApplication.getApplicationInstance(), LoginService.class);
        this.startService(intent);
    }
}

Also, Logcat:

> 04-01 18:20:41.048: VERBOSE/com.foo.foo(28942):
>     com.foo.foo.network.HttpConnection.execute - METHOD: PUT   
> 04-01 18:20:41.068: ERROR/com.foo.foo(28942):
>     com.foo.foo.social.NetworkUtils.login - class
>     android.os.NetworkOnMainThreadException:  null   
> 04-01 18:20:41.169: DEBUG/com.foo.foo(28942):
>     com.foo.foo.MainActivity$MyReceiver.onReceive - BROADCAST RECEIVED:
>     com.foo.foo.MainApplication@422d81d8 - Intent { act=com.foo.foo.login
>     dat=com.foo.foo.scheme://data/1364854841079 (has extras) }   
> 04-01 18:20:41.169: INFO/com.foo.foo(28942):
>     com.foo.foo.activity.LoginActivity.setData - ACTION: com.foo.foo.login
>     - ISERROR: true

SOLUTION

The underlying issue was a bit of legacy code that was calling onHandleIntent explicitly. In LoginService.java above:

@Override 
public void onStart(Intent intent, int startId) { 
    onHandleIntent(intent); 
} 

This is causing the onHandleIntent code to run on the main thread, as it is being called from the onStart event (which apparently runs on main thread).

click_whir
  • 427
  • 5
  • 19
  • 1
    You are missing some of your code so we can see what is going on. Please post some code, the logcat output, and point to the line the error is coming from. – Michael Celey Apr 01 '13 at 22:55

3 Answers3

5

I have spotted the issue. Check out this baffling override in LoginService.java above:

@Override 
public void onStart(Intent intent, int startId) { 
    onHandleIntent(intent); 
} 

This is causing the onHandleIntent code to run on the main thread, as it is being called from the onStart event (which apparently runs on main thread). I would love to read the mind of the developer who put that in!

click_whir
  • 427
  • 5
  • 19
2

Your suspicions are correct. You're instantiating the NetworkUtils class from your application class. That's not going to run in a background thread no matter how you call it.

Bill Mote
  • 12,644
  • 7
  • 58
  • 82
  • Does this imply I can instantiate NetworkUtils elsewhere from the application class and thus get it to run on a background thread _and_ it can remain a static class? (goes off to experiment with lazy constructor) – click_whir Apr 02 '13 at 00:16
  • 1
    hrm, even removing the instance of NetworkUtils from the Application class and allowing it to construct on the fly still fails. I guess a static helper class will not work at all and I must instantiate it locally wherever NetworkUtils are needed. – click_whir Apr 02 '13 at 01:56
  • Still not working. I have spotted the issue. Check out this baffling override in LoginService.java above: @Override public void onStart(Intent intent, int startId) { onHandleIntent(intent); } This is causing the onHandleIntent code to run on the main thread, as it is being called from the onStart event (which apparently runs on main thread). I would love to read the mind of the developer who put _that_ in.... – click_whir Apr 02 '13 at 21:18
2

Strictly speaking, it's a class that contains static variables. I strongly suggest you avoid static variables. Instead, use the Android API to persist state in objects such as SharedPreferences or Bundles.

The Android object environment is transitory by design. Instead of persisting state in memory, persist it in objects and constructs that are specifically designed for it, such as Bundles and SharedPreferences. You'll be much happier. Try anything else, and you'll end up trying to squeeze a large mass of worms back into a very small can.

Joe Malin
  • 8,621
  • 1
  • 23
  • 18
  • hm... good advice, I have indeed encountered situations already where just going with the android model makes for happier development. This is a bit of code I inherited, and I am (apparently wrong-headedly) applying the Java notion that helper classes that have no state should be static classes. – click_whir Apr 02 '13 at 01:15
  • yes, there are some custom constructs here I am doing away with. The app-wide BroadcastReceiver is turning into a ResultReceiver for this service only. Also Context in the static class is only there to get strings (R class constants) for user feedback so I'm going to send a purer result via ResultReceiver, _then_ pair that with feedback strings in the calling Activity/Fragment. This will allow me to make static calls on the members of NetworkUtils and not have to store the instance of said class in the Application class. Forgive my lacking the reputation to vote up your helpful feedback. – click_whir Apr 02 '13 at 01:28