I'm developing an app that should track user location from time to time (e.g. every 20 seconds) and at each minute send those locations to a web services. To achieve that, I've created a producer consumer structure where each task is an android service started by AlarmManagers/BroadcastReceivers. The application also has a UI developed with Phonegap and JQuery Mobile to do secondary tasks and send some data to the services(e.g. username). The problem occurs when the service responsible by the network communication get stuck into getInputStream() method and throws an Android Not Responding dialog. I understand that lengthy operations should not be performed into the UI Thread, but I'm not sure how to solve this problem given my approach, any clue?
Below is my app manifest and some code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.fcl"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:configChanges="orientation|keyboardHidden"
android:allowBackup="false">
<activity
android:name="com.android.fcl.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.android.fcl.GPSLoggerService"
android:label="@string/gps_logger_service">
</service>
<service
android:name="com.android.fcl.GPSHttpService"
android:label="@string/gps_http_service">
</service>
<receiver android:name="com.android.fcl.GPSLoggerReceiver" />
<receiver android:name="com.android.fcl.GPSHttpReceiver" />
</application>
</manifest>
Javascript:
function goToPage() {
window.ServiceControl.startService();
$.mobile.changePage('address.html', {transition: 'flip'});
$.mobile.hidePageLoadingMsg();
}
Activity:
public class MainActivity extends DroidGap {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.init();
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
ServiceControl service = ServiceControl.getInstance();
service.setActivity(this);
service.setView(appView);
appView.addJavascriptInterface(service, "ServiceControl");
ServiceStorage serviceStorage = ServiceStorage.getInstance();
serviceStorage.createDatabase(this);
super.loadUrl("file:///android_asset/www/login.html");
}
}
ServiceControl.java:
public class ServiceControl {
private AlarmManager gpsAlarmManager;
private PendingIntent gpsPendingIntent;
private AlarmManager httpAlarmManager;
private PendingIntent httpPendingIntent;
public void startService() {
if (this.activity != null) {
Intent gpsIntent = new Intent(this.activity, GPSLoggerReceiver.class);
Intent httpIntent = new Intent(this.activity, GPSHttpReceiver.class);
if (this.gpsAlarmManager == null) {
gpsAlarmManager = (AlarmManager) activity.getSystemService(Service.ALARM_SERVICE);
gpsPendingIntent = PendingIntent.getBroadcast(activity, 0, gpsIntent, 0);
gpsAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), GPS_MIN_TIME, gpsPendingIntent);
}
if (this.httpAlarmManager == null) {
httpAlarmManager = (AlarmManager) activity.getSystemService(Service.ALARM_SERVICE);
httpPendingIntent = PendingIntent.getBroadcast(activity, 0, httpIntent, 0);
httpAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), HTTP_MIN_TIME, httpPendingIntent);
}
}
}
BroadcastReceiver:
public class GPSHttpReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, GPSHttpService.class);
context.startService(service);
}
}
Service:
public class GPSHttpService extends Service {
private PowerManager pm = null;
private PowerManager.WakeLock wl = null;
private HttpURLConnection httpConnection = null;
private synchronized void setWakeLock() {
ServiceControl serviceControl = ServiceControl.getInstance();
DroidGap activity = serviceControl.getActivity();
pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wl.acquire();
}
private String sendPostMessage(String message) {
byte [] buffer = null;
String response = null;
URL url = null;
DataOutputStream outputStream = null;
InputStreamReader inputStream = null;
buffer = message.getBytes();
try {
url = new URL(URI);
httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setDoOutput(true);
httpConnection.setDoInput(true);
httpConnection.setInstanceFollowRedirects(false);
httpConnection.setRequestMethod(FCLString.POST_REQUEST_METHOD);
httpConnection.setRequestProperty(FCLString.CONTENT_TYPE_PROPERTY, FCLString.CONTENT_TYPE_APP_JSON);
httpConnection.setRequestProperty(FCLString.CHARSET_PROPERTY, FCLString.UTF8_FORMAT);
httpConnection.setConnectTimeout(CONN_TIMEOUT);
httpConnection.setRequestProperty(FCLString.CONTENT_LENGTH_PROPERTY, String.valueOf(buffer.length));
httpConnection.setUseCaches(false);
outputStream = new DataOutputStream(httpConnection.getOutputStream());
outputStream.write(buffer);
outputStream.flush();
outputStream.close();
inputStream = new InputStreamReader(httpConnection.getInputStream());
response = GPSMessage.readResponseStream(inputStream);
} catch (MalformedURLException e) {
Log.d(TAG, FCLString.MALFORMED_URL);
} catch (IOException e) {
Log.d(TAG, FCLString.IO_EXCEPTION);
Log.d(TAG, e.getMessage());
} finally {
if (httpConnection != null) {
httpConnection.disconnect();
}
}
return response;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ServiceStorage serviceStorage = ServiceStorage.getInstance();
JSONObject json= null;
String message = null;
String response = null;
boolean status = false;
super.onStartCommand(intent, flags, startId);
setWakeLock();
json = serviceStorage.getRecords();
if ((json != null) && (json.length() > 0)) {
message = GPSMessage.prepareMessage(json);
if (message != null) {
if (hasInternetConnection()) {
response = sendPostMessage(message);
status = GPSMessage.evaluateResponse(response);
if (status) {
serviceStorage.removeRecords(json);
}
}
}
}
this.stopSelf();
return Service.START_NOT_STICKY;
}
}