OkHTTP、Retrofit 中文乱码解决方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHTTP、Retrofit 中文乱码解决方法相关的知识,希望对你有一定的参考价值。

参考技术A 出现乱码的根本原因是客户端、服务端两端编码格式不一致导致的。

客户端:多数情况下,客户端的编码格式是 UTF-8。
服务端:服务端会根据不同的请求方法使用不同的编码格式。如:请求方法为 POST 时,编码格式为 UTF-8;请求方法为 GET 时,编码格式为 ISO8859-1。

当请求方法为 POST 时,客户端和服务端两边的编码格式一致,所以不存在乱码问题。因此此处着重看下如何解决当请求方法为 GET 时的乱码问题。

解决方法倒也简单,只不过需要客户端和服务端配合:

在向 URL 添加参数之前,先对目标参数进行两次 encode,如 UTF-8:

服务器在收到数据之后,只需将数据进行一次跟客户端编码格式一样的 decode,如 UTF-8:

这样处理之后,两边就不会再出现乱码了。

通过上面的分析可知,乱码产生的主要原因是客户端、服务器两边编码不一致造成的,即发送 GET 请求时,客户端使用的是 UTF-8 编码格式对 URL 中的参数进行编码,而服务器在接收数据的时候,使用的是 ISO8859-1(解析 POST 请求时,服务器使用的编码格式是 UTF-8 编码格式)编码格式对 URL 中的参数进行解码。

ISO8859-1 跟 ASCII 码一样,都是单字节编码,ISO8859-1 是从 ASCII 扩展而来的。ISO8859-1 将 ASCII 一个字节中剩余的最后一位用了起来,也就是说,它比 ASCII 多了 128 个字符。另外,因为 ISO8859-1 是从 ASCII 扩展而来的,所以,ISO8859-1 兼容 ASCII。

原数据:

客户端第一次编码,URLDecoder.decode(username, "UTF-8") 编码之后:

客户端第二次编码,URLDecoder.decode(username, "UTF-8") 编码之后:

客户端发出的 URL:

服务器接收的 URL:

服务器第一次解码,服务器接收到 GET 请求之后,默认会用 ISO8859-1 编码格式解码,解码之后得到:

需要注意的是,服务器用 ISO8859-1 编码格式解码 URL 中的参数是自动完成的。
因为客户端第一次用 URLDecoder.decode(username, "UTF-8") 编码 URL 中参数之后,得到的是 ASCII 码,且 UTF-8 和 ISO8859-1 对 ASCII 的编码结果是一致的,所以,客户端第二次用 URLDecoder.decode(username, "UTF-8") 之后的结果可以直接用 ISO8859-1 编码格式解码。
由于服务器解码之后的 URL 中的参数是用 UTF-8 编码格式编码的,所以,此时需要服务器再用 UTF-8 编码格式解码一次。

服务器第二次解码,服务器用 UTF-8 编码格式解码之后得到:

如果客户端程序员没有显式用 UTF-8 编码格式编码 URL 中的参数,服务端要如何处理才能获取到原数据?

首先,分析下如果客户端没有用 UTF-8 编码格式编码 URL 中的参数,程序是如何执行的:

网络请求框架会对 URL 中的参数进行一次 UTF-8 编码:

服务器会对 URL 中的参数进行一次 ISO8859-1 编码:

明白了执行流程之后,如何解决自然也就显而易见了:
先转回 ISO8859-1 解码(decode)之前的结果,再转会 UTF-8 编码(encode)之前的结果。

具体操作步骤:

因为 URL 中的参数经 UTF-8 编码格式编码之后得到的结果在 ISO8859-1 字符集可能一样也可能根本表示不了,这也是为什么 ASCII 码经 UTF-8 编码格式编码之后的结果可以用 ISO8859-1 编码格式解码。如,在 Unicode 字符集中,第 20013 个字符是“中”,而在 ISO8859-1 字符集中,一共才有 256 个字符。字符“中”经 UTF-8 编码之后的结果再经 ISO8859-1 解码,无论如何也得不到正确答案的。

Retrofit/OkHTTP/RxJava 间歇性 InterruptedIOException

【中文标题】Retrofit/OkHTTP/RxJava 间歇性 InterruptedIOException【英文标题】:Retrofit/OkHTTP/RxJava intermittent InterruptedIOException 【发布时间】:2018-06-12 14:37:22 【问题描述】:

