2015-05-30 9 views
8

In meinem Fragment versuche ich TMDBs Film-DB zu verwenden, um Details über "Now Playing" -Filme zu erhalten.Android Volley Timeout Ausnahme bei Verwendung von RequestFuture.get()

Wenn ich RequestFuture.get (Zeit, TimeUnit) -Methode, um diese Volley-Anfrage auszuführen, bekomme ich immer einen Timeout-Fehler. Wenn ich dieselbe URL in Safari manuell teste, erhalte ich die Ergebnisse sofort.

Was ich weiß:

1.) Es ist nicht JSON Parsing-Fehler (das Programm nicht sogar die Parsing Schritte voran)

2.) Keine Internet-Themen mit AVD.. (Grund wird später erklärt).

3.) Kein Problem mit meiner Volley Singleton Klasse oder meiner Request Queue. (Grund später erklärt).

Also ich nehme an, dass ich eine andere Art von Fehler in Bezug auf die Verwendung von Volley/Request Future mache.

Fragment-Code unten:

public class BoxOffice extends android.support.v4.app.Fragment { 
    private VolleySingleton volleySingleton; 
    private RequestQueue requestQueue; 
    private ImageLoader imageLoader; 
    private ArrayList<MyMovie> movieList; 
    private MyUriBuilder mBuilder; 

    public BoxOffice() { 
     // Required empty public constructor 
     volleySingleton = VolleySingleton.getInstance(); 
     requestQueue = volleySingleton.getRequestQueue(); 
     mBuilder = new MyUriBuilder(); 
     movieList = new ArrayList<>(); 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     if (getArguments() != null) { 
      mParam1 = getArguments().getString(ARG_PARAM1); 
      mParam2 = getArguments().getString(ARG_PARAM2); 
     } 
     StepA(); 
    } 

    public void StepA() { 
     String url = mBuilder.getURL("box"); 
     Log.d("RT", "StepA initiated - "+ url); // Url is perfect - works when copied in Safari. 
     RequestFuture<JSONObject> futureA = RequestFuture.newFuture(); 
     JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, (String) null, futureA, futureA); 
     requestQueue.add(request); 

     try { 
      JSONObject response = futureA.get(30, TimeUnit.SECONDS); 
      Log.d("RT", "StepA - response received"); //Never reaches this step 
      parseJsonFeed(response); 
     } catch (InterruptedException e) { 
      Log.e("RT", "StepA - InterruptedException - " + e); 
      e.printStackTrace(); 
     } catch (ExecutionException e) { 
      Log.e("RT", "StepA - ExecutionException - " + e); 
      e.printStackTrace(); 
     } catch (TimeoutException e) { 
      Log.e("RT", "StepA - TimeoutException - " + e); 
      e.printStackTrace(); 
     } 
     Log.d("RT", "StepA END"); 
    } 

    public void parseJsonFeed(JSONObject response) { 
     Log.d("RT", "StepA - parseFeed Begin"); 
     if (response == null || response.length() == 0) { 
      return; 
     } 
     MyMovie currentMovie = null; 
     DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 

     try { 
      if (response.has("results")) { 
       Log.d("RT", "StepA - results"); 
       JSONArray resultList = response.getJSONArray("results"); 
       for (int i = 0; i < 3; i++) { 
        Log.d("RT", "movie " + i); 
        JSONObject movieElement = resultList.getJSONObject(i); 
        if (movieElement.has("id") && movieElement.has("title")) { 
         currentMovie = new MyMovie(); 
         currentMovie.setTmdb_id(movieElement.getString("id")); 
         currentMovie.setTitle(movieElement.getString("title")); 
         if (movieElement.has("release_date")) { 
          currentMovie.setReleaseDate(dateFormat.parse(movieElement.getString("release_date"))); 
         } else { 
          currentMovie.setReleaseDate(dateFormat.parse("0000-00-00")); 
         } 
         movieList.add(i, currentMovie); 
        } 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     Log.d("RT", "StepA - parseFeed END"); 
    } 
} 

Logcat mit dem Filter für den Tag "RT":

05-30 15:17:51.710 D/RT﹕ TL - Constructor Called 
05-30 15:17:51.800 D/RT﹕ StepA initiated - https://api.themoviedb.org/3/movie/now_playing?api_key=##### (link works fine) 
05-30 15:18:21.820 E/RT﹕ StepA - TimeoutException - java.util.concurrent.TimeoutException 
05-30 15:18:21.820 D/RT﹕ StepA END 

Bevor die RequestFuture Methoden verwenden, im Grunde ich die gleiche Sache Umsetzung tat meine eigenen Response.Listener und Response.ErrorListener in meinem Fragment oncreate (statt der StepA();) und es funktionierte !!!

Unten ist der Code-Snippet für das:

JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, mBuilder.getURL("box"), (String) null, new Response.Listener<JSONObject>() { 
      @Override 
      public void onResponse(JSONObject response) { 
       parseJsonFeed(response); 
      } 
     }, new Response.ErrorListener() { 
      @Override 
      public void onErrorResponse(VolleyError error) { 
       Toast.makeText(getActivity(), error.toString(), Toast.LENGTH_LONG).show(); 
      } 
     }); 
     requestQueue.add(request); 

So ist meine Frage, warum es nicht funktioniert, wenn ich die Anfrage zukünftige Methoden implementieren?

Wenn Sie mich fragen, warum ich für die synchrone Volley-Implementierung gehen möchte; Es ist, weil ich danach zwei weitere Volley-Anfragen haben muss, die davon abhängen, dass diese Anfrage vollständig, erfolgreich abgeschlossen ist. Und auch ich lerne :)

