Android+オレオレ証明書=通信不能

WebViewを使用したAndroidアプリを製作し、信頼できない証明書(いわゆるオレオレ証明書)を使用したWebサイトにアクセスすると、頻繁にリンクをタップしたときに通信不能になってしまった。放っておくと約10分で復活するけど・・・。
頻繁というのはリンクをタップした後、ロードの完了を待たずに別(同じでもいいかも)のリンクをタップするくらいの感じ。
ちなみに WebViewClient の onReceivedSslError() はオーバーライドして、

public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
    handler.proceed() ;
}

な感じで全部もみ消すように実装している。

この現象の詳細を追ってみた。環境はFroyo。
あ、回避策はわかりませんでした。誰か教えてください。(ちゃんとした証明書使えという以外で ^^;)

andoid.net.http, android.webkit 辺りの実装概要

  • HTTP通信は http0〜http3 という4本のスレッドで行われる
  • スレッドクラスは android.net.http.ConnectionThread
  • HTTP通信のリクエストを保持するキューとキューに溜まったリクエストを http0〜3 で処理する実装
  • 最後のほうにクラス図(抜粋)載せてます

通信不能時の状態

waitしている場所

android.net.http.HttpsConnection.openConnection() の抜粋

01: sslSock = (SSLSocket) getSocketFactory().createSocket();
02: sslSock.connect(new InetSocketAddress(mHost.getHostName(), mHost.getPort()));
03: 
04: SslError error = CertificateChainValidator.getInstance().
05:          doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
06: 
07: if (error != null) { // ←オレオレ証明書はエラーとなるのでここはtrue
08:     synchronized (mSuspendLock) {
09:         mSuspended = true;
10:     }
11:     boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
12:     synchronized (mSuspendLock) {
13:         if (mSuspended) {
14:                 mSuspendLock.wait(10 * 60 * 1000);

通信不能時のhttpxスレッドはこの14行目のwaitでwaitしている。

waitに至る流れ

mSuspneded を false に変更するのは HttpsConnection.restartConnection() のみ。このwaitから復帰させるための mSuspendLock.notify() を呼ぶのも HttpsConnection.restartConnection() 内のみ。

つまり、waitしぱなっしにならないためには、muSuspened が9行目で true に設定された後、別スレッドでrestartConnection()が呼ばれるか、11行目の req.getEventHandler().handleSslErrorRequest(error) の中で restartConnection() がよばれる必要がある。

restartConnection() を呼ぶのは HttpsConnection.closeConnection() と Request.handleSslErrorResponse(boolean) のみ。

ロードが正常に完了する時のrestartConnection()の呼び出しスタックはこんな感じ。

01: HttpsConnection.restartConnection(boolean) line: 409
02: HttpsConnection(Request).handleSslErrorResponse(boolean) line: 519
03: RequestHandle.handleSslErrorResponse(boolean) line: 119
04: LoadListener.handleSslErrorResponse(boolean) line: 881
05: SslErrorHandler.handleSslErrorResponse(LoadListener, SslError, boolean) line: 270
06: SslErrorHandler.checkSslPrefTable(LoadListener, SslError) line: 165
07: Network.checkSslPrefTable(LoadListener, SslError) line: 336
08: LoadListener.handleSslErrorRequest(SslError) line: 814
09: HttpsConnection.openConnection(Request) line: 325
10: HttpsConnection(Connection).openHttpConnection(Request) line: 407
11: HttpsConnection(Connection).processRequests(Request) line: 260
12: ConnectionThread.run() line: 134

ところが、別スレッドからロードが既にキャンセルされてしまっていた場合は5行目の SslErrorHandler.handleSslErrorResponse() の中が以下のようになっているので、これ以降のメソッドは呼び出されず、restartConnection()が呼ばれることはない。

01: synchronized void handleSslErrorResponse(・・・) {
02:     if (!loader.cancelled()) { // ←ロードが既にキャンセルされていればスキップ
03:         ・・・
04:         loader.handleSslErrorResponse(proceed);
05:         ・・・
06:     }
07: }

では、キャンセルの処理の中でrestartConnection()が呼ばれるかというと・・・呼ばれません! キャンセル処理の中ではcloseConnection()が以下のように呼ばれる。

01: HttpsConnection.closeConnection() line: 385
02: HttpsConnection(Connection).cancel() line: 159
03: Request.cancel() line: 384
04: RequestHandle.cancel() line: 99
05: LoadListener.cancel() line: 1297
06: JWebCoreJavaBridge.sharedTimerFired() line: not available [native method]
07: JWebCoreJavaBridge.fireSharedTimer() line: 88
08: JWebCoreJavaBridge.handleMessage(Message) line: 105
09: JWebCoreJavaBridge(Handler).dispatchMessage(Message) line: 99
10: Looper.loop() line: 123
11: WebViewCore$WebCoreThread.run() line: 626
12: Thread.run() line: 1096

が、closeConnection()の処理は

01: void closeConnection() {
02:     ・・・
03:     if (mSuspended) {
04:         restartConnection(false);
05:     ・・・
06: }

となっていて、mSuspended が true になる前に呼ばれても restartConnection() は呼ばれず mSuspended も false にはならない。

つまり、上記HttpsConnection.openConnection() の2行目辺りで通信中にユーザーが何かリンクをタップすると、そのスレッドは10分間のwaitに入ってしまい、使用不能となる。これが、http0〜3全てのスレッドで起きるとリンクをタップしても通信しないと言う症状が発生する。

図示してみる

  • 関与しているクラスのクラス図(抜粋)


  • ロードが正常に完了する場合のシーケンス図(抜粋)


  • httpxスレッドが10分間waitになってしまう場合のシーケンス図