防止WebView显示“网页不可用”

90
我有一个应用程序,它广泛使用WebView。当这个应用程序的用户没有互联网连接时,会显示一个页面,上面写着“网页不可用”和其他各种文本。有没有办法在我的WebView中不显示这个通用文本?我想提供自己的错误处理方式。
private final Activity activity = this;

private class MyWebViewClient extends WebViewClient
 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
  // I need to do something like this:
  activity.webView.wipeOutThePage();
  activity.myCustomErrorHandling();
  Toast.makeText(activity, description, Toast.LENGTH_LONG).show();
 }
}

我发现WebView->clearView()实际上并没有清除视图。


3
在展示 WebView 之前,为什么不检查网络连接呢?如果没有网络可用,您可以跳过显示 WebView,并显示一个带有无网络消息的警告或弹出窗口。 - Andro Selva
@JoJo,你能将一个答案标记为正确吗?很可能是我的:P - Sherif elKhatib
16个回答

107

首先在您的资产文件夹中创建自己的HTML错误页面,称其为myerrorpage.html。 然后使用onReceivedError:

mWebView.setWebViewClient(new WebViewClient() {
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        mWebView.loadUrl("file:///android_asset/myerrorpage.html");

    }
});

34
自定义的错误页面将在一小段时间内替换旧的“网页未加载”错误页面,这个过程只持续了几分之一秒,然后从资产中加载自定义错误页面。 - Cheenu Madan
2
同意Cheenu的观点。必须有更好的解决方案。 - Jozua
3
  1. 当 WebView 不可见时,你可以加载网页内容(可能在其上显示进度条),如果页面成功加载,则显示它,否则在出现错误时加载“myerrorpage.html”。
  2. 请注意,如果加载出现问题或者在页面加载后运行的 JS 出现问题(例如在 document.ready() 事件中运行的 jQuery 函数),会触发 onReceiveError。桌面浏览器会允许 JS 错误而不通知用户,移动浏览器也应该这样做。
- FunkSoulBrother
7
هœ¨è°ƒç”¨loadUrlن¹‹ه‰چو·»هٹ view.loadUrl("about:blank");هڈ¯éک²و­¢é،µé‌¢هœ¨çں­وڑ‚و—¶é—´ه†…وک¾ç¤؛错误é،µم€‚ - Aashish Kumar
2
把myerrorpage.html文件放在哪里?Android资产文件夹在哪里? - Nived Kannada
转到“Packages”,右键单击它,然后选择:“新建”->“文件夹”->“资产文件夹”,单击“完成”,现在您就可以看到资产文件夹了,在其中创建新的“myerrorpage.html”。 - Mostafa Soliman

39

我发现的最佳解决方案是在OnReceivedError事件中加载一个空页面,如下所示:

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
    super.onReceivedError(view, errorCode, description, failingUrl);

    view.loadUrl("about:blank");
}

好主意! 在我的情况下,我添加了两次view.loadUrl(noConnectionUrl);(其中noConnectionUrl是带有错误消息的本地文件)。 这也有助于在短短几秒钟内不“看到”内置的错误页面。 - kasparspr
5
请注意,如果你在处理 webView.goBack() 方法时,一定要处理这个空白页面的情况。否则,你的上一个页面将会被重新加载,如果再次失败,那么你将再次看到这个空白页面。 - Chintan Shah
那么,处理它的最佳方式是什么? - Mostafa Soliman
@MoSoli 这完全取决于您的需求。一个替代显示空白页面的方法是展示一些自定义UI或缓存的网页。 - 7heViking

11

最终,我解决了这个问题。(到目前为止它都有效...)

我的解决方案如下...

  1. 准备布局,以在出现错误时显示,而不是网页(一个肮脏的“页面未找到”消息)。该布局有一个名为“重新加载”的按钮,并带有一些指南信息。

  2. 如果发生错误,请记住使用布尔值并显示我们准备好的布局。

  3. 如果用户单击“重新加载”按钮,则将mbErrorOccured设置为false。并将mbReloadPressed设置为true。
  4. 如果mbErrorOccured为false且mbReloadPressed为true,则表示webview已成功加载页面。因为如果再次出现错误,mbErrorOccured将在onReceivedError(...)中设为true。