+0

kann mir bitte jemand hier helfen? :) – rapidclock

Antwort

12

Sad, dass niemand könnte helfen, diese Frage zu beantworten, aber ich schaffte dieses Problem wie unten zu lösen:

Der Timeout zum RequestFuture.get passieren wird(), wenn es im selben Thread wie der UI-Thread. Ich habe den Mechanismus der Anforderung geändert, so dass der Antrag auf einem separaten Asynch Gewinden (nicht UI Gewinde), und die Antwort empfangen wird, auch auf einem separaten Faden von der Anfrage, wie weiter unten ausgeführt wird:

private void StepA() { 
     Log.d("RT", "StepA initiated"); 
     final CountDownLatch latchA = new CountDownLatch(1); 

     Thread t = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       Log.d("RT", "Thread t Begins"); 
       ThreadA threadA = new ThreadA(); 
       try { 
        JSONObject jsonObject = threadA.execute().get(10, TimeUnit.SECONDS); 
        parseA(jsonObject); 
        latchA.countDown(); 
        Log.d("RT", "Thread t Ends"); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } catch (ExecutionException e) { 
        e.printStackTrace(); 
       } catch (TimeoutException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
     t.start(); 
     try { 
      latchA.await(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     Log.d("RT", "StepA END"); 
    } 

Unten ist die asynch Aufgabe Code für die Anforderung:

protected class ThreadA extends AsyncTask<Void, Void, JSONObject> { 
    final String url = mBuilder.getURL("box"); 

    public ThreadA() { 
    } 

    @Override 
    protected JSONObject doInBackground(Void... params) { 
     final RequestFuture<JSONObject> future = RequestFuture.newFuture(); 
     JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, (String) null, future, future); 
     requestQueue.add(request); 
     try { 
      return future.get(10, TimeUnit.SECONDS); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } catch (ExecutionException e) { 
      e.printStackTrace(); 
     } catch (TimeoutException e) { 
      e.printStackTrace(); 
     } 
     return null; 
    } 
} 

I Countdown hinzugefügt haben die Riegel, weil sie genial sind und auch, weil ich in meinem Programm wie dieses einige weitere Wünsche haben, die auf dieser Schnipsel Antwort abhängen. Daher helfen sie, das Programm synchroner zu betreiben.

+0

Danke. Es ist eigentlich nicht so "überraschend", da dies ein allgemeiner Fehler ist, aber ich dachte, ich könnte das Async vermeiden, indem ich mit der Zukunft gehe, wenn klar ich nicht kann. Die Botschaft von Volley könnte jedoch klarer sein als eine einfache "Zeitaufnahme". – Pelpotronic

+0

Das ist eine wahnsinnig nervige Sache, auf die man stoßen kann! Vielen Dank. – JoeHz

+0

Danke :). Das hat mich zwei Stunden gebraucht. – fjc

0

rapidclocks Antwort ist in Ordnung. Persönlich bevorzuge ich einen IntentService, weil sie so wunderbar sind.Auch Google empfiehlt es: https://www.youtube.com/watch?v=xHXn3Kg2IQE&t=1852s

hier ist mein IntentService:

// http://stackoverflow.com/questions/30549268/android-volley-timeout-exception-when-using-requestfuture-get 
//http://afzaln.com/volley/com/android/volley/toolbox/RequestFuture.html 
//http://stackoverflow.com/questions/36735682/android-synchronizing-methods-across-processes/36737001#36737001 
// http://stackoverflow.com/questions/16904741/can-i-do-a-synchronous-request-with-volley 
package org.peacekeeper.service; 

import android.app.IntentService; 
import android.content.Intent; 
import android.os.*; 

import com.android.volley.RequestQueue; 

import org.json.JSONObject; 
import org.peacekeeper.app.R; 
import org.peacekeeper.service.pkRequest.pkURL; 
import org.peacekeeper.util.*; 
import org.slf4j.*; 

import java.util.concurrent.*; 


/** 
Asynchronously handles an intent using a worker thread. Receives a ResultReceiver object and a 
location through an intent. Tries to fetch the address for the location using a Geocoder, and 
sends the result to the ResultReceiver. 
*/ 
public class RESTIntentService extends IntentService{ 
//begin static 
//Intent putextra ID's 
static public final String 
     RECEIVER = "RESTIntentServiceRCVR", 
     JSONResult = "JSONResult", 
     REQUEST = "RESTIntentServiceRequest"; 
protected final static pkUtility mUtility  = pkUtility.getInstance(); 
protected final static RequestQueue mRequestQueue = mUtility.getRequestQueue(); 
private final static long   TIMEOUT  = 5; 

//end static 
private static final Logger mLog = LoggerFactory.getLogger(RESTIntentService.class); 
//The receiver where results are forwarded from this service. 
private ResultReceiver mReceiver; 

//This constructor is required, and calls the super IntentService(String) constructor with the name for a worker thread. 
public RESTIntentService(){ super("RESTIntentService"); } 


@Override protected void onHandleIntent(Intent intent){ 
    String errorMessage = ""; 

    mReceiver = intent.getParcelableExtra(RECEIVER); 

    if (mReceiver == null){// Check if receiver was properly registered. 
     mLog.error("No RESTIntentService receiver received. There is nowhere to send the results."); 
     return; 
    } 


    // Get the pkRequest passed to this service through an extra. 
    pkRequest.pkURL URL = pkURL.valueOf(intent.getStringExtra(REQUEST)); 
    mLog.debug("RESTIntentService URL: " + URL.toString()); 
    // Make sure that the location data was really sent over through an extra. If it wasn't, 
    // send an error message and return. 
    if (URL == null){ 
     errorMessage = getString(R.string.no_pkRequest_provided); 
     mLog.error(errorMessage); 
     deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage); 
     return; 
    } 


    //Request retval = null; 
    JSONObject response = null; 

    pkRequest request = new pkRequest(URL); 
    mLog.debug("onHandleIntent:\n" + request.toString()); 

    request.submit(); 

    try{ 
     //while (!request.mFuture.isDone()) {;} 
// TODO THIS BLOCKS the service but not the main UI thread. Consider wrapping in an asynch task: 
// see http://stackoverflow.com/questions/30549268/android-volley-timeout-exception-when-using-requestfuture-get 
     response = request.mFuture.get(TIMEOUT, TimeUnit.SECONDS); 

     mLog.debug("onHandleIntent:\n" + response.toString()); 

    }catch (InterruptedException | ExecutionException | TimeoutException x){ 
     errorMessage = getString(R.string.failed_future_request); 
     mLog.error(errorMessage, x); 
     x.printStackTrace(); 
    } 

    if (errorMessage.isEmpty()){ 
     deliverResultToReceiver(Constants.SUCCESS_RESULT, 
           response.toString()); 
    } 
    else{ deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage); } 
}//onHandleIntent() 

