Android volley Excepción de tiempo de espera al utilizar RequestFuture.get ()

En mi Fragmento, estoy tratando de usar la base de datos de películas abiertas de TMDB para obtener detalles acerca de las películas "Now Playing".

Si utilizo el método RequestFuture.get (time, TimeUnit) para ejecutar esta solicitud de volley siempre obtengo un error de tiempo de espera. Si pruebo manualmente la misma URL en Safari, obtengo los resultados instantáneamente.

Lo que yo sé:

1.) No es ningún error de análisis de JSON (el programa no progresa hasta los pasos de análisis)

2.) No hay problemas de Internet con AVD. (La razón se explica más adelante).

3.) No es un problema con mi clase de volley singleton o mi cola de solicitudes. (Razón explicada más adelante).

Así que estoy suponiendo que estoy haciendo algún otro tipo de error con respecto a los usos de volley / Solicitar Futuro.

Fragmento Código abajo:

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 con el filtro para la etiqueta "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 

Antes de usar los métodos de RequestFuture, básicamente hice lo mismo implementando mi propio Response.Listener y Response.ErrorListener en mi fragmento oncreate (en lugar de StepA ();) y funcionó !!!

A continuación se muestra el código-fragmento para eso:

 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); 

Así que mi pregunta es ¿por qué no funciona cuando implemento los métodos de solicitud de futuro?

Si usted me pregunta por qué quiero ir para la aplicación de volea sincrónica; Es porque después de esto tengo que tener dos solicitudes de volea más que dependen de esta solicitud se completa, completado con éxito. Y también estoy aprendiendo 🙂

Triste que nadie podría ayudar a responder a esta pregunta, pero he logrado resolver este problema como a continuación:

El tiempo de espera pasará a RequestFuture.get () si está en el mismo hilo que el subproceso de interfaz de usuario. He cambiado el mecanismo de la solicitud para que la solicitud se realiza en un hilo de Asynch separado (no el hilo de interfaz de usuario) y la respuesta también se recibe en un hilo independiente de la solicitud, como a continuación:

 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"); } 

A continuación se muestra el código de tarea Asynch para la solicitud:

 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; } } 

He añadido cerraduras de cuenta regresiva porque son impresionantes y también porque tengo pocas solicitudes más como esta en mi programa que dependen de la respuesta de este fragmento. Por lo tanto, ayudan a ejecutar el programa de forma más sincronizada.

La respuesta de rapidclock está bien. Personalmente prefiero usar un IntentService porque son tan maravillosos. Google también lo recomienda: https://www.youtube.com/watch?v=xHXn3Kg2IQE&t=1852s

Aquí está mi 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 

La desventaja de utilizar un IntentService es que la TI (pero no el hilo principal de la interfaz de usuario) será bloqueada por el future.get (…). (Vea el comentario en el código re: bloque future.get) Así que si estás volando llamadas REST en él, entonces usted podría considerar todavía utilizarlo y envolver sus llamadas en un async como recomendado por rapidclock.

Para utilizar el IntentService anterior, póngalo en la interfaz de usuario principal (o donde sea):

 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 bien … aquí está mi actividad (por favor, no me hagas por ser demasiado detallado … Me encanta el amor amor STACKOVERFLOW pero ninguna buena acción queda impune ….):

 //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 
  • Exportar aplicación de Android con ProGuard da error de ParseException
  • ¿Por qué algunos códigos de Android tienen campos públicos?
  • Cambiar la intensidad de clarear / oscurecer en mapas de bits mediante PorterDuffXfermode en la clase Paint de Android
  • Pasando enActividadResultado en Córdoba
  • Cómo deshacerse de la advertencia: Utilice '$' en lugar de '.' Para clases internas en Eclipse
  • Hacer clic en el elemento giratorio dentro de la ventana emergente causa WindowManager $ BadTokenException
  • Uso de OAuth con Scribe en Android
  • Clasificación de Fecha ArrayList
  • Desactivar la sobrecarga agresiva en ProGuard para Android
  • Cómo encubrir el tiempo que tarda la respuesta a venir desde el servidor en android sin mostrar la pantalla en negro?
  • Facebook Login no funciona correctamente (Parse)
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.