带你解决 WebView 里的常见问题,建议收藏备用

本文详细解析WebView的常见问题及解决方案,包括加载控制、缓存管理、文件处理、JS注入、视频播放、Cookie管理等,旨在帮助开发者高效调试和优化WebView应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

640?wx_fmt=gif

热文导读 | 点击标题阅读

金九银十跳槽季如何进阶找到合适满意的工作?

IBM大中华区总架构师:话说程序员的职业生涯

杭州29岁IT男凌晨突发脑出血!老父亲面对医生急的差点跪下

来源:cpacm

www.jianshu.com/p/fea5e829b30a


前言


通常我们在自己开发的 APP 中打开网页无非两种方法: 一是跳转到系统自带的浏览器,二是使用 WebView 控件加载页面。使用 WebView 控件的好处就是可以通过各种 api 接口来定制各种行为,常用的几个设置地方为 WebSettings、JavaScriptInterface、WebViewClient 和WebChromeClient。平时出现的问题都可以通过修改这些设置来解决。


问题总结


1、使用了 WebView 还是跳转到了系统自带的浏览器?


很简单的解决方法,为你的 webview 设置一个新的 WebViewClient。



 

webView.setWebViewClient(new WebViewClient(){
   @Override
   public boolean shouldOverrideUrlLoading(WebView view, String url) {
       view.loadUrl(url);
       return true;
   }
});



 

// 或者直接添加,效果是一样的
webView.setWebViewClient(new WebViewClient());


2、获取网页的标题和图标


通过 WebChromeClient 可以获取到这些信息。



 

webView.setWebChromeClient(new WebChromeClient() {
   @Override
   public void onReceivedTitle(WebView view, String title) {
       super.onReceivedTitle(view, title);
       setTitle(title);
   }

   @Override
   public void onReceivedIcon(WebView view, Bitmap icon) {
       super.onReceivedIcon(view, icon);
       setIcon(icon);
   }

});


但是,这里有个问题,当通过 webView.goBack() 方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。所以需要在 onPageFinished() 中对界面标题重新设置。



 

webView.setWebViewClient(new WebViewClient(){
   @Override
   public void onPageFinished(WebView view, String url) {
       super.onPageFinished(view, url);
       setTitle(String.valueOf(view.getTitle()));
   }
});


3、返回键实现网页的后退键


在 WebView 中可以通过 goBack() 方法后退到历史记录的上一项。



 

// 在 Actvity 中监听返回键按钮
   @Override
   public void onBackPressed() {
       if (webView.canGoBack())
           webView.goBack();
       else
           super.onBackPressed();
   }


4、设置 WebView 的 header


在 WebView 的 loadUrl() 方法中传入 Header 参数即可。



 

public void loadURLWithHTTPHeaders() {
   final String url = "http://cpacm.net";
   WebView webView = new WebView(getActivity());
   Map<String,String> extraHeaders = new HashMap<StringString>();
   extraHeaders.put("Referer""http://www.google.com");
   webView.loadUrl(url, extraHeaders);
}


5、设置 WebView 的 User-Agent


不要试图在 Header 里面去修改,而是在 WebSettings 修改



 

webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");


6、如何设置 WebView 的缓存


当需要本地缓存网页的时候就需要打开 WebViewSettings 的缓存开关,这样子当下次进到该页面无网络的情况下也能打开页面。



 

WebSettings settings = webView.getSettings();
settings.setAppCacheEnabled(true); //启用应用缓存
settings.setDomStorageEnabled(true); //启用或禁用DOM缓存。
settings.setDatabaseEnabled(true); //启用或禁用DOM缓存。
if (SystemUtil.isNetworkConnected()) { //判断是否联网
   settings.setCacheMode(WebSettings.LOAD_DEFAULT); //默认的缓存使用模式
else {
   settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); //不从网络加载数据,只从缓存加载数据。
}


7、无法下载文件?