// Sends a resultCode and message to the receiver. 
private void deliverResultToReceiver(int resultCode, String message){ 
    Bundle bundle = new Bundle(); 
    bundle.putString(JSONResult, message); 
    mReceiver.send(resultCode, bundle); 
} 
}//class RESTIntentService 

Der Nachteil eines IntentService zu verwenden ist, dass es (aber nicht die Haupt-UI-Thread) wird von der future.get blockiert werden (.. .). (Siehe Kommentar im Code re: future.get Block) Wenn Sie also REST-Aufrufe ausführen, dann sollten Sie es vielleicht noch verwenden und Ihre Aufrufe in einem Async-Format umwandeln, wie es von rapidclock empfohlen wird.

die oben IntentService Um dieses in der Haupt-UI (oder wo auch immer):

protected void startRESTService(final pkRequest.pkURL aURL){ 
    // Start the service. If the service isn't already running, it is instantiated and started 
    // (creating a process for it if needed); if it is running then it remains running. The 
    // service kills itself automatically once all intents are processed. 

    startService(
      new Intent(this, RESTIntentService.class) 
        .putExtra(RESTIntentService.RECEIVER, mRESTResultReceiver) 
        .putExtra(RESTIntentService.REQUEST, aURL.name()) 
       ); 
}//startRESTService() 