这是我的完整源代码。看看吧。

public class MyWebViewActivity extends ActionBarActivity implements OnClickListener {

    private final String TAG = MyWebViewActivity.class.getSimpleName();
    private WebView mWebView = null;
    private final String URL = "http://www.google.com";
    private LinearLayout mlLayoutRequestError = null;
    private Handler mhErrorLayoutHide = null;

    private boolean mbErrorOccured = false;
    private boolean mbReloadPressed = false;

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);

        ((Button) findViewById(R.id.btnRetry)).setOnClickListener(this);
        mlLayoutRequestError = (LinearLayout) findViewById(R.id.lLayoutRequestError);
        mhErrorLayoutHide = getErrorLayoutHideHandler();

        mWebView = (WebView) findViewById(R.id.webviewMain);
        mWebView.setWebViewClient(new MyWebViewClient());
        WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        mWebView.setWebChromeClient(getChromeClient());
        mWebView.loadUrl(URL);
    }

    @Override
    public boolean onSupportNavigateUp() {
        return super.onSupportNavigateUp();
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();

        if (id == R.id.btnRetry) {
            if (!mbErrorOccured) {
                return;
            }

            mbReloadPressed = true;
            mWebView.reload();
            mbErrorOccured = false;
        }
    }

    @Override
    public void onBackPressed() {
        if (mWebView.canGoBack()) {
            mWebView.goBack();
            return;
        }
        else {
            finish();
        }

        super.onBackPressed();
    }

    class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return super.shouldOverrideUrlLoading(view, url);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }

        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (mbErrorOccured == false && mbReloadPressed) {
                hideErrorLayout();
                mbReloadPressed = false;
            }

            super.onPageFinished(view, url);
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            mbErrorOccured = true;
            showErrorLayout();
            super.onReceivedError(view, errorCode, description, failingUrl);
        }
    }

    private WebChromeClient getChromeClient() {
        final ProgressDialog progressDialog = new ProgressDialog(MyWebViewActivity.this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setCancelable(false);

        return new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
            }
        };
    }

    private void showErrorLayout() {
        mlLayoutRequestError.setVisibility(View.VISIBLE);
    }

    private void hideErrorLayout() {
        mhErrorLayoutHide.sendEmptyMessageDelayed(10000, 200);
    }

    private Handler getErrorLayoutHideHandler() {
        return new Handler() {
            @Override
            public void handleMessage(Message msg) {
                mlLayoutRequestError.setVisibility(View.GONE);
                super.handleMessage(msg);
            }
        };
    }
}
附加说明:这里是布局...
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rLayoutWithWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<WebView
    android:id="@+id/webviewMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<LinearLayout
    android:id="@+id/lLayoutRequestError"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:background="@color/white"
    android:gravity="center"
    android:orientation="vertical"
    android:visibility="gone" >

    <Button
        android:id="@+id/btnRetry"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:minWidth="120dp"
        android:text="RELOAD"
        android:textSize="20dp"
        android:textStyle="bold" />
</LinearLayout>


请问您能分享一下资源文件吗?我是新手。 - Rajan Rawal
@RajanRawal 我编辑并添加了布局示例。 :) - cmcromance
我看不到任何进度条,但我可以看到代码。 :-( - Rajan Rawal

9
当Webview嵌入到自定义视图中时,用户几乎会认为他看到的是原生视图而不是Webview。在这种情况下显示“页面无法加载”错误是荒谬的。我通常在这种情况下加载空白页面并显示如下的toast消息。
webView.setWebViewClient(new WebViewClient() {

            @Override
            public void onReceivedError(WebView view, int errorCode,
                    String description, String failingUrl) {
                Log.e(TAG," Error occured while loading the web page at Url"+ failingUrl+"." +description);     
                view.loadUrl("about:blank");
                Toast.makeText(App.getContext(), "Error occured, please check newtwork connectivity", Toast.LENGTH_SHORT).show();
                super.onReceivedError(view, errorCode, description, failingUrl);
            }
        });

7

查看Android WebView onReceivedError()的讨论。虽然很长,但是共识似乎是 a) 无法停止“网页不可用”的页面出现,但是 b) 在收到onReceivedError之后,可以始终加载一个空页面。