在自己写的 WebView 下是无法直接下载文件,需要自己监听下载事件并对下载的动作进行处理。



 

/**
* 当下载文件时打开系统自带的浏览器进行下载,当然也可以对捕获到的 url 进行处理在应用内下载。
**/

webView.setDownloadListener(new FileDownLoadListener());

private class FileDownLoadListener implements DownloadListener {
   @Override
   public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
       Uri uri = Uri.parse(url);
       Intent intent = new Intent(Intent.ACTION_VIEW, uri);
       startActivity(intent);
   }
}


8、无法打开文件选择器?


通过重写 WebChromeClient 来实现点击 <input type='file'> 来打开系统文件选择器。


一个完整的Activity示例



 

public class MainActivity extends AppCompatActivity {

   /** Android 5.0以下版本的文件选择回调 */
   protected ValueCallback<Uri> mFileUploadCallbackFirst;
   /** Android 5.0及以上版本的文件选择回调 */
   protected ValueCallback<Uri[]> mFileUploadCallbackSecond;

   protected static final int REQUEST_CODE_FILE_PICKER = 51426;


   protected String mUploadableFileTypes = "image/*";

   private WebView mWebView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       initWebView();
   }

   private void initWebView() {
       mWebView = (WebView) findViewById(R.id.my_webview);

       mWebView.loadUrl("file:///android_asset/index.html");
       mWebView.setWebChromeClient(new OpenFileChromeClient());
   }

   private class OpenFileChromeClient extends WebChromeClient {

       //  Android 2.2 (API level 8)到Android 2.3 (API level 10)版本选择文件时会触发该隐藏方法
       @SuppressWarnings("unused")
       public void openFileChooser(ValueCallback<Uri> uploadMsg) {
           openFileChooser(uploadMsg, null);
       }

       // Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本选择文件时会触发,该方法为隐藏方法
       public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
           openFileChooser(uploadMsg, acceptType, null);
       }

       // Android 4.1 (API level 16) -- Android 4.3 (API level 18)版本选择文件时会触发,该方法为隐藏方法
       @SuppressWarnings("unused")
       public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
           openFileInput(uploadMsg, nullfalse);
       }

       // Android 5.0 (API level 21)以上版本会触发该方法,该方法为公开方法
       @SuppressWarnings("all")
       public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
           if (Build.VERSION.SDK_INT >= 21) {
               final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多选
               openFileInput(null, filePathCallback, allowMultiple);
               return true;
           }
           else {
               return false;
           }
       }
   }

   @SuppressLint("NewApi")
   protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) {
       //Android 5.0以下版本
       if (mFileUploadCallbackFirst != null) {
           mFileUploadCallbackFirst.onReceiveValue(null);
       }
       mFileUploadCallbackFirst = fileUploadCallbackFirst;

       //Android 5.0及以上版本
       if (mFileUploadCallbackSecond != null) {
           mFileUploadCallbackSecond.onReceiveValue(null);
       }
       mFileUploadCallbackSecond = fileUploadCallbackSecond;

       Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);

       if (allowMultiple) {
           if (Build.VERSION.SDK_INT >= 18) {
               i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
           }
       }

       i.setType(mUploadableFileTypes);

       startActivityForResult(Intent.createChooser(i, "选择文件"), REQUEST_CODE_FILE_PICKER);

   }

   public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
       if (requestCode == REQUEST_CODE_FILE_PICKER) {
           if (resultCode == Activity.RESULT_OK) {
               if (intent != null) {
                   //Android 5.0以下版本
                   if (mFileUploadCallbackFirst != null) {
                       mFileUploadCallbackFirst.onReceiveValue(intent.getData());
                       mFileUploadCallbackFirst = null;
                   }
                   else if (mFileUploadCallbackSecond != null) {//Android 5.0及以上版本
                       Uri[] dataUris = null;

                       try {
                           if (intent.getDataString() != null) {
                               dataUris = new Uri[] { Uri.parse(intent.getDataString()) };
                           }
                           else {
                               if (Build.VERSION.SDK_INT >= 16) {
                                   if (intent.getClipData() != null) {
                                       final int numSelectedFiles = intent.getClipData().getItemCount();

                                       dataUris = new Uri[numSelectedFiles];

                                       for (int i = 0; i < numSelectedFiles; i++) {
                                           dataUris[i] = intent.getClipData().getItemAt(i).getUri();
                                       }
                                   }
                               }
                           }
                       }
                       catch (Exception ignored) { }
                       mFileUploadCallbackSecond.onReceiveValue(dataUris);
                       mFileUploadCallbackSecond = null;
                   }
               }
           }
           else {
               //这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了
               //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
               //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
               if (mFileUploadCallbackFirst != null) {
                   mFileUploadCallbackFirst.onReceiveValue(null);
                   mFileUploadCallbackFirst = null;
               }
               else if (mFileUploadCallbackSecond != null) {
                   mFileUploadCallbackSecond.onReceiveValue(null);
                   mFileUploadCallbackSecond = null;
               }
           }
       }
   }

}