//Receiver for data sent from RESTIntentService. 
class RESTResultReceiver extends ResultReceiver{ 
    public RESTResultReceiver(Handler handler){ super(handler); } 

    //Receives data sent from RESTIntentService and updates the UI in MainActivity. 
    @Override protected void onReceiveResult(int resultCode, Bundle resultData){ 
     String snippet = resultData.getString(RESTIntentService.JSONResult); 
     mLog.debug("RESTResultReceiver:\t" + snippet); 

    }//onReceiveResult 
}//class RESTResultReceiver 

oh gut ... hier ist meine Tätigkeit (bitte nicht ding mich tun für allzu detailliert zu sein ... I Liebe Liebe Liebe STACKOVERFLOW aber keine gute Tat geht unbestraft ....):

//http://stackoverflow.com/questions/34582370/how-can-i-show-current-location-on-a-google-map-on-android-marshmallow/34582595#34582595 
package org.peacekeeper.app; 

import android.Manifest; 
import android.content.*; 
import android.content.pm.PackageManager; 
import android.location.Location; 
import android.os.*; 
import android.support.v4.app.ActivityCompat; 
import android.support.v4.content.ContextCompat; 
import android.support.v7.app.*; 
import android.widget.Toast; 

import com.google.android.gms.common.ConnectionResult; 
import com.google.android.gms.common.api.GoogleApiClient; 
import com.google.android.gms.location.*; 
import com.google.android.gms.maps.*; 
import com.google.android.gms.maps.GoogleMap.*; 
import com.google.android.gms.maps.model.*; 
import com.google.android.gms.maps.model.Marker; 

import org.json.JSONObject; 
import org.peacekeeper.rest.LinkedRequest; 
import org.peacekeeper.service.*; 
import org.peacekeeper.service.pkRequest.pkURL; 
import org.peacekeeper.util.pkUtility; 
import org.slf4j.*; 

import ch.qos.logback.classic.LoggerContext; 
import ch.qos.logback.classic.util.ContextInitializer; 
import ch.qos.logback.core.joran.spi.JoranException; 

public class actGeocoder extends AppCompatActivity 
     implements OnMapReadyCallback, 
        GoogleApiClient.ConnectionCallbacks, 
        GoogleApiClient.OnConnectionFailedListener, 
        LocationListener, 
        OnMapLongClickListener, 
        OnMarkerClickListener{ 

//begin static 
private static final LoggerContext mLoggerContext = 
     (LoggerContext) LoggerFactory.getILoggerFactory(); 
private static final ContextInitializer mContextInitializer = 
     new ContextInitializer(mLoggerContext); 
private static final Logger mLog = LoggerFactory.getLogger(actGeocoder.class); 

private static final int MY_PERMISSIONS_REQUEST_LOCATION = 99; 
//end static 


private GoogleMap mGoogleMap; 
private SupportMapFragment mapFrag; 
private LocationRequest mLocationRequest; 
private GoogleApiClient mGoogleApiClient; 
private MarkerOptions mMarkerOptions; 
private Marker mMarker; 
private AddressResultReceiver mResultReceiver = new AddressResultReceiver(new Handler()); 
private RESTResultReceiver mRESTResultReceiver = new RESTResultReceiver(new Handler()); 
private pkUtility mUtility; 

public void newPeaceKeeperStatus(){ 
    startRESTService(pkRequest.pkURL.status); 
} 




@Override protected void onCreate(Bundle savedInstanceState){ 
    super.onCreate(savedInstanceState); 
    mUtility = pkUtility.getInstance(this); 
    newPeaceKeeperStatus(); 
    setContentView(R.layout.geocoder); 

    getSupportActionBar().setTitle(R.string.RegisterYourLocn); 
    buildGoogleApiClient(); 
    mapFrag = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.geocoder); 
    mapFrag.getMapAsync(this); 
}//onCreate 


