Crash-fix-2:org.springframework.http.converter.HttpMessageNotReadableException

最近開始對APP上的Crash進行對應,發現有好多常見的問題,同一個問題在多個APP都類似的出現了,這裏記錄下這些常見的錯誤。
crash Log:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 425727 path $.shops[631].lineup; nested exception is com.google.gson.ad: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 425727 path $.shops[631].lineup
    at 包名.k.readInternal(SourceFile:75)
    at org.springframework.http.converter.AbstractHttpMessageConverter.read(SourceFile:147)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(SourceFile:76)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(SourceFile:655)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(SourceFile:641)
    at org.springframework.web.client.RestTemplate.doExecute(SourceFile:484)
    at org.springframework.web.client.RestTemplate.execute(SourceFile:439)
    at org.springframework.web.client.RestTemplate.exchange(SourceFile:415)

根據錯誤log的意思,應該是服務器(php開發)返回了非正常的json格式錯誤信息導致app崩潰。

項目背景:
項目是使用AA框架開發的,Api請求使用的是SpringRestTemplate,使用Gson進行json與Bean的轉換

爲了解決Gson在Android6.0上的bug,自定義了一個GsonConverter,繼承自GsonHttpMessageConverter。在數據轉換時添加了log,主要代碼如下:

 @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        String str = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())));
        LogUtil.d("in =" + str);
        try {
            Type typeOfT = getType();

            if (typeOfT != null) {
                return this.gson.fromJson(str, typeOfT);
            } else {
                return this.gson.fromJson(str, clazz);
            }
        } catch (JsonSyntaxException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (JsonIOException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (Exception ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

然後就是AA中Rest的配置了,將自定義的GsonConverter配置到Rest上。
在每個請求中都設置了RestErrorHandler,單純的log出數據,並沒有業務邏輯

mClient.setRestErrorHandler(handler);
  @Override
    public void onRestClientExceptionThrown(RestClientException e) {
        LogUtil.e(e);
    }

根據CrashLog,定位到問題是Api返回的數據轉換成Bean出錯導致的,代碼定位到了GsonConverter.readInternal方法,通常來說方法上已經聲明瞭錯誤類型了,按照業務邏輯拋出指定的錯誤類型不應該導致App崩潰,應該是回調RestErrorHandler的方法纔對的。但是根據實際測試下來和猜想的還是有很大的區別。

然後抽取一個Api,代碼如下:


        ResponseEntity<CheckVersionResponse> entity = apiHelper.checkVersion();

        if (null == entity || !entity.hasBody()) {
            return;
        }

如果在GsonConverter.readInternal中拋出異常,則App崩潰。如果在以上代碼中添加TryCatch,則可以捕獲到異常。這個就好奇了,怎麼是直接拋出異常,而不會回調異常處理接口。如果是這麼修改的話,整個系統幾十個接口都需要修改,工程量太大而且太傻。
解決辦法:
既然拋出異常會導致崩潰,那麼當Api轉換錯誤時,數據返回null不就可以了。修改後的代碼:

 @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        String str = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())));
        LogUtil.d("in =" + str);
        try {
            Type typeOfT = getType();

            if (typeOfT != null) {
                return this.gson.fromJson(str, typeOfT);
            } else {
                return this.gson.fromJson(str, clazz);
            }
        } catch (JsonSyntaxException ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (JsonIOException ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (Exception ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
        return null;
    }

原因分析:
解決辦法找到了,深究下定位個原因,AA框架自動生成的ApiClient源碼:


    @Override
    public ResponseEntity<CheckVersionResponse> checkVersion(Map<String, Object> params) {
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<Map<String, Object>>(params);
        try {
            return restTemplate.exchange(rootUrl.concat("/checkVersion/"), HttpMethod.POST, requestEntity, CheckVersionResponse.class);
        } catch (RestClientException e) {
            if (restErrorHandler!= null) {
                restErrorHandler.onRestClientExceptionThrown(e);
                return null;
            } else {
                throw e;
            }
        }
    }

從這裏可以看出,只有RestClientException類型纔會回調異常回調接口,其他的錯誤只會直接拋出。

然而HttpMessageNotReadableException不是RestClientException類型的,所以異常就直接拋出,沒有被捕獲當然就導致APP崩潰了。

發佈了157 篇原創文章 · 獲贊 92 · 訪問量 58萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章