IntentService : How to enqueue correctly? - android

In my code i'm using an IntentService to listen to location updates (either GPS or network updates) and this IntentService is triggered when an event is received, so it is started with startService() from any activity.
public class AddLocationService extends IntentService implements LocationListener {
/*My code here*/
}
#Override
protected void onHandleIntent(Intent intent) {
if(getOldLoc() == null)
{
//Get a new location
this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, TIME_INTERVAL_GPS, 0, this);
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, TIME_INTERVAL_GPS, 0, this);
Log.d(AddLocationService.TAG, "Network listener started");
this.time_start_listening = System.currentTimeMillis();
mTimerThread mTimerRunnable = new mTimerThread();
this.timerThread = new Thread(mTimerRunnable);
this.timerThread.start();
}
else
/*REUSE OLD LOCATION*/
}
Now my problem is : When two events start this IntentService and the second starts it while the first one is still requesting for updates, I will like the second one to wait until first one is fully finished (location found OR timer thread finishes).
However whenever the IntentService is executed a second time (first instance still running), it prints me the log and does as it was executing in parallel.
However I thought that the main goal of IntentService was that it is something sequential so a second intent would have to wait until first one is done...
Did I missunderstood something ?

It appears that your onHandleIntent method is not blocking the thread it is executing on, so it will return quickly and allow the second intent to be processed. Not only that, but any callbacks from the LocationManager to that thread are unlikely to be processed as the background thread is likely to be killed when onHandleIntent is finished.
If you really want to use IntentService to manage your intent queue then you will need to do your location handling on its own thread, and join the IntentService thread to the location thread whilst it is waiting for the location callback.
Heres a bit of code that demonstrates the idea:
public class TestService extends IntentService {
private static final String TAG = "TestService";
private Location mLocation = null;
public TestService() {
super(TAG);
}
#Override
public void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
if (mLocation == null) {
Log.d(TAG, "launching location thread");
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
LocationThread thread = new LocationThread(locationManager);
thread.start();
try {
thread.join(10000);
} catch (InterruptedException e) {
Log.d(TAG, "timeout");
return;
}
Log.d(TAG, "join finished, loc="+mLocation.toString());
} else {
Log.d(TAG, "using existing loc="+mLocation.toString());
}
}
private class LocationThread extends Thread implements LocationListener {
private LocationManager locationManager = null;
public LocationThread(LocationManager locationManager) {
super("UploaderService-Uploader");
this.locationManager = locationManager;
}
#Override
public void run() {
Log.d(TAG, "Thread.run");
Looper.prepare();
this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
Looper.loop();
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
Log.d(TAG, "onLocationChanged("+location.toString()+")");
mLocation = location;
Looper.myLooper().quit();
}
#Override
public void onProviderDisabled(String arg0) {
}
#Override
public void onProviderEnabled(String arg0) {
}
#Override
public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
}
}
}
Of interest in there is the Looper that runs a message loop on the thread (to allow handling of the callbacks).
Given the effort required to do this with IntentService it might be worthwhile investigating deriving from Service instead and managing your own intent queue.