@Override public void onResume(){ 
    super.onResume(); 
    mGoogleApiClient.connect(); 
} 


@Override protected void onRestart(){ 
    super.onRestart(); 
    // Reload Logback log: http://stackoverflow.com/questions/3803184/setting-logback-appender-path-programmatically/3810936#3810936 
    mLoggerContext.reset(); 

    //I prefer autoConfig() over JoranConfigurator.doConfigure() so I don't need to find the file myself. 
    try{ mContextInitializer.autoConfig(); } 
    catch (JoranException X){ X.printStackTrace(); } 
}//onRestart() 

@Override protected void onStop(){ 
    mGoogleApiClient.disconnect(); 
    mLoggerContext.stop();//flush log 
    super.onStop(); 
} 

@Override public void onDestroy(){ 
    mLog.trace("onDestroy():\t"); 
    mLoggerContext.stop();//flush log 
    super.onDestroy(); 
} 

@Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){ 
    switch (requestCode){ 
    case MY_PERMISSIONS_REQUEST_LOCATION:{ 
     // If request is cancelled, the result arrays are empty. 
     if (grantResults.length > 0 
      && grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED){ 

      // permission was granted, yay! Do the location-related task you need to do. 
      if (ContextCompat.checkSelfPermission(this, 
                Manifest.permission.ACCESS_FINE_LOCATION) 
       == PackageManager.PERMISSION_GRANTED){ 

       if (mGoogleApiClient == null){ buildGoogleApiClient(); } 
       mGoogleMap.setMyLocationEnabled(true); 
      } 

     } 
      // permission denied. Disable the functionality that depends on this permission. 
     else{ Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show(); } 
     return; 
    } 

    }//switch 
} 

protected synchronized void buildGoogleApiClient(){ 
    mGoogleApiClient = new GoogleApiClient.Builder(this) 
      .addConnectionCallbacks(this) 
      .addOnConnectionFailedListener(this) 
      .addApi(LocationServices.API) 
      .build(); 

    mGoogleApiClient.connect(); 
} 

//http://stackoverflow.com/questions/31328143/android-google-maps-onmapready-store-googlemap 
@Override public void onMapReady(GoogleMap googleMap){ 
    //Initialize Google Play Services 
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ 
     if (ContextCompat.checkSelfPermission(this, 
               Manifest.permission.ACCESS_FINE_LOCATION) 
      != PackageManager.PERMISSION_GRANTED){ 
      //Location Permission already granted 
      checkLocationPermission(); 
      return; //Request Location Permission 
     } 

    } 

    mGoogleMap = googleMap; 
    mGoogleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); 

    mGoogleMap.setOnMapLongClickListener(this); 
    mGoogleMap.setOnMarkerClickListener(this); 

    mMarkerOptions = new MarkerOptions() 
      .title("Tap this marker again to register your location") 
      .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)); 
} 



private void checkLocationPermission(){ 
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 
     != PackageManager.PERMISSION_GRANTED){ 

     // Should we show an explanation? 
     if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
                    Manifest.permission.ACCESS_FINE_LOCATION)){ 

// Show an explanation to the user *asynchronously* -- don't block this thread waiting for the user's response! 
// After the user sees the explanation, try again to request the permission. 
      new AlertDialog.Builder(this) 
        .setTitle("Location Permission Needed") 
        .setMessage(
          "This app needs the Location permission, please accept to use location functionality") 
        .setPositiveButton("OK", new DialogInterface.OnClickListener(){ 
         @Override public void onClick(DialogInterface dialogInterface, int i){ 
          //Prompt the user once explanation has been shown 
          ActivityCompat.requestPermissions(actGeocoder.this, 
                   new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, 
                   MY_PERMISSIONS_REQUEST_LOCATION); 
         } 
        }) 
        .create() 
        .show();  } 
     else{ // No explanation needed, we can request the permission. 
      ActivityCompat.requestPermissions(this, 
               new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, 
               MY_PERMISSIONS_REQUEST_LOCATION); 
     } 
    } 
} 

