Reputation: 1
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;
}
}
Upvotes: 0
Views: 947
Reputation: 11545
From the reference doc on Android services:
Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Processes and Threads. The IntentService class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.
Upvotes: 3
Reputation: 36302
You could consider using IntentService
instead of just a regular Service
as your work seems relatively self-contained. That will take care of doing your work on a separate thread.
Upvotes: 0