9、怎么为 WebView 的加载添加进度条


这里的 onPageFinished() 有个问题,不能在这里监听页面是否加载完毕(我自己测试的时候,好像在重定向和加载完 iframes 时都会调用这个方法)。


把页面加载完毕的判断放在 onProgressChanged() 里可能会更为准确。



 

webView.setWebChromeClient(new WebChromeClient() {

   @Override
   public void onProgressChanged(WebView view, int position) {
       progressBar.setProgress(position);
       if (position == 100) {
           progressBar.setVisibility(View.GONE);
       }
       super.onProgressChanged(view, position);
   }
});

webView.setWebViewClient(new WebViewClient(){
   @Override
   public void onPageStarted(WebView view, String url, Bitmap favicon) {
       progressBar.setVisibility(View.VISIBLE);
       super.onPageStarted(view, url, favicon);
   }
});


10、怎样对页面进行 Js 注入?


首先你要在 WebView 开启 JavaScript,然后搭建桥梁



 

WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppBridge(new WebAppBridge.OauthLoginImpl() {
           @Override
           public void getResult(String s) {
               //TODO
           }
       }),
       "oauth");
webView.loadUrl("javascript:" + getAssetsJs("autologin.js"));
webView.loadUrl("javascript:adduplistener()");


WebAppBridge的代码



 

public class WebAppBridge {

   private OauthLoginImpl oauthLogin;

   public WebAppBridge(OauthLoginImpl oauthLogin) {
       this.oauthLogin = oauthLogin;
   }

   @JavascriptInterface
   public void getResult(String str) {
       if (oauthLogin != null)
           oauthLogin.getResult(str);
   }

   public interface OauthLoginImpl {
       void getResult(String s);
   }
}


简单的说就是向网页注入一段 js, 在这段 js 里面设置回调到java中的方法 getResult(),由 WebAppBridge.getResult 来回收。
其中js的核心代码为:



 

oauth.getResult(str);


其中 oauth 这个名称要与 webView.addJavascriptInterface()方法的第二个参数一样。


具体的代码可以参考这个项目中写的 js 注入逻辑 OauthDialog
地址:https://github.com/cpacm/MoeMusic/blob/master/app/src/main/java/com/cpacm/moemusic/ui/widgets/dialogs/OauthDialog.java


11、如何手动添加 Cookie


需要获得 CookieManager 的对象并将 cookie 设置进去。


从服务器的返回头中取出 cookie 根据Http请求的客户端不同,获取 cookie 的方式也不同,请自行获取。



 

/**
* 将cookie设置到 WebView
* @param url 要加载的 url
* @param cookie 要同步的 cookie
*/