onHandleIntent is in it's own thread already. You don't (shouldn't) create on in there. It's all handled by IntentService for you.

Thanks a million, that is exactly what I needed to handle the location requests.
Thank you for explanations and making it clear for me, I wasn't very familiar with all the looper concept, now I understand it better !
In case someone need the same kind of thing, don't forget to stop the thread looper if your location thread is not stopping naturally (end of time on join(millis)), by adding this in onHandleIntent() :
if(thread.isAlive())
{
thread.onThreadStop();
try{
thread.interrupt();
}catch (Exception e) {
Log.d(TAG, "Exception on interrupt: " + e.getMessage());
}
}
after thread.join(yourTime), so for example if you didn't find any location update you still stop the thread after a certain time. And on method onThreadStop() :
/*We remove location updates here and stop the looper*/
public void onThreadStop()
{
this.locationManager1.removeUpdates(this);
handleLocationChange(AddLocationService.this.currentBestLocation);
Looper.myLooper().quit();
}
However I thought I saw my two intents being treated the first time I ran this code, but now only the first one is treated when I have multiple intents while still requesting location updates.
My method onHandleIntent() seems to execute correctly, stops the thread after the time specified and even displays the very last Log (last statement of the method) but the second intent is not executed...
Would you have any idea why ?

Related

Run my code in background every 1 sec effectively in Android

I have to run a bit of code in the background every one second, the code will call a webservice which searches a database and returns a value to the application. My question is which method would be the most effective to do this? I have read up on Timers, Threads, AsyncTask and Services and each seem to have their pros and cons. Please can someone tell me which would be the best to use considering execution time and battery life.
Thanks
Update:
I decided to use Aysnc task to run my code in the background while using a TimeTask to trigger the AsyncTask at regular intervals. This way the operation is destroyed when I leave that particular activity
You should use the service to do the background operation but in your case you want to run code in 1 sec here is the example of service using handler it call in every 1 sec.
public class YourService extends Service {
private static final String TAG = "Your Service";
private final Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
}
};
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
// Toast.makeText(this, "My Service Created", Toast.LENGTH_LONG).show();
Log.d(TAG, "onCreate");
}
#Override
public void onDestroy() {
super.onDestroy();
// Toast.makeText(this, "My Service Stopped", Toast.LENGTH_LONG).show();
handler.removeCallbacks(sendUpdatesToUI);
}
private Runnable sendUpdatesToUI = new Runnable() {
public void run() {
/// Any thing you want to do put the code here like web service procees it will run in ever 1 second
handler.postDelayed(this, 1000); // 1 seconds
}
};
#Override
public void onStart(Intent intent, int startid) {
handler.removeCallbacks(sendUpdatesToUI);
handler.postDelayed(sendUpdatesToUI, 1000);//1 second
Log.d(TAG, "onStart");
}
}
and service can't run every time android idle the service within 3 or 4 hr i suggested you to use the foreground service to use your process long running.
For operations like this I tend to use a Service component. for the task itself i use an AsyncTask which will wait a set time before it repeats itself (using a while loop).
You will have to create a new Thread so that the call don't lock up the device if the call takes longer than expected. The AsyncTask is an easy way to use multithreading, but it lacks the functionality of repeating tasks. I would say that you are best of either using a Timer or the newer ScheduledExecutorService.
If you chose to use the Timer you create a TimerTask that you can hand it. The ScheduledExecutorService takes a Runnable instead.
You might want to wrap the thread in a Service (The Service does not provide a new Thread), but this is not always necessary depending on your needs.
As suggested in comment, you can also use the Handler.postDelayed(). Although you still need to create a new thread and then call Looper.prepare() on it:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
(Code from Looper docs)
Also; calls to a webservice every second seems way too frequent, especially if the user is on a slow connection or there are data that needs to be transferred, try to reduce the calls as much as possible.
I think it's not only one solution, so it's up to you. You can try start thread with this run method:
private final int spleeptime = 1000;
public boolean running;
#Override
public void run() {
while (running) {
try {
int waited = 0;
while ((waited < spleeptime)) {
sleep(100);
waited += 100;
}
} catch (InterruptedException e) {
} finally {
// your code here
}
}
}

Android GPSListener get notified once GPS updates start

So it's a well known fact that it takes a while ( about 10-12 seconds ) for the GPS receiver to lock on to the satellites. Using Android LocationListener I want to get notified when the location updates start. For instance:
I have an activity with the following code to instantiate and call the LocationListener
public void startLocationUpdates() {
try{
Common.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
listener = new LocListener();
if(Common.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
Common.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
} else if(Common.locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
Common.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener);
}
}catch(Exception e) {
e.printStackTrace();
}
}
Here's the LocationListener onLocationChanged implementation.
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
try{
Common.currentLocation = new String(String.valueOf(location.getLatitude()) + "," + String.valueOf(location.getLongitude()));
Log.i("LocListener",Common.currentLocation);
} catch(Exception e) {
e.printStackTrace();
}
}
The Common.currentLocation string gets updated after the locationUpdates start. However, I want to wait in my main activity until this field is updated.
If I put a while loop in my activity to wait for the Common.currentLocation field to be updated, the thread gets suspended and the onLocationChanged method doesn't get called ( the Log messages are not printed ).
What's the best way of getting an update from the onLocationChanged method to a common interface/activity as soon as onLocationChanged gets called and the latitude and longitude values are available ? Any help will be greatly appreciated.
If you need to put the listener in a class for re usability purposes then there a number of methods to provide the information
The important thing to remember is that Android is an event driven system, so instead of "Waiting" for the results by polling you need to create a way that lets the class create an even and then respond to any interested parties of that event.
The simplest thing to do if you don't require re usability is to simply implement the listener in the same activity and do your processing there so for instance
public class yourclass extends Activity implements LocationListener {
...
public void onLocationChanged(Location location) {
...
}
}
The next simplest method is to create an interface in your class with the listener so, this is not error free and from memory but you will get the general idea, in reality this just puts one layer between your class and the event listener but it does isolate you listener into a class that can then easily be used by other activitys, also not I am not doing anything to take into account multiple activity registration de-registration etc.
public mylistenerclass implements LocationListener {
...
public interface LocationEvents {
public abstract void onLocationEvent(Location location);
}
LocationEvents mListener = null;
public void registerForEvents(LocationEvents eventListener, ... any-params) {
... start listening for location stuff
mListener = eventListener;
}
public void unregisterForEvents(LocationEvents eventListener) {
... end listening for location stuff
mListener = null;
}
public void onLocationChanged(Location location) {
...
if (null != mListener)
mListener.onLocationEvent(location);
}
}
public class yourclass extends Activity implements LocationEvents {
...
#Override
public void onResume() {
super.onResume();
registerForEvents(locationEvent, anyparams);
}
#Override
public void onPause() {
super.onPause();
unregisterForEvents(locationEvent);
}
}
You could also use a service, or broadcast intents from your class. I suggest though if your other activities don't need the functionality to just start by implementing the listener in your existing activity and go for there
You should not busy-wait (e.g. in a loop) for the location update. LocationManager implements a requestSingleUpdate(String provider, PendingIntent intent) method. Consider using the PendingIntent to inform your activity that the update is available. There are various other forms of requestSingleUpdate, see here for more information:
http://developer.android.com/reference/android/location/LocationManager.html

