2

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!

bgenchel
  • 3,739
  • 4
  • 19
  • 28
  • You have received several complicated answers, maybe because you asked a simple question in a complicated way. – x-code Aug 16 '14 at 00:24
  • If your real question is something like "how do I make an http request in a background thread and then process the results on the UI thread" you should edit your question and point that out. – x-code Aug 16 '14 at 00:27
  • that is now closer to what I would like to know, but originally I wanted to use serviceIntent hence the phrasing of the question. Also, I don't think your question is specific enough. I want to know what you asked, yes. But most importantly I want to know how to make these calls synchronous and blocking. I need the UI thread to wait for the background thread to finish running before it continues. – bgenchel Aug 18 '14 at 17:07
  • possible duplicate of [Make an HTTP request with android](http://stackoverflow.com/questions/3505930/make-an-http-request-with-android) – x-code Aug 18 '14 at 17:31
  • its not a duplicate since that post only asks for how to make a request independent of its role in the application, whereas this post asks for a method of requesting and then **waiting for the request to return** before continuing – bgenchel Aug 18 '14 at 19:05
  • I reworded my question to be more specific, and am trying to look more deeply into one of the solutions listed below by submitting questions to the author. If this question being on hold is preventing the author from being notified of my comments, I would like to request that it be taken off hold, or at least that i be given more specific instructions as to how to get it off hold so that he can be notified and respond. – bgenchel Aug 18 '14 at 22:52

4 Answers4

2

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

Davide
  • 622
  • 4
  • 10
  • could you try to verbalize this pattern so that i can understand what its doing? This seems really convoluted and confusing. – bgenchel Aug 15 '14 at 22:56
  • ..your manager execute HttpRequest end the data is sended to Actiity by the listener that you have added into onResume() method – Davide Aug 15 '14 at 23:06
  • You have understand now? see http://developer.android.com/reference/java/util/Observer.html – Davide Aug 15 '14 at 23:19
  • Or see this for another example http://stackoverflow.com/questions/7659974/set-a-listener-in-a-service-based-class#answer-7775771 I hope this will help.. – Davide Aug 15 '14 at 23:38
  • hey Davide, can you explain the static{} that initializes 'instance'? I've never seen a nameless method before. How does it work? when does it run? – bgenchel Aug 18 '14 at 21:41
2

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);
    }
}
Volodymyr Lykhonis
  • 2,936
  • 2
  • 17
  • 13
  • i don't think you misunderstand what i'm trying to do. I need to run my http calls on a separate thread, because android does not allow http calls on the same thread. So I wanted to find a way to make these async calls, because they have to be async or i'll error out as I just said, synchronous and blocking. Your answer might make sense to someone more experienced, but for me it just looks really confusing. Is there any where you might recommend learning more about these components, or would you be able to provide a simpler explanation of the compponents you've written here? – bgenchel Aug 16 '14 at 00:12
  • I do not think you should think as "synchronous and blocking", Android nature is more event based. Here Manager is a "bridge" as singleton (one and only instance across process) between your service life-cycle and life-cycle of your activity where you want to get your callback. – Volodymyr Lykhonis Aug 16 '14 at 02:12
  • can you explain how the 'synchronized' keyword is being used herE? – bgenchel Aug 18 '14 at 18:46
  • why do you double check instance == null in the getInstance() Method? – bgenchel Aug 18 '14 at 22:27
  • also, it seems that the service only gets the lock of the SyncManager when it calls notifyCompletion which then would block the UI thread, but the ui thread can still run before that call is made, when the service is processing the request. Is that true? Or am I looking at this the wrong way. – bgenchel Aug 18 '14 at 22:37
  • synchronized used here since we are dealing with shared state and 2 threads in order to keep data (lists and instances/references) persistent. Double check on null, it is just one of the ways to implement singleton pattern. Synchronize does not block UI or any other threads, unless 2 threads calls same function that contains synchronization block, but this may cause only few ms block or none at all. – Volodymyr Lykhonis Aug 20 '14 at 00:25
1

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:

  1. Handle all background operations at once in onHandleIntent
  2. 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);
    
  3. Handle it in ResultReceiver

    public void onReceiveResult(int resultCode, Bundle resultData) {
        if (resultCode == 0) { // handle step 1 }
        else if (resultCode == 1) { // handle step 2 }
    }
    
asylume
  • 534
  • 3
  • 6
  • I have a resultreceiver implemented currently, but it is on the same thread as the fragment. If it weren't, then I could implement the countDown for the countdown latch or release for the semaphore, but then I wouldn't know how to share that receiver among all my classes. – bgenchel Aug 15 '14 at 22:46
  • Then why do you even need to synchronize any thread? You invoke IntentService, wait for data, then make some operations, invoke second time, etc... Or maybe you have another AsyncTask or Thread and you are waiting there for results? – asylume Aug 15 '14 at 22:57
  • no, its only the intentservice. That second step, after invoke intentservice. That is my big problem. How do I wait for the data? i can't find a way to do that with intent service. – bgenchel Aug 15 '14 at 23:12
  • I don't know anything about what do you mean by "operations" so general solution: save required data in private fields then after you get data from ResultReceiver, make some other operations. – asylume Aug 15 '14 at 23:15
  • Also if you don't need to update UI while you make this time consuming work, just handle all operations in `onHandleIntent` and return final result. – asylume Aug 15 '14 at 23:18
  • i do need to update the UI, that's why i need to wait for the data to finish. that's why i can't save into private variables, because if i make a call to the intentservice, and then need to access the data right after, I need the thread to wait until the request/call to intent service has been processed. – bgenchel Aug 15 '14 at 23:23
1

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;
dangVarmit
  • 5,641
  • 2
  • 22
  • 24
  • it sounds to me, after reading the android man page, that a content provider is for managing an already present set of data. Also, How would I be able to run my requests in different threads? – bgenchel Aug 15 '14 at 23:09
  • content provider can be used to manage data like that, but it's not a requirement. It's a specialized form of a Service that exposes query,insert,update,delete methods that key off of uris. The nice thing is that android assumes that when you call query() it's going to take a while so there are Loaders and such that offload the task to separate threads. So you can create a content provider where the query() method can use a URL connection to grab data. You can just wait for it to return, because android requires that queries are run off the UI thread. – dangVarmit Aug 16 '14 at 00:28