public static void syncCookie(String url,String cookie) {
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
       CookieSyncManager.createInstance(context);
   }
   CookieManager cookieManager = CookieManager.getInstance();
   cookieManager.setAcceptCookie(true);
   
   /**
    * cookie 设置形式
    * cookieManager.setCookie(url, "key=value;" + "domain=[your domain];path=/;")
   **/

   cookieManager.setCookie(url, cookie);
}


删除 Cookie 的方法



 

/**
* 这个两个在 API level 21 被抛弃
* CookieManager.getInstance().removeSessionCookie();
* CookieManager.getInstance().removeAllCookie();
*
* 推荐使用这两个, level 21 新加的
* CookieManager.getInstance().removeSessionCookies();
* CookieManager.getInstance().removeAllCookies();
**/

public static void removeCookies() {
   CookieManager cookieManager = CookieManager.getInstance();
   cookieManager.removeAllCookie();
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
       cookieManager.flush();
   } else {
       CookieSyncManager.createInstance(Application.getInstance());
       CookieSyncManager.getInstance().sync();
   }
}


12、如何使 HTML5 video 在 WebView 全屏显示


当网页全屏播放视频时会调用 WebChromeClient.onShowCustomView() 方法,所以可以通过将 video 播放的视图全屏达到目的。



 

@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
   if (view instanceof FrameLayout && fullScreenView != null) {
       // A video wants to be shown
       this.videoViewContainer = (FrameLayout) view;
       this.videoViewCallback = callback;
       fullScreenView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
       fullScreenView.setVisibility(View.VISIBLE);
       isVideoFullscreen = true;
   }
}

@Override
public void onHideCustomView() {
   if (isVideoFullscreen && fullScreenView != null) {
       // Hide the video view, remove it, and show the non-video view
       fullScreenView.setVisibility(View.INVISIBLE);
       fullScreenView.removeView(videoViewContainer);

       // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
       if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) {
           videoViewCallback.onCustomViewHidden();
       }

       isVideoFullscreen = false;
       videoViewContainer = null;
       videoViewCallback = null;
   }
}


但是很多的手机版本在网页视频播放时是不会调用这个方法的,所以这个方法局限性很大。


13、Android5.0上 WebView中Http和Https混合问题



 

/**
* MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的;
* MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源;
* MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。
**/

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}


14、如何避免 WebView 的内存泄露问题


  • 可以将 Webview 的 Activity 新起一个进程,结束的时候直接System.exit(0);退出当前进程;

  • 不在xml中定义 WebView,而是在代码中创建,使用 getApplicationgContext() 作为传递的 Conetext;

  • 在 Activity 销毁的时候,将 WebView 置空



 

@Override
protected void onDestroy() {
   if (webView != null) {
       webView.loadDataWithBaseURL(null"""text/html""utf-8"null);
       webView.clearHistory();
       ((ViewGroup) webView.getParent()).removeView(webView);
       webView.destroy();
       webView = null;
   }
   super.onDestroy();
}


总结


如果你踩到了 WebView 上的坑,请先默哀一分钟,然后努力找找解决方法吧,总会有人体验过你的悲剧,也会有人重蹈你的覆辙。


当然 WebView 里肯定不止我上面列出来的这些问题,如果你有更多的 WebView 问题解决方案欢迎评论交流。


想进阿里吗?快加入我们的知识星球吧,如下:

640?wx_fmt=png

640?wx_fmt=gif

你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可


最后,国庆福利来了,我们的知识星球已达到1000人了,之前说过到达1000人时将大大幅涨价到169元,为了反馈大家对我们的关注和厚爱,特此维持现价99元最后一天,今天后(今晚 00:00)后将涨到169元,欢迎大家加入我们的知识星球,更多星球信息参见:

如何进阶成为Java和Android架构师?

金九银十跳槽季如何进阶找到合适满意的工作?

说两件事

640?wx_fmt=jpeg

微信扫描或者点击上方二维码领取Android\Python\AI\Java等高级进阶资源

更多学习资料点击下面的“阅读原文”获取

640?wx_fmt=gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值