Android: How to properly manage sequential threads within infinite loop

I have created IntentService with infinite loop inside the onHandleIntent then add static methods start,resume,pause,stop to directly call it within my Activities.
The scenario is, inside the infinite loop, I am calling callback methods which is creating a new thread to execute long process.
The problem is, I am worrying about continuously creating Threads due to infinite loop. I am pretty sure that there is better way to manage it. I am thinking of ThreadPool or something enable to use only one thread in a sequential manner. So that, I am saving time,memory,overheads etc..
OTHER APPROACH ARE VERY WELCOME. Ask me other information as needed. Then, I will update here.
Here are my codes(take a look at SampleCallback):
IntentService
import android.app.IntentService;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class SampleCallbackIntentService extends IntentService {
private final String LOG_LOGCAT_TAG = "SampleCallbackIntentService";
private Handler _handler;
public SampleCallbackIntentService(String name) {
super(name);
}
#Override
public void onCreate() {
super.onCreate();
// initialize variables for pause & resume thread
_mPauseLock = new Object();
_mPaused = false;
_mFinished = false;
// initialize handler to switch to UI/Main thread
_handler = new Handler()
{
#Override
public void handleMessage(final Message msg)
{
_callback.doSomethingFromUIThread(msg);
}
};
}
private final SampleCallback _callback = new SampleCallback() {
#Override
public void doSomethingFromCurrentThread(final Object object) {
new Thread(new Runnable() {
#Override
public void run() {
//do long running process.
// I will access object here.
}
}).start();
}
#Override
public void doSomethingFromUIThread(final Message msg) {
//may update UI here.
}
};
private final int CALLBACK_MESSAGE = 1;
#Override
protected void onHandleIntent(Intent arg0) {
Log.i(LOG_LOGCAT_TAG, "loop started");
while (!_mFinished) {
// do stuff here
// create the object variable. Then pass to callback method
_callback.doSomethingFromCurrentThread(object);
// process and create the result to pass
String someResult = "some result here";
_handler.sendMessage(_handler.obtainMessage(CALLBACK_MESSAGE, someResult));
synchronized (_mPauseLock) {
while (_mPaused) {
try {
Log.i(LOG_LOGCAT_TAG, "loop paused");
_mPauseLock.wait();
Log.i(LOG_LOGCAT_TAG, "loop resumed");
} catch (InterruptedException e) {
Log.e(LOG_LOGCAT_TAG, "error occured on pause", e);
}
}
}
try {
//using sleep here might be not good design.
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e(LOG_LOGCAT_TAG, "error occured on sleep", e);
}
}
Log.i(LOG_LOGCAT_TAG, "loop ended");
}
private static Object _mPauseLock;
private static boolean _mPaused;
private static boolean _mFinished;
public static void start(Context context) {
Intent service = new Intent(context, SampleCallbackIntentService .class);
if(context.startService(service)==null) {
Log.e(LOG_LOGCAT_TAG, "Service cannot be started");
} else {
Log.i(LOG_LOGCAT_TAG, "start() called");
}
}
/**
* Call this on pause.
*/
public static void pause() {
Log.i(LOG_LOGCAT_TAG, "pause() called");
synchronized (_mPauseLock) {
_mPaused = true;
}
}
/**
* Call this on resume.
*/
public static void resume() {
Log.i(LOG_LOGCAT_TAG, "resume() called");
synchronized (_mPauseLock) {
_mPaused = false;
_mPauseLock.notifyAll();
}
}
public static void stop() {
if(_mPauseLock == null) return;
synchronized (_mPauseLock) {
Log.i(LOG_LOGCAT_TAG, "stop() called");
_mFinished = true;
}
}
}
SampleCallback
import android.os.Message;
public interface SampleCallback {
public void doSomethingFromCurrentThread(final Object object);
public void doSomethingFromUIThread(final Message msg);
}
UPDATES1
I am using location api aside from google api. I will create a android library project and use that api to get the latest location (e.g. every 2secs) in the background.
On the application side, just need to call static methods to use it (e.g. start(context, callback), pause(), resume(), stop()). It has callbacks to obtain the location. After obtaining the needed information from the location object, I will create a new thread to call my own created callbacks (which implemented by the application side).
You can use AsyncTask instead of creating a new thread every time? AsyncTask manages a fixed pool of threads (or one background thread - depending on Android version) and allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
However I wonder why do you need to create an infinite loop inside the onHandleIntent method? By doing that you prevent your IntentService from receiving further Intents. Since in IntentService:
All requests are handled on a single worker thread -- they may take as
long as necessary (and will not block the application's main loop),
but only one request will be processed at a time.
I think you want to execute some long-running code out of the UI thread in the IntentService. But that doesn't require the creation of an infinite loop in the IntentService worker thread. Just send the requests as needed to the IntentService using Context.startService(Intent) call. If you want IntentService to send back some result or just call a callback in the UI thread you can pass a Messenger (or a ResultReceiver) object with the Intent.
Activity:
final Handler uiHandler = new Handler(Looper.getMainLooper());
private void postTask() {
Intent intent = new Intent("com.yourservice.DOACTION");
intent.putExtra("messenger", new Messenger(handler));
intent.putExtra("object", YourObject()); // pass other Parcelable objects
startService(intent);
}
IntentService:
protected void onHandleIntent(Intent intent) {
Messenger messenger = intent.getParcelableExtra("messenger");
YourObject object = intent.getParcelableExtra("object");
//... do work here ...
Message msg = Message.obtain();
msg.what = CALLBACK_MESSAGE;
msg.setData(someResult);
messenger.send(Message.obtain());
}
Look into the docs for ExecutorService (not to be confused with Android Services) and the Executors package. There are a few examples there on how to use thread pools.
So wait, why do you need to use all these callbacks? Can't you just have each intent encode what needs to be done and then have your onHandleIntent execute different code based on the information of the intent. This is the way IntentService is intended to be used.
You shouldn't be doing any of the thread handling in the IntentSerivce. The IntentService is supposed to be handling all the threading code (and you should let it because it's probably highly optimized).

