Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败

Posted

技术标签:

【中文标题】Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败【英文标题】:HttpURLConnnection request failures on Android 6.0 (MarshMallow) 【发布时间】:2016-01-22 04:34:07 【问题描述】:

我在我的应用程序项目中使用 Google 的 Volley 库,目标是最低 api 级别 14。因此,Volley 库使用 HttpURLConnection 作为 NetworkClient。

因此,应该不会有任何与移除 Apache HTTPClient 相关的问题。但是,我已经完成了 6.0 Sdk 所需的配置,即 compileSdkVersion 23、build-tool-version 23.0.1 和 build:gradle:1.3.1',甚至尝试添加 useLibrary 'org.apache.http.legacy'。在我的应用程序中为 Volley 库项目更新了相同的内容。

最近,我尝试在 android 6.0 (MarshMallow) 上运行我的应用程序,我的项目编译并运行。但是那些需要身份验证标头的请求在 MarshMallow 上失败了:

BasicNetwork.performRequest:意外响应代码 401 com.android.volley.AuthFailureError

但是在所有低于 23 的 Api 级别上运行相同。

我已经检查了很多次标头。奇怪的是,那些不需要身份验证的请求给出的响应是 200 OK。

现在我完全不知道是什么破坏了请求,有没有人知道新版本中 HttpURLConnection 请求仅针对 Api 级别 23 失败的变化?还有其他人在使用 Volley 并面临类似问题吗?

这是我的 CustomRequest 类

public class CustomRequest extends Request<Void> 

int id, cmd;
Map<String, String> params;
BaseModel model;

public CustomRequest(int method, int cmd, String url, Map<String, String> params, int id, BaseModel model) 
    super(method, url, null);
    this.id = id;
    this.cmd = cmd;
    this.params = params;
    this.model = model;

    if (method == Method.GET) 
        setUrl(buildUrlForGetRequest(url));
    

    Log.v("Volley", "Making request to: " + getUrl());


private String buildUrlForGetRequest(String url) 
    if (params == null || params.size() == 0) return url;

    StringBuilder newUrl = new StringBuilder(url);
    Set<Entry<String, String>> paramPairs = params.entrySet();
    Iterator<Entry<String, String>> iter = paramPairs.iterator();

    newUrl.append("?");
    while (iter.hasNext()) 
        Entry<String, String> param = iter.next();
        newUrl
            .append(param.getKey())
            .append("=")
            .append(param.getValue());
        if (iter.hasNext()) newUrl.append("&");
    

    return newUrl.toString();


@Override
public Map<String, String> getHeaders() throws AuthFailureError 
    Map<String, String> headers = new HashMap<String, String>();

    headers.put("X-Api-Version", Contract.API_VERSION);
    headers.put("X-Client", "android");
    String accessToken = APP.getInstance().getToken();
    if (!accessToken.equals("")) 
        headers.put("Authorization", "Bearer " + accessToken);    
    

    return headers;


@Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) 
    Exception ex;
    try 
        String jsonString = new String(
                response.data, HttpHeaderParser.parseCharset(response.headers));
        JsonNode json = new ObjectMapper().readTree(jsonString);
        if (model != null) model.parse(id, json);
        EventBus.getDefault().post(new EventResponse(cmd, true, null));
        return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); //Doesn't return anything. BaseModel.parse() does all the storage work.
     catch (NoMoreDataException e) 
        ex = e;
        EventBus.getDefault().post(new NoMoreDataModel(cmd, e));
        EventBus.getDefault().post(new EventResponse(cmd, false, null));
        return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
     catch (Exception e) 
        ex = e;
        Log.e("CustomRequest", Log.getStackTraceString(e));

        String message = APP.getInstance().getString(R.string.failedRequest);
        if (!TextUtils.isEmpty(e.getMessage()))
            message = e.getMessage();
        EventBus.getDefault().post(new ErrorEventModel(cmd, message, e));
        return Response.error(new ParseError(ex));
    


@Override
protected Map<String, String> getParams() throws AuthFailureError 
    return params;


@Override
protected void deliverResponse(Void response) 
    Log.v("Volley", "Delivering result: " + getUrl());


@Override
public void deliverError(VolleyError error) 
    Log.e("CustomRequest", "Delivering error: Request=" + getUrl() + " | Error=" + error.toString());

    String message = APP.getInstance().getString(R.string.failedRequest);
    EventBus.getDefault().post(new ErrorEventModel(cmd, message, error));

我发现 Api 23 和其他版本之间的唯一区别是 HostNameVerifier。 对于 Api 级别 23:com.android.okhttp.internal.tls.OkHostnameVerifier 对于 API 级别

【问题讨论】:

如果可用,请发布您的代码和 logcat 信息。 对不起,我的声誉不允许我发布更多代码。Logcat 仅显示上述代码 sn-p 错误。请看我的发现,这可能是问题的原因。 我认为您可以尝试硬编码访问令牌(您可以使用 Postman 获取新令牌,然后粘贴到您的代码中)。我的带有不记名令牌的 volley 项目在 API23 模拟器中工作。确保您有一个有效/未过期的令牌 :) 尝试过硬编码。没有运气:(毕竟这些请求都在 23 以下的 api 上工作,并且相同的代码库和令牌被正确读取。 您可以控制服务器端应用程序吗?如果是,请尝试调试它。是 Asp.Net Web API 吗? 【参考方案1】:

在检查了我的应用程序的服务器端后,找到了问题背后的原因。 对于 Android 6.0(MarshMallow),标题变为空,而其他版本则不是这样。

所以对我有用的修复:

创建并添加了一个新的自定义标头 X-Proxy-No-Redirect => 1 并与其他标头一起传递。

背后的理论:

我们发送请求到一个 API 服务器,然后 API 服务器根据 oAuth 令牌将请求重定向到相应的站点 重定向时 为了实现重定向,有两种方法可以做到这一点

1 - 我们只是将响应发送回调用者,说明重定向到某个页面。然后调用者(网络库)负责重定向

2 - API 服务器将自己发出重定向请求并获取响应,然后传递给调用者

之前我们使用的是方法 1。

在 Android 6.0 上 - 网络库 (Volley) 在发出重定向请求时似乎没有设置所有标头。 一旦设置了这个新的标头,方法二就会生效。

P.S此修复适用于我的应用程序项目,可能不适用于其他人。仅提供错误和对我有帮助的地方。

【讨论】:

以上是关于Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败的主要内容,如果未能解决你的问题,请参考以下文章

Android 6.0 (MarshMallow) 上的 HttpURLConnnection 请求失败

如何在 Android 6.0 Marshmallow 中访问相机?

如何以编程方式打开 Android 6.0 (Marshmallow) 上特定应用的权限屏幕?

Android 6.0 Marshmallow BLE 连接问题

何时在运行时请求 Android Marshmallow 6.0 的权限?

在 Android 6.0 Marshmallow (API 23) 上不推荐使用 getColor(int id)