Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NetworkOnMainThreadException in IntentService

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).

like image 714
click_whir Avatar asked Apr 01 '13 22:04

click_whir


3 Answers

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!

like image 196
click_whir Avatar answered Oct 11 '22 23:10

click_whir


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.

like image 24
Bill Mote Avatar answered Oct 12 '22 00:10

Bill Mote


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.

like image 39
Joe Malin Avatar answered Oct 11 '22 23:10

Joe Malin