我正在使用以下(过时的)库:

Retrofit: 1.9.0
OkHTTP: 2.3.0
RxAndroid: 0.24.0

我注意到每隔一段时间我都会收到以下关于我的 POST 请求的堆栈跟踪:

 D/Retrofit: ---> HTTP POST https:xxxxx
 D/Retrofit: Content-Type: application/x-www-form-urlencoded; charset=UTF-8
 D/Retrofit: Content-Length: 396
 D/Retrofit: ---> END HTTP (396-byte body)
 D/Retrofit: ---- ERROR https:xxxxx
 I/Choreographer: Skipped 33 frames!  The application may be doing too much work on its main thread.
 D/Retrofit: java.io.InterruptedIOException
   at okio.Timeout.throwIfReached(Timeout.java:146)
   at okio.Okio$1.write(Okio.java:75)
   at okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
   at okio.RealBufferedSink.flush(RealBufferedSink.java:201)
   at com.squareup.okhttp.internal.http.HttpConnection.flush(HttpConnection.java:140)
   at com.squareup.okhttp.Connection.makeTunnel(Connection.java:399)
   at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:229)
   at com.squareup.okhttp.Connection.connect(Connection.java:159)
   at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:175)
   at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:120)
   at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:330)
   at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:319)
   at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
   at com.squareup.okhttp.Call.getResponse(Call.java:271)
   at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228)
   at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199)
   at com.squareup.okhttp.Call.execute(Call.java:79)
   at retrofit.client.OkClient.execute(OkClient.java:53)
   at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:326)
   at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
   at java.lang.reflect.Proxy.invoke(Proxy.java:913)
   at $Proxy1.replyTransaction(Unknown Source)
   < App Specific Trace > 
   at rx.Observable$1.call(Observable.java:145)
   at rx.Observable$1.call(Observable.java:137)
   at rx.Observable.unsafeSubscribe(Observable.java:7304)
   at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62)
   at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:47)
   at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
   at java.util.concurrent.FutureTask.run(FutureTask.java:266)
   at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
   at java.lang.Thread.run(Thread.java:764)
 D/Retrofit: ---- END ERROR
 retrofit.RetrofitError
     at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:395)
     at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
     at java.lang.reflect.Proxy.invoke(Proxy.java:913)
     at $Proxy1.replyTransaction(Unknown Source)
     < App Specific Trace > 
     at rx.Observable$1.call(Observable.java:145)
     at rx.Observable$1.call(Observable.java:137)
     at rx.Observable.unsafeSubscribe(Observable.java:7304)
     at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62)
     at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:47)
     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
     at java.lang.Thread.run(Thread.java:764)
  Caused by: java.io.InterruptedIOException
     at okio.Timeout.throwIfReached(Timeout.java:146)
     at okio.Okio$1.write(Okio.java:75)
     at okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
     at okio.RealBufferedSink.flush(RealBufferedSink.java:201)
     at com.squareup.okhttp.internal.http.HttpConnection.flush(HttpConnection.java:140)
     at com.squareup.okhttp.Connection.makeTunnel(Connection.java:399)
     at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:229)
     at com.squareup.okhttp.Connection.connect(Connection.java:159)
     at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:175)
     at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:120)
     at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:330)
     at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:319)
     at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
     at com.squareup.okhttp.Call.getResponse(Call.java:271)
     at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228)
     at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199)
     at com.squareup.okhttp.Call.execute(Call.java:79)
     at retrofit.client.OkClient.execute(OkClient.java:53)
     at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:326)
     at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240) 
     at java.lang.reflect.Proxy.invoke(Proxy.java:913) 
     at $Proxy1.replyTransaction(Unknown Source) 
     < App Specific Trace > 
     at rx.Observable$1.call(Observable.java:145) 
     at rx.Observable$1.call(Observable.java:137) 
     at rx.Observable.unsafeSubscribe(Observable.java:7304) 
     at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) 
     at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:47) 
     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457) 
     at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
     at java.lang.Thread.run(Thread.java:764)

我的代码如下:

