You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:01:57