はじめに (対象読者・この記事でわかること)
この記事は、Androidアプリ開発者、特にWebViewを利用したハイブリッドアプリ開発に携わっている方を対象としています。WebViewでWebコンテンツを表示する際に、通信エラーやページの読み込み状態を適切に処理したいというニーズに応える内容です。
この記事を読むことで、WebViewでHTTPステータスコードを取得する具体的な方法、エラーハンドリングの実装方法、そしてそれらを活用したユーザー体験の向上策を理解できます。また、実装中によく遭遇する問題とその解決策も学べるため、開発効率の向上が期待できます。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 前提となる知識1: Javaの基本的な文法とオブジェクト指向の理解 前提となる知識2: Android Studioの基本的な操作とプロジェクト構造の理解 前提となる知識3: WebViewの基本的な使い方(XMLでの配置とJavaでの初期化)
WebViewとステータスコードの基礎知識
WebViewはAndroidアプリ内でWebページを表示するためのコンポーネントです。Webアプリとネイティブアプリの両方の特性を持つハイブリッドアプリ開発において不可欠な要素です。しかし、単純にWebページを表示するだけでなく、通信の成否やページの状態を適切に処理することは、ユーザー体験を大きく左右します。
HTTPステータスコードは、Webサーバーからクライアントへのレスポンスの状態を示す3桁の数字です。主な種類として、成功を示す200番台、リクエストエラーを示す400番台、サーバーエラーを示す500番台などがあります。特に、404(ページが見つからない)や500(サーバーエラー)といったエラーステータスコードを検知し、適切な対応を取ることは、アプリの信頼性を高める上で重要です。
WebViewでは、WebResourceRequestやWebResourceResponseを通じてこれらのステータスコードにアクセスできます。しかし、単にコードを取得するだけでなく、それに基づいて適切なエラーハンドリングを実装する必要があります。この記事では、その具体的な実装方法をステップバイステップで解説します。
WebViewでステータスコードを取得・活用する実装方法
ステップ1: WebViewの基本設定
まずはWebViewをアプリに組み込む基本的な設定から始めましょう。activity_main.xmlにWebViewを追加し、MainActivity.javaで初期化します。
Xml<!-- activity_main.xml --> <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" />
Java// MainActivity.java public class MainActivity extends AppCompatActivity { private WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webview); // WebView設定 WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); // JavaScriptからの呼び出しを許可 webView.addJavascriptInterface(new WebAppInterface(), "Android"); // WebViewClientを設定 webView.setWebViewClient(new CustomWebViewClient()); // Webページの読み込み webView.loadUrl("https://example.com"); } // 戻るボタンの処理 @Override public void onBackPressed() { if (webView.canGoBack()) { webView.goBack(); } else { super.onBackPressed(); } } // JavaScriptインターフェースのクラス public class WebAppInterface { @JavascriptInterface public void showToast(String toast) { Toast.makeText(getApplicationContext(), toast, Toast.LENGTH_SHORT).show(); } } }
この段階では、WebViewは単純にWebページを表示するだけです。次に、ステータスコードを取得するためのWebViewClientをカスタマイズします。
ステップ2: WebViewClientのカスタマイズとステータスコードの取得
WebViewClientを継承したクラスを作成し、shouldOverrideUrlLoadingメソッドとonReceivedHttpErrorメソッドをオーバーライドします。これにより、ページの読み込み中に発生するHTTPエラーを検知できます。
Java// CustomWebViewClient.java public class CustomWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { // URLをオーバーライドする必要がない場合はfalseを返す return false; } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { // HTTPエラーが発生した場合の処理 int statusCode = errorResponse.getStatusCode(); String responseMessage = errorResponse.getReasonPhrase(); // ステータスコードに基づいた処理 handleHttpError(statusCode, responseMessage); super.onReceivedHttpError(view, request, errorResponse); } private void handleHttpError(int statusCode, String message) { String errorMessage = ""; switch (statusCode) { case 400: errorMessage = "リクエストが不正です: " + message; break; case 401: errorMessage = "認証が必要です: " + message; break; case 403: errorMessage = "アクセスが拒否されました: " + message; break; case 404: errorMessage = "ページが見つかりません: " + message; break; case 500: errorMessage = "サーバーエラーが発生しました: " + message; break; case 503: errorMessage = "サービスが利用できません: " + message; break; default: errorMessage = "通信エラーが発生しました: " + statusCode + " " + message; break; } // エラーメッセージをログに出力 Log.e("WebViewError", errorMessage); // UIスレッドでエラーメッセージを表示 runOnUiThread(() -> { Toast.makeText(view.getContext(), errorMessage, Toast.LENGTH_LONG).show(); }); } }
このコードでは、onReceivedHttpErrorメソッドをオーバーライドすることで、HTTPエラーが発生した際にステータスコードとエラーメッセージを取得しています。handleHttpErrorメソッドでは、ステータスコードに基づいて適切なエラーメッセージを生成し、Toastで表示しています。
ステップ3: エラーページの表示
エラーが発生した際に、単にメッセージを表示するだけでなく、カスタムエラーページを表示する方法を実装します。MainActivity.javaに以下のメソッドを追加します。
Java// MainActivity.javaに追加 public void showErrorPage(String errorMessage) { String errorHtml = "<html><body style='text-align:center; padding-top:50px; font-family: sans-serif;'>" + "<h1>エラーが発生しました</h1>" + "<p style='color:red;'>" + errorMessage + "</p>" + "<button onclick='window.Android.reloadPage()' style='padding:10px 20px; background-color:#4CAF50; color:white; border:none; border-radius:4px; cursor:pointer;'>再読み込み</button>" + "</body></html>"; webView.loadDataWithBaseURL(null, errorHtml, "text/html", "UTF-8", null); } // WebAppInterfaceクラスに追加 @JavascriptInterface public void reloadPage() { webView.reload(); }
次に、CustomWebViewClient.javaのhandleHttpErrorメソッドを修正し、エラーページを表示するようにします。
Java// CustomWebViewClient.javaのhandleHttpErrorメソッドを修正 private void handleHttpError(int statusCode, String message, WebView view) { String errorMessage = ""; // ステータスコードに基づいたメッセージの設定(前述と同じ) // エラーページを表示 ((MainActivity) view.getContext()).showErrorPage(errorMessage); }
さらに、MainActivity.javaのWebViewClient設定部分を修正し、WebViewインスタンスを渡すようにします。
Java// MainActivity.javaのWebViewClient設定部分を修正 webView.setWebViewClient(new CustomWebViewClient(webView));
CustomWebViewClientのコンストラクタを追加し、WebViewインスタンスを受け取れるようにします。
Java// CustomWebViewClient.javaにコンストラクタを追加 public class CustomWebViewClient extends WebViewClient { private WebView webView; public CustomWebViewClient(WebView webView) { this.webView = webView; } // onReceivedHttpErrorメソッドの呼び出しを修正 @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { // ...(前述のコードと同じ) // handleHttpErrorの呼び出しを修正 handleHttpError(statusCode, responseMessage, view); super.onReceivedHttpError(view, request, errorResponse); } // handleHttpErrorメソッドのシグネチャを修正 private void handleHttpError(int statusCode, String message, WebView view) { // ...(前述のコードと同じ) } }
これで、エラーが発生した際にカスタムエラーページが表示され、再読み込みボタンからページを再読み込みできるようになります。
ステップ4: ローディング状態の表示
ページの読み込み中にプログレスバーを表示する方法を実装します。MainActivity.javaに以下のメソッドを追加します。
Java// MainActivity.javaに追加 private void showLoading() { runOnUiThread(() -> { ProgressBar progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge); progressBar.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); progressBar.setIndeterminate(true); webView.addView(progressBar); progressBar.setVisibility(View.VISIBLE); }); } private void hideLoading() { runOnUiThread(() -> { ViewGroup viewGroup = (ViewGroup) webView.getParent(); if (viewGroup != null) { View progressBar = viewGroup.getChildAt(viewGroup.getChildCount() - 1); if (progressBar instanceof ProgressBar) { viewGroup.removeView(progressBar); } } }); }
次に、WebViewClientのonPageStartedとonPageFinishedメソッドをオーバーライドして、ローディング状態を制御します。
Java// CustomWebViewClient.javaに追加 @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); ((MainActivity) view.getContext()).showLoading(); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); ((MainActivity) view.getContext()).hideLoading(); }
これで、ページの読み込み開始時にプログレスバーが表示され、読み込み完了時に非表示になります。
ステップ5: ネットワーク接続状態の監視
ネットワーク接続の状態を監視し、オフラインの場合に適切な処理を行う方法を実装します。まず、ネットワーク接続状態を監視するBroadcastReceiverを作成します。
Java// NetworkChangeReceiver.java public class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); boolean isConnected = activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting(); if (isConnected) { // ネットワーク接続あり Toast.makeText(context, "ネットワークに接続されました", Toast.LENGTH_SHORT).show(); } else { // ネットワーク接続なし Toast.makeText(context, "ネットワーク接続が失われました", Toast.LENGTH_SHORT).show(); // オフライン用のページを表示 ((MainActivity) context).showOfflinePage(); } } }
MainActivity.javaにオフライン用のページを表示するメソッドを追加します。
Java// MainActivity.javaに追加 public void showOfflinePage() { String offlineHtml = "<html><body style='text-align:center; padding-top:50px; font-family: sans-serif;'>" + "<h1>オフライン状態です</h1>" + "<p>ネットワーク接続を確認して、再度お試しください</p>" + "<button onclick='window.Android.reloadPage()' style='padding:10px 20px; background-color:#4CAF50; color:white; border:none; border-radius:4px; cursor:pointer;'>再試行</button>" + "</body></html>"; webView.loadDataWithBaseURL(null, offlineHtml, "text/html", "UTF-8", null); }
AndroidManifest.xmlにインターネットアクセスとネットワーク状態変更のパーミッションを追加します。
Xml<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
MainActivity.javaのonCreateメソッドでNetworkChangeReceiverを登録します。
Java// MainActivity.javaのonCreateメソッドに追加 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ...(既存のコード) // NetworkChangeReceiverを登録 IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(new NetworkChangeReceiver(), filter); } @Override protected void onDestroy() { super.onDestroy(); // NetworkChangeReceiverを登録解除 unregisterReceiver(new NetworkChangeReceiver()); }
これで、ネットワーク接続の状態が変化した際に適切な処理が行われるようになります。
ハマった点やエラー解決
問題1: WebViewClientのメソッドが呼ばれない
症状: onReceivedHttpErrorメソッドが呼ばれず、ステータスコードが取得できない。
原因: WebViewClientの設定が正しく行われていないか、Androidのバージョンによってはメソッドの仕様が異なる場合がある。
解決策: 1. WebViewClientの設定が正しく行われているか確認する。 2. Androidのバージョンに応じて、適切なメソッドを使用する。Android 7.0 (Nougat)以降はshouldOverrideUrlLoadingの代わりにshouldOverrideUrlLoading(WebResourceRequest request)を使用する。 3. WebViewの設定でJavaScriptを有効にしているか確認する。
Java// Android 7.0以降の対応 @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return shouldOverrideUrlLoading(view, request.getUrl().toString()); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // URLをオーバーライドする必要がない場合はfalseを返す return false; }
問題2: ステータスコードが取得できない
症状: WebResourceResponseのgetStatusCode()メソッドが常に0を返す。
原因: 一部のサイトでは、エラーページがHTMLとして返されるだけで、HTTPステータスコードが正しく設定されていない場合がある。
解決策: 1. WebResourceResponseのgetReasonPhrase()メソッドも確認し、エラーメッセージからステータスコードを推測する。 2. サーバー側で正しいHTTPステータスコードが設定されているか確認する。 3. キャッシュの影響で正しいレスポンスが取得できない可能性があるため、キャッシュを無効にしてみる。
Java// キャッシュを無効にする設定 webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
問題3: メインスレッドでのUI操作に関する問題
症状: WebViewClientのコールバックメソッド内でUI操作を行おうとすると、例外が発生する。
原因: WebViewClientのコールバックメソッドはメインスレッド以外のスレッドから呼び出される可能性がある。
解決策: 1. UI操作を行う前に、現在のスレッドがメインスレッドかどうかを確認する。 2. runOnUiThreadメソッドを使用して、UI操作をメインスレッドで実行する。
Java// メインスレッドでUI操作を行う private void updateUIOnUiThread(Runnable action) { if (Thread.currentThread() == Looper.getMainLooper().getThread()) { action.run(); } else { new Handler(Looper.getMainLooper()).post(action); } }
問題4: HTTPSとHTTPの混在によるセキュリティエラー
症状: WebViewでHTTPSのページを表示しようとすると、セキュリティエラーが発生する。
原因: Android 7.0以降では、HTTPSとHTTPの混在が許可されない。
解決策: 1. AndroidManifest.xmlでusesCleartextTraffic属性をtrueに設定する。
Xml<!-- AndroidManifest.xmlのapplicationタグ内に追加 --> android:usesCleartextTraffic="true"
- WebViewの設定でMixedContentModeを設定する。
Java// MixedContentModeを設定 webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_MODE_COMPATIBILITY);
まとめ
本記事では、Android StudioでWebViewのステータスコードを取得し、エラーハンドリングを実装する方法について解説しました。
- WebViewClientをカスタマイズすることで、HTTPステータスコードを取得できる
- ステータスコードに基づいたエラーハンドリングを実装することで、ユーザー体験を向上できる
- ローディング状態の表示とネットワーク接続状態の監視は、アプリの信頼性を高める重要な要素である
- 実装中によく遭遇する問題には、適切な解決策がある
この記事を通して、WebViewを利用したアプリ開発におけるエラーハンドリングの重要性と実装方法を理解できたかと思います。今後は、取得したステータスコードを活用した分析機能や、より高度なエラーページのカスタマイズなど、発展的な内容についても記事にする予定です。
参考資料
- Android Developer - WebView
- Android Developer - WebResourceResponse
- MDN Web Docs - HTTPステータスコード
- Android Developer - ConnectivityManager
