Проблема с Android Java UTF-8 HttpClient

У меня странные проблемы с кодировкой символов в массиве JSON, который извлекается с веб-страницы. Сервер отправляет обратно этот заголовок:

Content-Type text / javascript; кодировка =UTF-8

Также я могу посмотреть на вывод JSON в Firefox или любом браузере, и символы Юникода отображаются правильно. Ответ иногда будет содержать слова из другого языка с символами ударения и тому подобное. Однако я получаю эти странные знаки вопроса, когда я опускаю их и помещаю в строку в Java. Вот мой код:

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "utf-8");
params.setBooleanParameter("http.protocol.expect-continue", false);

HttpClient httpclient = new DefaultHttpClient(params);

HttpGet httpget = new HttpGet("http://www.example.com/json_array.php");
HttpResponse response;
    try {
        response = httpclient.execute(httpget);

        if(response.getStatusLine().getStatusCode() == 200){
            // Connection was established. Get the content. 

            HttpEntity entity = response.getEntity();
            // If the response does not enclose an entity, there is no need
            // to worry about connection release

            if (entity != null) {
                // A Simple JSON Response Read
                InputStream instream = entity.getContent();
                String jsonText = convertStreamToString(instream);

                Toast.makeText(getApplicationContext(), "Response: "+jsonText, Toast.LENGTH_LONG).show();

            }

        }


    } catch (MalformedURLException e) {
        Toast.makeText(getApplicationContext(), "ERROR: Malformed URL - "+e.getMessage(), Toast.LENGTH_LONG).show();
        e.printStackTrace();
    } catch (IOException e) {
        Toast.makeText(getApplicationContext(), "ERROR: IO Exception - "+e.getMessage(), Toast.LENGTH_LONG).show();
        e.printStackTrace();
    } catch (JSONException e) {
        Toast.makeText(getApplicationContext(), "ERROR: JSON - "+e.getMessage(), Toast.LENGTH_LONG).show();
        e.printStackTrace();
    }

private static String convertStreamToString(InputStream is) {
    /*
     * To convert the InputStream to String we use the BufferedReader.readLine()
     * method. We iterate until the BufferedReader return null which means
     * there's no more data to read. Each line will appended to a StringBuilder
     * and returned as String.
     */
    BufferedReader reader;
    try {
        reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
    } catch (UnsupportedEncodingException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    StringBuilder sb = new StringBuilder();

    String line;
    try {
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

Как вы видите, я указываю UTF-8 в InputStreamReader, но каждый раз, когда я просматриваю возвращенный текст JSON через Toast, у него появляются странные знаки вопроса. Я думаю, что вместо этого мне нужно отправить InputStream в byte[]?

Заранее благодарю за любую помощь.

5 ответов

Решение

Попробуй это:

if (entity != null) {
    // A Simple JSON Response Read
    // InputStream instream = entity.getContent();
    // String jsonText = convertStreamToString(instream);

    String jsonText = EntityUtils.toString(entity, HTTP.UTF_8);

    // ... toast code here
}

@ Ответ Архимеда - решение. Но я не вижу ничего, что явно не так с вашей convertStreamToString код.

Мои догадки:

  1. Сервер устанавливает метку порядка байтов UTF (BOM) в начале потока. Стандартный символьный декодер Java UTF-8 не удаляет спецификацию, поэтому есть вероятность, что она окажется в результирующей строке. (Тем не менее, код для EntityUtils, похоже, тоже ничего не делает с BOM.)
  2. Ваш convertStreamToString читает символьный поток по очереди и собирает его с помощью аппаратного '\n' как маркер конца строки. Если вы собираетесь записать это во внешний файл или приложение, вам, вероятно, следует использовать специфический для платформы маркер конца строки.

Просто ваш convertStreamToString не поддерживает кодировку, установленную в HttpRespnose. Если вы посмотрите внутрь EntityUtils.toString(entity, HTTP.UTF_8), вы увидите, что EntityUtils узнает, есть ли в HttpResponse сначала кодировка, а затем, если она есть, EntityUtils использует эту кодировку. Он вернется к кодировке, переданной в параметре (в данном случае HTTP.UTF_8), если в объекте не задана кодировка.

Таким образом, вы можете сказать, что ваш HTTP.UTF_8 передается в параметре, но он никогда не используется, потому что это неправильная кодировка. Итак, вот обновление вашего кода с помощью вспомогательного метода из EntityUtils.

           HttpEntity entity = response.getEntity();
           String charset = getContentCharSet(entity);
           InputStream instream = entity.getContent();
           String jsonText = convertStreamToString(instream,charset);

    private static String getContentCharSet(final HttpEntity entity) throws ParseException {
    if (entity == null) {
        throw new IllegalArgumentException("HTTP entity may not be null");
    }
    String charset = null;
    if (entity.getContentType() != null) {
        HeaderElement values[] = entity.getContentType().getElements();
        if (values.length > 0) {
            NameValuePair param = values[0].getParameterByName("charset");
            if (param != null) {
                charset = param.getValue();
            }
        }
    }
    return TextUtils.isEmpty(charset) ? HTTP.UTF_8 : charset;
}



private static String convertStreamToString(InputStream is, String encoding) {
    /*
     * To convert the InputStream to String we use the
     * BufferedReader.readLine() method. We iterate until the BufferedReader
     * return null which means there's no more data to read. Each line will
     * appended to a StringBuilder and returned as String.
     */
    BufferedReader reader;
    try {
        reader = new BufferedReader(new InputStreamReader(is, encoding));
    } catch (UnsupportedEncodingException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    StringBuilder sb = new StringBuilder();

    String line;
    try {
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

Извлеките кодировку из поля типа содержимого ответа. Вы можете использовать следующий метод для этого:

private static String extractCharsetFromContentType(String contentType) {
    if (TextUtils.isEmpty(contentType)) return null;

    Pattern p = Pattern.compile(".*charset=([^\\s^;^,]+)");
    Matcher m = p.matcher(contentType);

    if (m.find()) {
        try {
            return m.group(1);
        } catch (Exception e) {
            return null;
        }
    }

    return null;
}

Затем используйте извлеченную кодировку для создания InputStreamReader:

String charsetName = extractCharsetFromContentType(connection.getContentType());

InputStreamReader inReader = (TextUtils.isEmpty(charsetName) ? new InputStreamReader(inputStream) :
                    new InputStreamReader(inputStream, charsetName));
            BufferedReader reader = new BufferedReader(inReader);

Архимед ответ правильный. Однако это можно сделать, просто предоставив дополнительный заголовок в HTTP-запросе:

Accept-charset: utf-8

Не нужно ничего удалять или использовать любую другую библиотеку.

Например,

GET / HTTP/1.1
Host: www.website.com
Connection: close
Accept: text/html
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.10 Safari/537.36
DNT: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: utf-8

Скорее всего, ваш запрос не имеет Accept-Charset заголовок.

Другие вопросы по тегам