在我的片段中,我正在尝试使用TMDB的开放电影数据库来获取有关"正在播放"影。
如果我使用RequestFuture.get(time,TimeUnit)方法来执行此排球请求,我总是会收到超时错误。如果我在Safari中手动测试相同的Url,我会立即得到结果。
我所知道的:
1。)这不是任何JSON解析错误。(程序甚至没有进入解析步骤)
2。)AVD没有互联网问题。 (后面解释的原因)。
3.。)我的凌空单件类或我的请求队列不是问题。 (理由后来解释)。
所以我假设我在排球/请求未来的使用方面犯了另一个错误。
以下片段代码:
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&#34; RT&#34;:
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
在使用RequestFuture方法之前,我基本上在我的Fragment oncreate(而不是StepA();)中实现了我自己的Response.Listener和Response.ErrorListener,并且它已经工作了!!!
以下是代码摘要:
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);
所以我的问题是,当我实现请求未来方法时,为什么它不起作用?
如果你问我为什么要进行同步截击实施;这是因为在此之后我必须再提出两个完全成功完成的请求。而且我也在学习:)。
答案 0 :(得分:16)
很遗憾没有人可以帮助回答这个问题,但我设法解决了这个问题,如下所示:
如果RequestFuture.get()与UI线程在同一个线程上,则会发生超时。我已经更改了请求的机制,以便请求在单独的Asynch线程(而不是UI线程)上完成,并且响应也在请求的单独线程上接收,如下所示:
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");
}
以下是请求的Asynch任务代码:
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;
}
}
我添加了倒计时锁存器,因为它们非常棒,而且我的程序中依赖于此片段的响应,我还有更多这样的请求。因此,它们可以帮助更加同步地运行程序。
答案 1 :(得分:0)
rapidclock的答案很好。我个人更喜欢使用IntentService,因为它们太棒了。谷歌也建议: https://www.youtube.com/watch?v=xHXn3Kg2IQE&t=1852s
这是我的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
使用IntentService的缺点是future.get(...)将阻止IT(但不是主UI线程)。 (请参阅代码re:future.get块中的注释)因此,如果您正在对它进行REST调用,那么您可能会考虑仍然使用它并将您的调用包装在rapidclock建议的异步中。
要使用上面的IntentService,请将其放在主UI(或任何)中:
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
好吧......好吧......这是我的活动(请不要因为过于详细而告诉我......我喜欢爱情STACKOVERFLOW但没有好事不受惩罚......):
//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