I'm trying to create an app that makes HTTP requests through an intentservice. I need the app to wait for the service to finish its run (aka, have the request be returned with some data) before it continues its operations, as its operations involve manipulation of the data I hope to receive from the HTTP requests. I've tried numerous means of doing so - Semaphore, CountDownLatch, but it seems that for all of them, I need some method of passing in the waiting/counting object into the intentservice so that it can tell the main thread where that object is waiting that it is done processing. How do I go about doing that? Basically, I want a synchronous, blocking call to an http server to work conveniently with an Intent Service, since an intent service makes multi threading easy.
Again to reiterate just to make sure i'm not misusing terminology: What I mean by Synchronous and blocking/what I want: I make a call to the http server by sending an intent to my intentservice that makes the request. My UI thread, or thread from which this intent was sent, now waits until the request has been processed and a result has been returned before continuing to run.
If you think that I am going about this process (making http calls in a blocking, synchronous way) all wrong, what is another way you might choose to go about it? Thanks!
I am sorry, but I think your architecture is not right or I may understand it wrong. IntentService is built to do thing serial way on separate thread. Now you say you want it to be synchronous and blocking. You cannot block UI thread!
In order to create notification system from your IntentService to Activity/Fragment/etc. you have few choices: singleton, broadcast message (receiver, resultReceiver), others?
Based on assumption that service and other parts of the application are working in same process. Best option would be to create manager to do this job. Something like this can be built to start service as well as listen for completion event:
public class MyNetworkManager {
static MyNetworkManager sInstance;
Context mContext;
LinkedList<OnCompletionListener> mListeners;
private MyNetworkManager(Context context) {
mContext = context;
mListeners = new LinkedList<>();
}
public static MyNetworkManager getInstance(Context context) {
if (sInstance == null) {
synchronized (MyNetworkManager.class) {
if (sInstance == null) {
sInstance = new MyNetworkManager(context.getApplicationContext());
}
}
}
return sInstance;
}
// add listener to listen for completion event
public void addListener(OnCompletionListener listener) {
synchronized (mListeners) {
mListeners.add(listener);
}
}
// remove listener to stop listening for completion event
public void removeListener(OnCompletionListener listener) {
synchronized (mListeners) {
mListeners.remove(listener);
}
}
// call from UI to start service operation
public void startNetworkOperation() {
Intent service = new Intent();
mContext.startService(service);
}
// call from service to notify UI (still on different thread, can use Handler to make call on main thread)
public void notifyCompletion() {
synchronized (mListeners) {
for (OnCompletionListener listener : mListeners) {
listener.onCompleted(this);
}
}
}
public static interface OnCompletionListener {
void onCompleted(MyNetworkManager manager);
}
}
Use this pattern
public interface SynchronizationListener {
//void onStart(int id); not requered
//void onProgress(int id, long updateTime); not requered
void onFinish(Object data); // replace Object with your data type
}
In your service add end call this
private void startSynchronization() {
SynchronizationManager.getInstance().startSynchronizing();
}
Your Singleton Manager
public class SynchronizationManager {
private static SynchronizationManager instance;
private Object synRoot = new Object();
private boolean synchronizing = false;
private List<SynchronizationListener> synchronizationListeners;
public SynchronizationManager() {
synchronizationListeners = new ArrayList<SynchronizationListener>();
}
static {
instance = new SynchronizationManager();
}
public static SynchronizationManager getInstance() {
return instance;
}
public boolean isSynchronizing() {
synchronized (synRoot) {
return synchronizing;
}
}
public void startSynchronizing() {
synchronized (synRoot) {
if (synchronizing) {
return;
}
synchronizing = true;
}
Object data; // <-- replace Object with your data type
if (ConnectivityReceiver.hasGoodEnoughNetworkConnection()) { // check connection
data = sync();
}
synchronized (synRoot) {
synchronizing = false;
}
onSynchronizationFinish(data); // use listener for send data tu Observer Activity
}
public void stopSynchronizing() {
synchronized (synRoot) {
synchronizing = false;
}
}
public synchronized void registerSynchronizationListener(
SynchronizationListener listener) {
if (!synchronizationListeners.contains(listener)) {
synchronizationListeners.add(listener);
}
}
public synchronized void unregisterSynchronizationListener(
SynchronizationListener listener) {
if (synchronizationListeners.contains(listener)) {
synchronizationListeners.remove(listener);
}
}
public void onSynchronizationStart(int id) {
for (SynchronizationListener listener : synchronizationListeners) {
listener.onStart(id);
}
}
protected void onSynchronizationProgress(int id, long updateTime) {
for (SynchronizationListener listener : synchronizationListeners) {
listener.onProgress(id, updateTime);
}
}
protected void onSynchronizationFinish(Object data) {
for (SynchronizationListener listener : synchronizationListeners) {
listener.onFinish(data);
}
}
protected int sync) {
// code for load your data your HttpRequest
}
}
In your activity
private SynchronizationListener synchronizationListener = new SynchronizationListener() {
/*public void onStart(int id) {
}
public void onProgress(int id, long updateTime) {
}*/
public void onFinish(Object data) {
//elaborate data
}
};
#Override
protected void onResume() {
super.onResume();
SynchronizationManager.getInstance().registerSynchronizationListener(
synchronizationListener);
}
#Override
protected void onPause() {
super.onPause();
SynchronizationManager.getInstance().unregisterSynchronizationListener(
synchronizationListener);
}
See this code for example UnivrApp
A ContentProvider would be a better choice than an IntentService in my thinking. You can trigger each network call with a query and then return a MatrixCursor with details about the results of your background work. Android already has lots of good plumbing around running queries in background tasks and waiting for the results before triggering ui updates.
in ContentProvider query() method :
MatrixCursor cursor = new MatrixCursor(new String[]{"_id","uri", "status", "last_modified", "result"});
String lastModified=null;
int id =1;
// do your work here
// ..
// report your work here
cursor.addRow(new Object[]{id++, uri.toString(), HttpStatus.SC_OK, lastModified, "" });
// set uri for data observers to register
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
What you try to do is just communication between IntentService and Activity/Fragment.
You can try send broadcast at the end of onHandleIntent and catch it in registered receiver or use ResultReceiver - read more how to implement here.
Edit:
Try this:
Handle all background operations at once in onHandleIntent
On every step send new data using ResultReceiver
// operation 1
Bundle b1 = new Bundle();
b1.putParcelable("data", data1);
resultReceiver.send(0, b1);
// operation 2
Bundle b2 = new Bundle();
b2.putParcelable("data", data2);
resultReceiver.send(1, b2);
Handle it in ResultReceiver
public void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 0) { // handle step 1 }
else if (resultCode == 1) { // handle step 2 }
}
Related
How to continuously get the results from a service to UI or MainActivity.?
I am copying some large file using a service,and i want to update the progress bar in Foreground.
Simplest way to do this is to use Singleton object with listener.
Subscribe for update in activity and just set values in Service.
public class ProgressSingleton {
private static ProgressSingleton instance;
private ProgressListener progressListener = null;
public static void initInstance() {
if (instance == null) {
instance = new ProgressSingleton();
}
}
public static ProgressSingleton getInstance() {
return instance;
}
private ProgressSingleton() {
}
public void setProgress(int progress) {
if (progressListener != null) {
progressListener.onProgressUpdated(progress);
}
}
public interface ProgressListener {
void onProgressUpdated(int progress);
}
}
Make shure that you handle updates from listener in UI thread.
Also you can start your service in foreground, best option for big files.
I'm looking at retrofit for my networking layer. Is there any way to tell if a particular async request is running at any given moment?
For example, I'd like to know if a request is running so that I can update the user interface at various times. I could do this myself by keeping variables around to track state, but wondering if there's something already in the library for this.
Here is what I would normally do when needing a way to keep track of running requests:
First, using retrofit, as soon as you make the request, you can do the following:
Use EventBus library to post an event to your activity or fragment. Now, this can be done inside onSuccess() method of your Callback or onError() method of the same.
In your activity or fragment's onEvent(EventClassName event) method, you can simply check a variable like [isRunning] from your event to make sure that if the event is still running, you update the UI accordingly and if not, do what you need to do respectively.
When the request is completed, obviously isRunning will be false and you can then update the UI as expected by the user.
I am recommending EventBus here simply because it is much easier to decouple your application code with it; you can send different events that notify the activity of the different statuses of your requests and then update your UI that way.
You can find EventBus here
I hope this helps!
What I personally ended up doing in this case was that I was running the example with Retrofit, Android Priority Jobqueue (from yigit's fork) and Otto eventbus.
public enum SingletonBus {
INSTANCE;
private Bus bus;
private Handler handler = new Handler(Looper.getMainLooper());
private SingletonBus() {
this.bus = new Bus(ThreadEnforcer.ANY);
}
public <T> void postToSameThread(final T event) {
bus.post(event);
}
public <T> void postToMainThread(final T event) {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
public <T> void register(T subscriber) {
bus.register(subscriber);
}
public <T> void unregister(T subscriber) {
bus.unregister(subscriber);
}
}
public interface Interactor {
void injectWith(PresenterComponent presenterComponent);
}
public interface SendCertificateRequestInteractor
extends Interactor {
interface Listener {
void onSuccessfulEvent(SuccessfulEvent e);
void onFailureEvent(FailureEvent e);
}
class SuccessfulEvent
extends EventResult<CertificateBO> {
public SuccessfulEvent(CertificateBO certificateBO) {
super(certificateBO);
}
}
class FailureEvent
extends EventResult<Throwable> {
public FailureEvent(Throwable throwable) {
super(throwable);
}
}
void sendCertificateRequest(String username, String password);
}
Pay attention to the Job here:
public class SendCertificateRequestInteractorImpl
implements SendCertificateRequestInteractor {
private Presenter presenter;
private boolean isInjected = false;
#Inject
public JobManager jobManager;
public SendCertificateRequestInteractorImpl(Presenter presenter) {
this.presenter = presenter;
}
#Override
public void sendCertificateRequest(String username, String password) {
if(!isInjected) {
injectWith(presenter.getPresenterComponent());
isInjected = true;
}
InteractorJob interactorJob = new InteractorJob(presenter, username, password);
long jobId = jobManager.addJob(interactorJob); //this is where you can get your jobId for querying the status of the task if you want
}
#Override
public void injectWith(PresenterComponent presenterComponent) {
presenterComponent.inject(this);
}
public static class InteractorJob
extends Job {
private final static int PRIORITY = 1;
private final static String TAG = InteractorJob.class.getSimpleName();
private String username;
private String password;
#Inject
public MyService myService;
public InteractorJob(Presenter presenter, String username, String password) {
super(new Params(PRIORITY).requireNetwork());
presenter.getPresenterComponent().inject(this);
this.username = username;
this.password = password;
}
#Override
public void onAdded() {
// Job has been saved to disk.
// This is a good place to dispatch a UI event to indicate the job will eventually run.
// In this example, it would be good to update the UI with the newly posted tweet.
}
#Override
public void onRun()
throws Throwable {
String certificate = myService.getCertificate(username, password);
SingletonBus.INSTANCE.postToMainThread(new SuccessfulEvent(certificate));
}
#Override
protected void onCancel() {
// Job has exceeded retry attempts or shouldReRunOnThrowable() has returned false.
Log.e(TAG, "Cancelled job.");
}
#Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
// An error occurred in onRun.
// Return value determines whether this job should retry running (true) or abort (false).
Log.e(TAG, "Failed to execute job.", throwable);
SingletonBus.INSTANCE.postToMainThread(new FailureEvent(throwable));
return false;
}
}
}
And then
#Subscribe
#Override
public void onSuccessfulEvent(SendCertificateRequestInteractor.SuccessfulEvent e) {
String certificate = e.getResult();
//do things
}
#Subscribe
#Override
public void onFailureEvent(SendCertificateRequestInteractor.FailureEvent e) {
Throwable throwable = e.getResult();
//handle error
}
More about android priority jobqueue here.
This way, technically the async handling is referred to the job queue, while Retrofit itself is using the synchronous interface. It works well as long as you don't need to access the headers of the response. Although to be fair, I was also keeping track of whether the job was running with a boolean instead of the job manager and the id as well..
Also, I haven't figured out how to use dependency injection properly with persisted jobs; nor do I really know how they intended to make that work. Of course, it'd work if it was using the application scoped component rather than a supplied presenter scoped one, but that is irrelevant.
You'll probably need to customize this solution to your own scenario, and use only what you actually need.
I am using a Loader in my application and based on the result I get from the query I perform on COntacts using this Loader I perform some calculations and store them back in a Sqlite DB. I want this operation to be Asynchronous, however I am confused between using an Async task, as I have lot of different data types to return or should I use a simple handler or an AsyncTaskLoader, I want it to be simple as I am new to Loaders. I tried to search around for examples of AsyncTaskLoader but it seems rocket science, a basic and simple functional example of any of the three in the context of my scenario would be a lot helpful.
If you wish to use AsyncTaskLoader, here's a nice sample for you.
EDIT: I've decided to make a simpler solution (based on this repo):
public abstract class AsyncTaskLoaderEx<T> extends AsyncTaskLoader<T> {
private static final AtomicInteger sCurrentUniqueId = new AtomicInteger(0);
private T mData;
public boolean hasResult = false;
public static int getNewUniqueLoaderId() {
return sCurrentUniqueId.getAndIncrement();
}
public AsyncTaskLoaderEx(final Context context) {
super(context);
onContentChanged();
}
#Override
protected void onStartLoading() {
if (takeContentChanged())
forceLoad();
//this part should be removed from support library 27.1.0 :
//else if (hasResult)
// deliverResult(mData);
}
#Override
public void deliverResult(final T data) {
mData = data;
hasResult = true;
super.deliverResult(data);
}
#Override
protected void onReset() {
super.onReset();
onStopLoading();
if (hasResult) {
onReleaseResources(mData);
mData = null;
hasResult = false;
}
}
protected void onReleaseResources(T data) {
//nothing to do.
}
public T getResult() {
return mData;
}
}
Usage:
in your activity:
getSupportLoaderManager().initLoader(TASK_ID, TASK_BUNDLE, new LoaderManager.LoaderCallbacks<Bitmap>() {
#Override
public Loader<Bitmap> onCreateLoader(final int id, final Bundle args) {
return new ImageLoadingTask(MainActivity.this);
}
#Override
public void onLoadFinished(final Loader<Bitmap> loader, final Bitmap result) {
if (result == null)
return;
//TODO use result
}
#Override
public void onLoaderReset(final Loader<Bitmap> loader) {
}
});
inner static class , or a normal class:
private static class ImageLoadingTask extends AsyncTaskLoaderEx<Bitmap> {
public ImageLoadingTask (Context context) {
super(context);
}
#Override
public Bitmap loadInBackground() {
//TODO load and return bitmap
}
}
Update: starting from support library 27.1.0, things changed a bit (link here) :
In version 27.1.0, onStartLoading() is called every time the Activity
is started. Since you call deliverResult() in onStartLoading(), you
trigger onLoadFinished(). This is Working as Intended.
You should remove your call to deliverResult() from onStartLoading()
as it is not needed (Loaders already deliver results computed in
loadInBackground() without any additional work needed on your part).
I've updated the code above for this change.
EDIT:
Updated, kotlin version can be found here.
Since Honeycomb and the v4 Compatibility Library it is possible to use AsyncTaskLoader. From what I understand, the AsyncTaskLoader can survive through config changes like screen flips. But using AsyncTask you can mess up with configuration changes.
Key information: AsyncTaskLoader is subclass of Loader. This class performs the same function as the AsyncTask, but a bit better, it can also be useful in handling configuration changes (screen orientation).
A very good example and explanation is given here.
http://www.javacodegeeks.com/2013/01/android-loaders-versus-asynctask.html
Google has a pretty good example directly in the API Docs.
Android Design Patterns provides some more detail and the reasoning behind Loaders.
This tutorial will definetly help You. http://www.javacodegeeks.com/2013/08/android-custom-loader-to-load-data-directly-from-sqlite-database.html
Here's step by step tutorial to implement AsyncTaskLoader. or check out this same article on Medium
Implement LoaderManager.LoaderCallbacks<String> on MainActivity and create a static int to uniquely identify your loader and create a String key to pass string url to your loader
public class MainActivity extends AppCompatActivity
implements LoaderManager.LoaderCallbacks<String>{
public static final int OPERATION_SEARCH_LOADER = 22;
public static final String OPERATION_QUERY_URL_EXTRA = "query";
//...}
Override onCreateLoader,onLoadFinishedand onLoaderReset functions inside MainActivity
#Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
//Here we will initiate AsyncTaskLoader
return null;
}
#Override
public void onLoadFinished(Loader<String> loader, String operationResult) {
//Think of this as AsyncTask onPostExecute method, the result from onCreateLoader will be available in operationResult variable and here you can update UI with the data fetched.
Log.d("MAINACTIVITY","result : "+ operationResult);
}
#Override
public void onLoaderReset(Loader<String> loader) {
//Don't bother about it, Android Studio will override it for you
}
inside onCreateLoader() return a new AsyncTaskLoader<String> as an anonymous inner class with this as the constructor's parameter and override loadInBackground & onStartLoading inside anonymous
inner class
#Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
return new AsyncTaskLoader<String>(this) {
#Override
public String loadInBackground() {
//Think of this as AsyncTask doInBackground() method, here you will actually initiate Network call
return null;
}
#Override
protected void onStartLoading() {
//Think of this as AsyncTask onPreExecute() method,start your progress bar,and at the end call forceLoad();
forceLoad();
}
};
}
Inside loadInBackground make a network call using HTTPUrlConnection or OKHttp or anything that you use.
#Override
public String loadInBackground() {
String url = args.getString(OPERATION_QUERY_URL_EXTRA);//This is a url in string form
if (url!=null&&"".equals(url)) {
return null;//if url is null, return
}
String operationResult="";
try {
operationResult = NetworkUtils.getResponseFromHttpUrl(url);//This just create a HTTPUrlConnection and return result in strings
} catch (IOException e) {
e.printStackTrace();
}
return operationResult;
}
Inside onCreate initialize the loader with OPERATION_SEARCH_LOADER as the ID, null for the bundle, and this for the context
getSupportLoaderManager().initLoader(OPERATION_SEARCH_LOADER, null, this);
Now call this method, whenever and wherever you want to trigger the loader
private void makeOperationSearchQuery(String url) {
// Create a bundle called queryBundle
Bundle queryBundle = new Bundle();
// Use putString with OPERATION_QUERY_URL_EXTRA as the key and the String value of the URL as the value
queryBundle.putString(OPERATION_QUERY_URL_EXTRA,url);
// Call getSupportLoaderManager and store it in a LoaderManager variable
LoaderManager loaderManager = getSupportLoaderManager();
// Get our Loader by calling getLoader and passing the ID we specified
Loader<String> loader = loaderManager.getLoader(OPERATION_SEARCH_LOADER);
// If the Loader was null, initialize it. Else, restart it.
if(loader==null){
loaderManager.initLoader(OPERATION_SEARCH_LOADER, queryBundle, this);
}else{
loaderManager.restartLoader(OPERATION_SEARCH_LOADER, queryBundle, this);
}
}
Walla, you are done, just to remind you NetworkUtils.getResponseFromHttpUrl(url); is my custom function which take string convert it into URL which in turn used to create HTTPUrlConnection
I like this brief example AsyncTask and AsyncTaskLoader.
class FooLoader extends AsyncTaskLoader {
public FooLoader(Context context, Bundle args) {
super(context);
// do some initializations here
}
public String loadInBackground() {
String result = "";
// ...
// do long running tasks here
// ...
return result;
}
}
class FooLoaderClient implements LoaderManager.LoaderCallbacks {
Activity context;
// to be used for support library:
// FragmentActivity context2;
public Loader onCreateLoader(int id, Bundle args) {
// init loader depending on id
return new FooLoader(context, args);
}
public void onLoadFinished(Loader loader, String data) {
// ...
// update UI here
//
}
public void onLoaderReset(Loader loader) {
// ...
}
public void useLoader() {
Bundle args = new Bundle();
// ...
// fill in args
// ...
Loader loader =
context.getLoaderManager().initLoader(0, args, this);
// with support library:
// Loader loader =
// context2.getSupportLoaderManager().initLoader(0, args, this);
// call forceLoad() to start processing
loader.forceLoad();
}
}
Simplifying hard, maybe
private void loadContent() {
getLoaderManager().initLoader(1000, new Bundle(),
new LoaderManager.LoaderCallbacks<List<String>>() {
#Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<List<String>>(MainActivity.this.getApplicationContext()) {
#Override
public List<String> loadInBackground() {
Log.i("B", "Load background data ");
ArrayList<String> data = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
data.add("Data." + i + " " + System.currentTimeMillis());
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}
};
}
#Override
public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
Log.i("B", "Here are your data loaded" + data);
if (!loader.isAbandoned()) {
mAdapter.setData(data); // Read also about RecyclerView
}
}
#Override
public void onLoaderReset(Loader<List<String>> loader) {
Log.i("B", "Loader reset");
}
}).forceLoad();
}
#Override
protected void onDestroy() {
// Abandon the loader so that it should not attempt to modify already dead GUI component
getLoaderManager().getLoader(1000).abandon();
super.onDestroy();
}
Make this part of your Activity. The sample simulates delay, but makes new entries easy to recognize because they will have the different time stamp suffix. Of course you also need RecyclerView to display the data, the answer to this question seems very good.
The loader in this example is the inner class that keeps the reference to the parent activity. It must be external static class without such reference in production.
I prefer using Bolts-Android. it is very easy.
https://github.com/BoltsFramework/Bolts-Android
Task.callInBackground(new Callable<Void>() {
public Void call() {
// Do a bunch of stuff.
}
}).continueWith(...);
I seem to be having one of two issues here. Either an error like:
java.lang.IllegalStateException: Event bus [Bus "default"] accessed from non-main thread Looper
Or, if I've managed to "address" that, I simply never receive the event in my subscriber.
Currently, I have a class, cobbled from a few sources, sub-classing Bus:
public class MainThreadBus extends Bus {
private static Bus _bus;
private Handler _handler = new Handler(Looper.getMainLooper());
public MainThreadBus() {
if (_bus == null) {
_bus = new Bus();
}
}
#Override public void register(Object obj) {
_bus.register(obj);
}
#Override public void unregister(Object obj) {
_bus.unregister(obj);
}
#Override public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
_bus.post(event);
} else {
_handler.post(new Runnable() {
#Override public void run() {
_bus.post(event);
}
});
}
}
}
Which is used in an Activity like this:
#Subscribe
public void requestProgressAvailable(RESTRequestProgress progress) {
processProgress(progress);
}
#Override
protected void onResume() {
super.onResume();
_bus = new MainThreadBus();
_bus.register(this);
}
#Override
protected void onPause() {
super.onPause();
_bus = new MainThreadBus();
_bus.unregister(this);
}
And publishing from an IntentService like this:
_bus = new MainThreadBus();
_bus.post(request.createRESTRequestProgress(RESTRequest.STATUS_STARTED));
And the messages are not received. An alternate configuration had me receiving the thread error, so I'm going with this, for now.
So, what am I missing, or doing wrong?
EDIT: Thanks to Andy below for pointing out that my code wasn't acting as I thought it was. The code above now reflects modifications based on that feedback.
Aside from the fact this implementation isn't a Singleton, when getting this error, you can use the threadEnforcer.ANY option in the constructor:
private static final Bus BUS = new Bus(ThreadEnforcer.ANY);
The problem is that your code is not interacting with the same bus instance. Instead of creating a new MainThreadBus in each case, you should access the same bus, for example a singleton obtained from a factory or via injection.
I have a Service that creates AsyncTasks for downloading files. In activities, we create Runnables or Threads that we pass to Activity.runOnUiThread(). I can't access that method from a service, so how do I use AsyncTask correctly, (do heavy work without blocking the UI Thread)?
If your service is only called from your application, and you can make it a singleton, then try this:
public class FileDownloaderService extends Service implements FileDownloader {
private static FileDownloaderService instance;
public FileDownloaderService () {
if (instance != null) {
throw new IllegalStateException("This service is supposed to be a singleton");
}
}
public static FileDownloaderService getInstance() {
// TODO: Make sure instance is not null!
return instance;
}
#Override
public void onCreate() {
instance = this;
}
#Override
public IBinder onBind(#SuppressWarnings("unused") Intent intent) {
return null;
}
#Override
public void downloadFile(URL from, File to, ProgressListener progressListener) {
new Thread(new Runnable() {
#Override
public void run() {
// Perform the file download
}
}).start();
}
}
Now you can directly call methods on your service. So just call downloadFile() to put the service to work.
About your real question of how to update the UI. Notice that this method receives a ProgressListener instance. It could look like this:
public interface ProgressListener {
void startDownloading();
void downloadProgress(int progress);
void endOfDownload();
void downloadFailed();
}
Now you just update the UI from the activity (not from the service, which remains unaware of how the UI looks like).