网络调用:

    return Observable.create(new Observable.OnSubscribe<Object>() 
      @Override public void call(Subscriber<? super Object> subscriber) 
        try 
          subscriber.onNext(/* Sync network call here. */);
         catch (Exception e) 
          subscriber.onError(e);
        
        subscriber.onCompleted();
      
    )
        .onBackpressureBuffer()
        // singleton for Schedulers.io()
        .subscribeOn(ioScheduler)
        // singleton for AndroidSchedulers.mainThread()
        .observeOn(mainThreadScheduler);

这样称呼:

subscription = makeNetworkCall()
            .subscribe(new Observer<Object>() 
              @Override public void onNext(Object object) 
                // close and finish activity
              
              @Override public void onError(Throwable e) 
                // whoops!
              
            );

并且在 Activity 中被取消订阅,如下所示:

  @Override
  protected void onPause() 
    super.onPause();
    if (subscription != null) 
      subscription.unsubscribe();
      subscription = null;
      if (condition) 
        finish();
      
    
  

我对此的理解是我得到了一个InterruptedException,因为我是来自Subscription 中的unsubscribe()ing onPause()。我们会为其他请求执行此操作,所以我不确定为什么我只在这里看到它以及为什么会发生这种情况。

为了提供更多关于这方面的信息,网络调用发生在一个从锁定屏幕启动的 Activity 中,通过通知操作强制用户解锁他们的设备以采取行动。

我的问题是为什么会发生这种情况,是否有补救的好方法?根据一些文章,onPause() 是您应该取消订阅Observables 的地方。

更多信息请见In okHTTP's github issues

谢谢!

【问题讨论】:

当心你破坏了Observable Contract。如果您调用onError,您不得调用onCompleted 【参考方案1】:

这是一个猜测,但可能是您在发出中断的异常之前没有检查订阅者是否仍然存在。

试试这个:

return Observable.create(new Observable.OnSubscribe<Object>() 
      @Override public void call(Subscriber<? super Object> subscriber) 
        try 
          subscriber.onNext(/* Sync network call here. */);
          subscriber.onCompleted();
         catch (Exception e) 
          if (!subscriber.isUnsubscribed())  // <-- check before emitting error
            subscriber.onError(e);
          
        
      
    )

【讨论】:

【参考方案2】:

取消订阅后,链不再需要做任何工作,因为我们不再关心输出。我们可以让当前的操作完成,并在到达下一个操作员时干净地停止工作,但是为什么要浪费我们不需要的资源呢?如果有正在进行的网络请求,我们想取消它。我们通过中断线程来做到这一点。如果/当此线程上的代码检查它是否被中断时,则不需要做更多的工作,并且可以抛出InterruptedIOException

取决于调用取消订阅的确切时间,您可能会或可能不会看到错误。如果在请求开始加载之前或完成之后调用取消订阅,那么您可能不会看到此错误。

所以这个异常的原因是 OkHttp 一旦知道你对它的结果不感兴趣就想放弃,而 RxJava 不想吞下它,以防它对你很重要。

您可以添加一个默认错误处理程序来抑制无法交付的InterruptedExceptions 和InterruptedIOExceptions,它们很少有用。见RxJava 2 Error Handling,我相信RxJava 1 也有类似的结构,但你应该考虑迁移到RxJava 2。其实差别不大,RxBinding 已经更新到RxJava 2。

onPause() 不一定是您应该取消订阅的位置。您应该在您的应用程序逻辑需要的地方以及可以防止内存泄漏的地方取消订阅。

【讨论】:

【参考方案3】:

试试这个:

return Observable.create(e -> 
           try 
               e.onNext(...);
            catch (Exception ex) 
               e.tryOnError(ex);
           
           e.onComplete();
       );

tryOnError() 在发送错误时检查 Observer 是否被释放。

参考:https://github.com/ReactiveX/RxJava/issues/4863

【讨论】:

以上是关于OkHTTP、Retrofit 中文乱码解决方法的主要内容,如果未能解决你的问题,请参考以下文章

retrofit: Expected Android API level 21+ but was 19

Android OkHttp + Retrofit 取消请求的方法

使用 retrofit2 和 Okhttp 获取 Api

Retrofit2 OkHttp3 响应正文空错误

Android OkHttp + Retrofit 取消请求的方法

OkHttp,Retrofit 1.x - 2.x 基本使用