问题
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!
回答1:
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);
}
}
回答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
回答3:
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;
回答4:
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 } }
来源:https://stackoverflow.com/questions/25335079/thread-synchronization-with-intentservice