Android 使用 RequestFuture.get() 时截击超时异常
Android volley Timeout Exception when using RequestFuture.get()
在我的片段中,我正在尝试使用 TMDB 的开放电影数据库来获取有关 "Now Playing" 电影的详细信息。
如果我使用 RequestFuture.get(time, TimeUnit) 方法来执行此 volley 请求,我总是会收到超时错误。如果我在 Safari 中手动测试相同的 Url,我会立即得到结果。
我知道的:
1.) 这不是任何 JSON 解析错误。(程序甚至没有进行到解析步骤)
2.) AVD 没有互联网问题。 (原因稍后解释)。
3.) 我的 volley 单例 class 或我的请求队列不是问题。 (原因稍后解释)。
所以我假设我在使用 volley/Request Future 时犯了一些其他错误。
片段代码如下:
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 使用标签过滤器 "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
在使用 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);
所以我的问题是为什么当我实现请求的未来方法时它不起作用?
如果你问我为什么要进行同步排球实施;这是因为在这之后我必须有两个截击请求,这取决于这个请求是否完全、成功完成。而且我也在学习:)
遗憾的是没有人可以帮助回答这个问题,但我设法解决了这个问题,如下所示:
如果 RequestFuture.get() 与 UI 线程在同一线程上,则会发生超时。我已经更改了请求的机制,以便请求在单独的异步线程(不是 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");
}
下面是请求的异步任务代码:
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;
}
}
我添加了倒计时锁存器,因为它们很棒,而且我的程序中很少有像这样的请求依赖于此代码段的响应。因此,它们可以更同步地帮助 运行 程序。
rapidclock 的回答很好。我个人更喜欢使用 IntentService,因为它们非常棒。另外 Google 推荐它:
https://www.youtube.com/watch?v=xHXn3Kg2IQE&t=1852s
这是我的 IntentService:
//
//http://afzaln.com/volley/com/android/volley/toolbox/RequestFuture.html
//
//
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
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 的缺点是 IT(但不是主 UI 线程)将被 future.get(...) 阻塞。 (请参阅代码回复中的评论: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
哦好吧...这是我的activity(请不要因为我过于详细而对我说...我爱爱爱爱 Whosebug 但好事必有报应....):
//
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:
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();
}
//
@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
在我的片段中,我正在尝试使用 TMDB 的开放电影数据库来获取有关 "Now Playing" 电影的详细信息。
如果我使用 RequestFuture.get(time, TimeUnit) 方法来执行此 volley 请求,我总是会收到超时错误。如果我在 Safari 中手动测试相同的 Url,我会立即得到结果。
我知道的:
1.) 这不是任何 JSON 解析错误。(程序甚至没有进行到解析步骤)
2.) AVD 没有互联网问题。 (原因稍后解释)。
3.) 我的 volley 单例 class 或我的请求队列不是问题。 (原因稍后解释)。
所以我假设我在使用 volley/Request Future 时犯了一些其他错误。
片段代码如下:
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 使用标签过滤器 "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
在使用 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);
所以我的问题是为什么当我实现请求的未来方法时它不起作用?
如果你问我为什么要进行同步排球实施;这是因为在这之后我必须有两个截击请求,这取决于这个请求是否完全、成功完成。而且我也在学习:)
遗憾的是没有人可以帮助回答这个问题,但我设法解决了这个问题,如下所示:
如果 RequestFuture.get() 与 UI 线程在同一线程上,则会发生超时。我已经更改了请求的机制,以便请求在单独的异步线程(不是 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");
}
下面是请求的异步任务代码:
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;
}
}
我添加了倒计时锁存器,因为它们很棒,而且我的程序中很少有像这样的请求依赖于此代码段的响应。因此,它们可以更同步地帮助 运行 程序。
rapidclock 的回答很好。我个人更喜欢使用 IntentService,因为它们非常棒。另外 Google 推荐它: https://www.youtube.com/watch?v=xHXn3Kg2IQE&t=1852s
这是我的 IntentService:
//
//http://afzaln.com/volley/com/android/volley/toolbox/RequestFuture.html
//
//
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
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 的缺点是 IT(但不是主 UI 线程)将被 future.get(...) 阻塞。 (请参阅代码回复中的评论: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
哦好吧...这是我的activity(请不要因为我过于详细而对我说...我爱爱爱爱 Whosebug 但好事必有报应....):
//
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:
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();
}
//
@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