@Override public void onConnected(Bundle bundle){ 
    mLocationRequest = new LocationRequest() 
      .setInterval(1000) 
      .setFastestInterval(1000) 
      .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); 

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 
     == PackageManager.PERMISSION_GRANTED){ 
        LocationServices.FusedLocationApi. 
        requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); 
    } 
} 


private final static float ZOOM = 18; 
@Override public void onLocationChanged(Location location){//this is called only once on startup. 
    //stop location updates since only current location is needed 
    LocationServices.FusedLocationApi 
      .removeLocationUpdates(mGoogleApiClient, this); 

    LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); 
    mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM)); 

    onMapLongClick(latLng); 
} 


@Override public void onMapLongClick(final LatLng latLng){ 
    startIntentService(latLng); 

    if (mMarker != null) mMarker.remove(); 

    mMarkerOptions.position(latLng); 
    mMarker = mGoogleMap.addMarker(mMarkerOptions); 
}//onMapLongClick 

@Override public boolean onMarkerClick(Marker marker) { 
    startActivity(
      new Intent(this, actRegistration.class) 
        .putExtra(FetchAddressIntentService.LOCATION, marker.getSnippet()) 
        .putExtra(FetchAddressIntentService.LATLNG, marker.getPosition()) 


       ); 
    return true; 
}//onMarkerClick 


protected void startIntentService(final LatLng latLng){ 
    // Start the service. If the service isn't already running, it is instantiated and started 
    // (creating a process for it if needed); if it is running then it remains running. The 
    // service kills itself automatically once all intents are processed. 
    startService(
      new Intent(this, FetchAddressIntentService.class) 
        .putExtra(FetchAddressIntentService.RECEIVER, mResultReceiver) 
        .putExtra(FetchAddressIntentService.LATLNG, latLng) 
       ); 
}//startIntentService() 

protected void startRESTService(final pkRequest.pkURL aURL){ 
    // Start the service. If the service isn't already running, it is instantiated and started 
    // (creating a process for it if needed); if it is running then it remains running. The 
    // service kills itself automatically once all intents are processed. 

    startService(
      new Intent(this, RESTIntentService.class) 
        .putExtra(RESTIntentService.RECEIVER, mRESTResultReceiver) 
        .putExtra(RESTIntentService.REQUEST, aURL.name()) 
       ); 
}//startRESTService() 



//Receiver for data sent from FetchAddressIntentService. 
class AddressResultReceiver extends ResultReceiver{ 
    public AddressResultReceiver(Handler handler){ super(handler); } 

    //Receives data sent from FetchAddressIntentService and updates the UI in MainActivity. 
    @Override protected void onReceiveResult(int resultCode, Bundle resultData){ 
     mMarker.setSnippet(resultData.getString(FetchAddressIntentService.LOCATION)); 
     mMarker.showInfoWindow(); 
    }//onReceiveResult 
}//class AddressResultReceiver 

//Receiver for data sent from RESTIntentService. 
class RESTResultReceiver extends ResultReceiver{ 
    public RESTResultReceiver(Handler handler){ super(handler); } 

    //Receives data sent from RESTIntentService and updates the UI in MainActivity. 
    @Override protected void onReceiveResult(int resultCode, Bundle resultData){ 
     String snippet = resultData.getString(RESTIntentService.JSONResult); 
     mLog.debug("RESTResultReceiver:\t" + snippet); 
    }//onReceiveResult 
}//class RESTResultReceiver 


@Override public void onConnectionSuspended(int i){ mLog.info("onConnectionSuspended: " + i );} 
@Override public void onConnectionFailed(ConnectionResult connectionResult){ 
    mLog.error(R.string.GoogleApiClientConnFailed + ":\t" + connectionResult.getErrorMessage()); 
    Toast.makeText(this, R.string.GoogleApiClientConnFailed, Toast.LENGTH_LONG).show(); 
} 
}//class actGeocoder