前の記事に書いた通り、Android 版の Web 表示器を作った時には、エラー処理のためにアプリを開発しました。

特に重要なエラー処理は「タイムアウト」の処理です。工場向けの表示器としては、通信異常が発生したからといって30秒も画面が止まってしまうようでは使い物になりません。

shouldInterceptRequest

WebView のエラー処理を実装する際には WebViewClient.shouldInterceptRequest をオーバーライドするのが最も柔軟な方法です。

以下にコードを示します。この記事に紹介するコードはアプリを書いた当時のものでかなり古いのですが、今でも基本的な考え方は変わっていないようです。

webView.setWebViewClient(new WebViewClient() {
	@Override
	public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
		// url が HTTP だった場合に、自分で通信してレスポンスを作る
		if (url.matches("https?://[¥¥w¥¥.¥¥-]+(/.*)?")) {
			// 通信の準備
			HttpGet req = new HttpGet(url);
			HttpParams params = req.getParams();
			// タイムアウトを設定する
			HttpConnectionParams.setConnectionTimeout(params, 1000);
			HttpConnectionParams.setSoTimeout(params, 2000);
			DefaultHttpClient client = new DefaultHttpClient();
			try {
				// 通信開始
				HttpResponse res = client.execute(req);
				// ステータスコードを確認
				if (HttpStatus.SC_OK != res.getStatusLine().getStatusCode()) {
					throw new Exception();
				}
				// 正常な応答を記憶しておく
				HttpEntity entity = res.getEntity();
				String mimeType = null, encoding = null;
				Header mimeHeader = entity.getContentType();
				if (null != mimeHeader) mimeType = mimeHeader.getValue();
				Header encodingHeader = entity.getContentEncoding();
				if (null != encodingHeader) encoding = encodingHeader.getValue();
				byte[] data = EntityUtils.toByteArray(entity);
				_lastGootResponse = new WebResponseArchive();
				_lastGootResponse.data = data;
				_lastGootResponse.mime = mimeType;
				_lastGootResponse.encoding = encoding;
				_lastGootResponse.url = url;
				_errCount = 0;
			} catch (Exception e) {
				// 正常な応答ではなかった場合の処理
				_errCount++;
				// 次回のリロード時間を1秒後に設定する
			} finally {
				// 通信を終了する
				req.abort();
				client.getConnectionManager().shutdown();
			}
			// 必要に応じて正常応答記録を削除する
			if (null != _lastGootResponse) {
				if (_errCount >= 3 || !url.equals(_lastGootResponse.url)) {
					_lastGootResponse = null;
				}
			}
			// 正常応答記録がなければ空のリソースを返して onReceivedError を発生させる
			if (null == _lastGootResponse) {
				return new WebResourceResponse(null, null, null);
			}
			// 正常な応答(またはキャッシュ応答)を返す
			WebResponseArchive a = _lastGootResponse;
			return new WebResourceResponse(a.mime, a.encoding, new ByteArrayInputStream(a.data));
		}
		// url が HTTP ではなかった場合の処理
		_lastGootResponse = null;
		return super.shouldInterceptRequest(view, url);
	}
	@Override
	public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
		// エラー画面表示
		// 自動回復のためのリロード処理を仕掛ける
	}
});

エラー発生時の処理

上記のコード例は、接続のタイムアウトが1秒に設定されています。タイムアウトが発生した場合には「次回のリロード時間を1秒後に設定する」処理が実行されます。この時、WebView には正常な応答(キャッシュ応答)が返されます。WebView でエラーが発生するのは内部のエラーが3回発生した以降です。

実際のアプリでもほぼコード例の通り、エラーが発生してもすぐにエラー画面にはならないようになっています。他にもいくつかの工夫により、表示器アプリは「安定した表示」「エラーに対する高速な応答」「自動回復」を実現しています。

ところで、表示器のために作ったはずの表示器アプリですが、工場ではもう少し別の用途でも使われています。次の記事では、表示器アプリの活用事例を紹介してみたいと思います。

この記事の投稿者

崎 洋佑
崎 洋佑
さきラボの代表取締役。
自称プログラマーもどき。よく使う言語は日本語。
IT系の技術者ですが、コンピューターよりも人が好きです。