Volley在Lollipop设备上报400 Bad Request,Postman可正常请求
解决Volley GET请求在Android Lollipop中返回400 Bad Request的问题
嘿,看起来你遇到了一个典型的Android版本兼容性问题——Volley的GET请求在Nougat、Oreo这些高版本系统正常运行,到Lollipop(API 21-22)就触发400错误,而且Postman调用API完全正常。我帮你梳理几个最可能的原因和针对性的解决办法:
1. 优先排查:URL参数未编码导致的解析失败
你的代码里直接把SharedPref中的值和变量手动拼接进URL,但很多参数(比如userName、userRank这类可能包含空格、特殊字符的字段)没有做URL编码。高版本Android的Volley或系统网络层可能自动补全了编码,但Lollipop的网络组件不会这么做;而Postman会自动帮你处理参数编码,所以调用正常。
解决办法:改用Uri.Builder构建URL
Uri.Builder会自动帮你处理所有参数的URL编码,避免手动拼接的错误:
private void proceedToCheakOut(){ String tag_string_req = "req_check_out_data"; // 用Uri.Builder安全构建带参数的URL Uri.Builder builder = Uri.parse(AppConfig.URL_CHECK_OUT).buildUpon(); builder.appendQueryParameter("userid", preferences.getString(SharedPref.KEY_USER_ID, "")); builder.appendQueryParameter("hotelid", preferences.getString(SharedPref.KEY_HOTEL_ID,"")); builder.appendQueryParameter("band", preferences.getString(SharedPref.KEY_BAND_ID, "")); builder.appendQueryParameter("area", preferences.getString(SharedPref.KEY_SEARCH_AREA, "")); builder.appendQueryParameter("check_in", preferences.getString(SharedPref.KEY_CHECK_IN, "")); builder.appendQueryParameter("check_out", preferences.getString(SharedPref.KEY_CHECK_OUT, "")); builder.appendQueryParameter("room", preferences.getString(SharedPref.KEY_ROOM, "")); builder.appendQueryParameter("adult", preferences.getString(SharedPref.KEY_ADULT, "")); builder.appendQueryParameter("above", preferences.getString(SharedPref.KEY_ABOVE_8, "")); builder.appendQueryParameter("below", preferences.getString(SharedPref.KEY_BELOW_8, "")); builder.appendQueryParameter("spouse", preferences.getString(SharedPref.KEY_SPOUSE, "")); builder.appendQueryParameter("days", preferences.getString(SharedPref.KEY_NO_OF_DAYS, "")); builder.appendQueryParameter("roomid", preferences.getString(SharedPref.KEY_ROOM_ID, "")); builder.appendQueryParameter(SharedPref.KEY_DEFENCE_ID, preferences.getString(SharedPref.KEY_DEFENCE_ID, "")); // 数值类型参数转字符串,避免格式问题 builder.appendQueryParameter("spousePrice", String.valueOf(spousePrice)); builder.appendQueryParameter("adultPrice", String.valueOf(adultPrice)); builder.appendQueryParameter("abovePrice", String.valueOf(abovePrice)); builder.appendQueryParameter("belowPrice", String.valueOf(belowPrice)); builder.appendQueryParameter("price", String.valueOf(price)); builder.appendQueryParameter("tax", String.valueOf(tax)); builder.appendQueryParameter("grandTotal", String.valueOf(grandTotal)); builder.appendQueryParameter("userName", adult1Data); builder.appendQueryParameter("userRank", RankData); builder.appendQueryParameter("adultList", adultsArray); builder.appendQueryParameter("aboveList", childaboveArray); builder.appendQueryParameter("belowList", childbelowArray); String uri = builder.build().toString(); Log.e("URI", uri); // 后续的StringRequest代码保持不变 StringRequest strReq = new StringRequest(Request.Method.GET, uri, new Response.Listener<String>() { @Override public void onResponse(String response) { try { JSONObject jObj = new JSONObject(response); boolean error = jObj.getBoolean("error"); if (!error) { Toast.makeText(CheckoutPersonalDetailActivity.this, "Booking Successful", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(CheckoutPersonalDetailActivity.this, BookingDoneActivity.class); intent.putExtra("grandTotal", grandTotal); intent.putExtra(SharedPref.KEY_BOOKING_ID, jObj.getString("bookingid")); intent.putExtra(SharedPref.KEY_BOOKING_DATE, jObj.getString("bookingdate")); intent.putExtra(SharedPref.KEY_BOOKING_STATUS, jObj.getString("bookingstatus")); intent.putExtra("reminderhour", jObj.getString("reminderhour")); intent.putExtra("HotelImageString", hotelImgString); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); String title = "Booking Pending"; String description = "Booking is Pending, Your Booking Id is: "+ jObj.getString("bookingid"); sendNotification(title, description, jObj.getString("bookingid")); } else { Toast.makeText(CheckoutPersonalDetailActivity.this,jObj.getString("error_msg"), Toast.LENGTH_LONG).show(); } } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if (error == null || error.networkResponse == null) { return; } String body; final String statusCode = String.valueOf(error.networkResponse.statusCode); try { body = new String(error.networkResponse.data,"UTF-8"); Toast.makeText(CheckoutPersonalDetailActivity.this, "Error: "+body, Toast.LENGTH_LONG).show(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }); AppController.getInstance().addToRequestQueue(strReq, tag_string_req); }
2. 备选方案:强制启用TLS 1.2解决协议兼容性
Lollipop默认不强制启用TLS 1.2,如果你的服务器要求使用TLS 1.2+的安全协议,就会出现请求被拒绝的情况。可以通过自定义Volley的HurlStack来强制启用TLS 1.2:
步骤1:创建自定义SSLSocketFactory
public class TLSSocketFactory extends SSLSocketFactory { private SSLSocketFactory internalSSLSocketFactory; public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(null, null, null); internalSSLSocketFactory = context.getSocketFactory(); } @Override public String[] getDefaultCipherSuites() { return internalSSLSocketFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return internalSSLSocketFactory.getSupportedCipherSuites(); } @Override public Socket createSocket() throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); } private Socket enableTLSOnSocket(Socket socket) { if(socket instanceof SSLSocket) { ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"}); } return socket; } }
步骤2:修改AppController的RequestQueue初始化
public class AppController extends Application { private static AppController instance; private RequestQueue requestQueue; public static synchronized AppController getInstance() { return instance; } @Override public void onCreate() { super.onCreate(); instance = this; requestQueue = getRequestQueue(); } public RequestQueue getRequestQueue() { if (requestQueue == null) { try { TLSSocketFactory tlsSocketFactory = new TLSSocketFactory(); HurlStack stack = new HurlStack(null, tlsSocketFactory); requestQueue = Volley.newRequestQueue(getApplicationContext(), stack); } catch (KeyManagementException | NoSuchAlgorithmException e) { e.printStackTrace(); // 异常时 fallback 到默认队列 requestQueue = Volley.newRequestQueue(getApplicationContext()); } } return requestQueue; } public <T> void addToRequestQueue(Request<T> req, String tag) { req.setTag(TextUtils.isEmpty(tag) ? "DEFAULT_TAG" : tag); getRequestQueue().add(req); } }
3. 额外检查:请求头是否匹配Postman
400错误也可能是请求头不一致导致的。Postman会自动添加一些默认请求头,而Volley的默认请求头在Lollipop上可能有所差异。你可以在StringRequest中添加和Postman一致的请求头:
StringRequest strReq = new StringRequest(Request.Method.GET, uri, new Response.Listener<String>() { ... }, new Response.ErrorListener() { ... }) { @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<>(); headers.put("Accept", "application/json"); headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"); // 复制Postman中的其他请求头到这里 return headers; } };
建议先尝试第一个方案(URL编码),这是此类问题最常见的诱因。如果解决了问题,就不需要再折腾其他方案了。
内容的提问来源于stack exchange,提问作者Hitesh Kushwah
相关产品推荐
相关产品推荐