Running GPS positioning in a background thread on Android

I have curious problem, in my application, the GPS-positioning takes quite a long time (as it tends to do) and therefore I wanted to run it as its own thread while the user is making choices in other views.
My structure is that I have a "main" view that is always active in the background, then the user is shown a series of views in which he or she makes a few choices. In the end, the user is presented with a result based on the choices made and the current position. All of this works, but I wanted to move the GPS-bit to its own thread so that the user wouldn't have to wait for it to finish.
All of the user's choices and the GPS coordinates is stored in a singleton called CurrentLogEntry. All communication between the different parts of the program is performed through it.
I created a handleMessage(Message msg) override in the main view and then I implemented this function in Main.java:
void startGPSThread() {
Thread t = new Thread() {
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean isDebug = CurrentLogEntry.getInstance().isDebug();
// Define a listener that responds to location updates
LocationListener locationListener = new LocationListener() {
public void onStatusChanged(String provider, int status, Bundle extras) {
/* This is called when the GPS status changes */
String tag = "onStatusChanged, ";
switch (status) {
case LocationProvider.OUT_OF_SERVICE:
Log.w(tag, "Status Changed: Out of Service");
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
Log.w(tag, "Status Changed: Temporarily Unavailable");
break;
case LocationProvider.AVAILABLE:
break;
}
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
// This is called if the GPS is disabled in settings.
// Bring up the GPS settings
Intent intent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
public void onLocationChanged(Location location) {
// Once a location has been received, ignore all other position
// updates.
locationManager.removeUpdates(locationListener);
locationManager.removeUpdates(this);
// Make sure that the received location is a valid one,
// otherwise show a warning toast and hit "back".
if (location == null) {
String warningString = "Location was unititialized!";
if (isDebug) {
Toast.makeText(getApplicationContext(),
warningString,
Toast.LENGTH_LONG).show();
}
KeyEvent kev = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
onKeyDown(KeyEvent.KEYCODE_BACK, kev);
}
CurrentLogEntry.getInstance().setUserLatitude(location.getLatitude());
CurrentLogEntry.getInstance().setUserLongitude(location.getLongitude());
//Send update to the main thread
int result = 0;
if (location.getLatitude() == 0 || location.getLongitude() == 0) {
result = -1;
}
messageHandler.sendMessage(Message.obtain(messageHandler, result));
}
};
#Override
public void run() {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, locationListener);
// Wait until a position has bee acquired.
while(!CurrentLogEntry.getInstance().isReadyToCalculate()){
try {
wait(250);
} catch (InterruptedException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
};
t.start();
}
This function is called in Main.onCreate().
Unfortunately, this doesn't work at all. The program crashes as soon as it reaches locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); and that has me completely stumped.
Can anyone enlighten me as to why this won't run as a background thread? Also, do I really need the wait-polling at the end to ensure that the thread stays alive until it receives its data? The GPS bit worked just fine before I wanted to put it in a thread of its own, so what in the world am I doing wrong?
From what i understand you have to make the Thread a Looper thread in order to run Location Updates in the background...however i am still trying to figure this out myself...when i figure out how to do this i will post it.
Probably have the same error that i received, which is can't create handler in thread where Looper.prepare() has not been called
Hope you get it working bud
Check the this thread and specifically for this answer. They deal with the same subject, location, and the same problem, running it from a Thread loop.

How to manage Loopers and Threads (thread doesn't die anymore!)

I created a class extending Thread to retrieve user location through LocationManager in a non-ui thread. I implemented this as a thread because it has to be started on request and do its work just for a limited time.
By the way, I had to add a Looper object in the thread, to be able to create the handler for the LocationManager (onLocationChanged).
This is the code:
public class UserLocationThread extends Thread implements LocationListener {
//...
public void run() {
try {
Looper.prepare();
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
Looper.loop();
Looper.myLooper().quit();
} catch (Exception e) {
//...
}
}
#Override
public void onLocationChanged(Location location) {
locationManager.removeUpdates(this);
//...
handler.sendMessage(msg); //this is the handler for communication with father thread
}
//...}
I would like the thread to start, receive the user location data (in this case just one time), send the data to the main thread via a message to the handler, and then die.
The problem is that in my case the thread does not die anymore, once the run method ended (that should be fine, because otherwise onLocationChanged would not receive the new locations).
But in this way, assuming that thread's stop and suspend methods are deprecated, what would be a good way, in this case at least, to make a thread with a looper die?
Thanks in advance ;)
You can explicitly quit from Looper's loop using Handler:
private Handler mUserLocationHandler = null;
private Handler handler = null;
public class UserLocationThread extends Thread implements LocationListener {
public void run() {
try {
Looper.prepare();
mUserLocationHandler = new Handler();
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
Looper.loop();
} catch (Exception e) {
//...
}
}
#Override
public void onLocationChanged(Location location) {
locationManager.removeUpdates(this);
//...
handler.sendMessage(msg);
if(mUserLocationHandler != null){
mUserLocationHandler.getLooper().quit();
}
}
"I implemented this as a tread because it has to be started on request and do its work just for a limited time."
This sounds like a perfect reason to simply reuse the main looper. There's no need to spawn a new Thread here. If you're doing blocking work (network I/O, etc) in onLocationChanged(), at that point you could spin up an ASyncTask.
Implement LocationListener on your Activity/Service or whatever and let it use the main looper by default.
Spawning a new thread, setting it to loop, and then immediately quitting is unnecessary.
IntentService is good for do this job.
IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
Looper().quit(); is good, and according to specification:
Causes the loop() method to terminate without processing any more messages in the message queue.
But, if you have a task that already is under processing, and you want to stop it too, you can acquire working thread and cause it to interrupt:
#Override
public void onLocationChanged(Location location) {
locationManager.removeUpdates(this);
handler.sendMessage(msg); //this is the handler for communication with father thread
if(mUserLocationHandler != null){
mUserLocationHandler.getLooper().quit();
mUserLocationHandler.getLooper().getThread().interrupt(); // <-- here
}
}
This works fine with most IO, and thread locking/waiting.
Extend the AsyncTask class. It does all the threading and handling for you automatically.

Categories

Resources