这些人在谈论onReceivedError根本没有触发。当没有网络连接时,我的代码可以正确触发它。 - JoJo

2

我在这里找到了最简单的解决方案。请查看以下内容:

   @Override
        public void onReceivedError(WebView view, int errorCode,
                                    String description, String failingUrl) {
//                view.loadUrl("about:blank");
            mWebView.stopLoading();
            if (!mUtils.isInterentConnection()) {
                Toast.makeText(ReportingActivity.this, "Please Check Internet Connection!", Toast.LENGTH_SHORT).show();
            }
            super.onReceivedError(view, errorCode, description, failingUrl);
        }

这里是isInterentConnection()方法...

public boolean isInterentConnection() {
    ConnectivityManager manager = (ConnectivityManager) mContext
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    if (manager != null) {
        NetworkInfo info[] = manager.getAllNetworkInfo();
        if (info != null) {
            for (int i = 0; i < info.length; i++) {
                if (info[i].getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
    }
    return false;
}

2
您可以使用GET请求获取页面内容,然后使用Webview显示该数据,这样您就不需要使用多个服务器调用。 或者,您可以使用Javascript检查DOM对象的有效性。

那是个好主意,然而,我们需要做类似这样的事情真的很不幸... - Jeffrey Blattman

2
也许我误解了问题,但听起来你是在说你收到错误回调,只是想知道最好的方法是不显示错误?为什么不直接将网页视图从屏幕上移除或在其上方显示另一个视图呢?

1

我想,如果你坚持这样做,你可以在调用loadURL函数之前检查资源是否存在。只需简单地覆盖函数并在调用super()之前进行检查即可。

备注(可能与主题无关):在http中,有一种称为HEAD的方法,描述如下:

HEAD方法与GET方法相同,但服务器在响应中不得返回消息正文

这种方法可能很方便。无论如何,无论你如何实现它...都要检查这段代码:

import java.util.Map;

import android.content.Context;
import android.webkit.WebView;

public class WebViewPreLoad extends WebView{

public WebViewPreLoad(Context context) {
super(context);
}
public void loadUrl(String str){
if(//Check if exists)
super.loadUrl(str);
else
//handle error
}
public void loadUrl(String url, Map<String,String> extraHeaders){
if(//Check if exists)
super.loadUrl(url, extraHeaders);
else
//handle error
}
}

你可以尝试使用以下代码进行检查:
if(url.openConnection().getContentLength() > 0)

7
在实际访问资源之前检查资源是否存在会导致服务器负载翻倍,因为每个虚拟请求需要进行两次物理请求。这似乎有点过度。 - JoJo
1
无论如何,您仍然可以重载它并获取HTML内容,检查是否有错误。如果没有错误,则解析并显示它。 - Sherif elKhatib
如何实际实现这个呢? - JoJo
@JoJo,实际上这种方法可以减轻服务器的负担,因为返回HEAD响应比返回304或500响应的开销要小得多,尤其是在无效的URL情况下。 - Justin Shield
如果在下载页面时连接断开,这并不能解决问题。 - Pedro Loureiro

0

我只需要将网页更改为您用于错误处理的内容:

getWindow().requestFeature(Window.FEATURE_PROGRESS);  
webview.getSettings().setJavaScriptEnabled(true);  
final Activity activity = this;  
webview.setWebChromeClient(new WebChromeClient() {  
public void onProgressChanged(WebView view, int progress) {  
 // Activities and WebViews measure progress with different scales.  
 // The progress meter will automatically disappear when we reach 100%  
 activity.setProgress(progress * 1000);  
 }  
});  
webview.setWebViewClient(new WebViewClient() {  
public void onReceivedError(WebView view, int errorCode, String description, String 
failingUrl) {  
 Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();  
}  
});  
webview.loadUrl("http://slashdot.org/");

这些都可以在http://developer.android.com/reference/android/webkit/WebView.html上找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接