阿里云服务器免费领卷啦。

捡代码论坛-最全的游戏源码下载技术网站!

 找回密码
 立 即 注 册

QQ登录

只需一步,快速开始

搜索
关于源码区的附件失效或欺骗帖, 处理办法
查看: 1799|回复: 0

带你解决安卓 WebView 里的常见问题

[复制链接]

4208

主题

210

回帖

12万

积分

管理员

管理员

Rank: 9Rank: 9Rank: 9

积分
126075
QQ
发表于 2017-2-14 10:25:13 | 显示全部楼层 |阅读模式
带你解决安卓 WebView 里的常见问题。
通常我们在自己开发的 APP 中打开网页无非两种方法: 一是跳转到系统自带的浏览器,二是使用 WebView 控件加载页面。使用 WebView 控件的好处就是可以通过各种 api 接口来定制各种行为,常用的几个设置地方为WebSettings、JavaScriptInterface、WebViewClient 和 WebChromeClient。平时出现的问题都可以通过修改这些设置来解决。

WebView.jpg

使用了 WebView 还是跳转到了系统自带的浏览器?
很简单的解决方法,为你的 webview 设置一个新的 WebViewClient。
  1. webView.setWebViewClient(new WebViewClient(){
  2.     @Override
  3.     public boolean shouldOverrideUrlLoading(WebView view, String url) {
  4.         view.loadUrl(url);
  5.         return true;
  6.     }
  7. });
复制代码
  1. // 或者直接添加,效果是一样的
  2. webView.setWebViewClient(new WebViewClient());
复制代码
获取网页的标题和图标
通过 WebChromeClient 可以获取到这些信息。
  1. webView.setWebChromeClient(new WebChromeClient() {
  2.     @Override
  3.     public void onReceivedTitle(WebView view, String title) {
  4.         super.onReceivedTitle(view, title);
  5.         setTitle(title);
  6.     }

  7.     @Override
  8.     public void onReceivedIcon(WebView view, Bitmap icon) {
  9.         super.onReceivedIcon(view, icon);
  10.         setIcon(icon);
  11.     }

  12. });
复制代码
但是,这里有个问题,当通过 webView.goBack() 方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。所以需要在 onPageFinished() 中对界面标题重新设置。
  1. webView.setWebViewClient(new WebViewClient(){
  2.     @Override
  3.     public void onPageFinished(WebView view, String url) {
  4.         super.onPageFinished(view, url);
  5.         setTitle(String.valueOf(view.getTitle()));
  6.     }
  7. });
复制代码
返回键实现网页的后退键
在 WebView 中可以通过 goBack() 方法后退到历史记录的上一项。

  1.     // 在 Actvity 中监听返回键按钮
  2.     @Override
  3.     public void onBackPressed() {
  4.         if (webView.canGoBack())
  5.             webView.goBack();
  6.         else
  7.             super.onBackPressed();
  8.     }
复制代码
设置 WebView 的 header
在 WebView 的 loadUrl() 方法中传入 Header 参数即可。
  1. public void loadURLWithHTTPHeaders() {
  2.     final String url = "http://cpacm.net";
  3.     WebView webView = new WebView(getActivity());
  4.     Map<String,String> extraHeaders = new HashMap<String, String>();
  5.     extraHeaders.put("Referer", "http://www.google.com");
  6.     webView.loadUrl(url, extraHeaders);
  7. }
复制代码

设置 WebView 的 User-Agent
不要试图在 Header 里面去修改,而是在 WebSettings 修改
  1. webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");
复制代码
如何设置 WebView 的缓存
当需要本地缓存网页的时候就需要打开 WebViewSettings 的缓存开关,这样子当下次进到该页面无网络的情况下也能打开页面。
  1. WebSettings settings = webView.getSettings();
  2. settings.setAppCacheEnabled(true); //启用应用缓存
  3. settings.setDomStorageEnabled(true); //启用或禁用DOM缓存。
  4. settings.setDatabaseEnabled(true); //启用或禁用DOM缓存。
  5. if (SystemUtil.isNetworkConnected()) { //判断是否联网
  6.     settings.setCacheMode(WebSettings.LOAD_DEFAULT); //默认的缓存使用模式
  7. } else {
  8.     settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); //不从网络加载数据,只从缓存加载数据。
  9. }
复制代码
无法下载文件?
在自己写的 WebView 下是无法直接下载文件,需要自己监听下载事件并对下载的动作进行处理。
  1. /**
  2. * 当下载文件时打开系统自带的浏览器进行下载,当然也可以对捕获到的 url 进行处理在应用内下载。
  3. **/
  4. webView.setDownloadListener(new FileDownLoadListener());

  5. private class FileDownLoadListener implements DownloadListener {
  6.     @Override
  7.     public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
  8.         Uri uri = Uri.parse(url);
  9.         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
  10.         startActivity(intent);
  11.     }
  12. }
复制代码
无法打开文件选择器?
通过重写 WebChromeClient 来实现点击 <input type='file'> 来打开系统文件选择器。
一个完整的Activity示例
  1. public class MainActivity extends AppCompatActivity {

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

  6.     protected static final int REQUEST_CODE_FILE_PICKER = 51426;


  7.     protected String mUploadableFileTypes = "image/*";

  8.     private WebView mWebView;

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

  13.         initWebView();
  14.     }

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

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

  20.     private class OpenFileChromeClient extends WebChromeClient {

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

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

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

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

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

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

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

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

  67.         i.setType(mUploadableFileTypes);

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

  69.     }

  70.     public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
  71.         if (requestCode == REQUEST_CODE_FILE_PICKER) {
  72.             if (resultCode == Activity.RESULT_OK) {
  73.                 if (intent != null) {
  74.                     //Android 5.0以下版本
  75.                     if (mFileUploadCallbackFirst != null) {
  76.                         mFileUploadCallbackFirst.onReceiveValue(intent.getData());
  77.                         mFileUploadCallbackFirst = null;
  78.                     }
  79.                     else if (mFileUploadCallbackSecond != null) {//Android 5.0及以上版本
  80.                         Uri[] dataUris = null;

  81.                         try {
  82.                             if (intent.getDataString() != null) {
  83.                                 dataUris = new Uri[] { Uri.parse(intent.getDataString()) };
  84.                             }
  85.                             else {
  86.                                 if (Build.VERSION.SDK_INT >= 16) {
  87.                                     if (intent.getClipData() != null) {
  88.                                         final int numSelectedFiles = intent.getClipData().getItemCount();

  89.                                         dataUris = new Uri[numSelectedFiles];

  90.                                         for (int i = 0; i < numSelectedFiles; i++) {
  91.                                             dataUris[i] = intent.getClipData().getItemAt(i).getUri();
  92.                                         }
  93.                                     }
  94.                                 }
  95.                             }
  96.                         }
  97.                         catch (Exception ignored) { }
  98.                         mFileUploadCallbackSecond.onReceiveValue(dataUris);
  99.                         mFileUploadCallbackSecond = null;
  100.                     }
  101.                 }
  102.             }
  103.             else {
  104.                 //这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了
  105.                 //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
  106.                 //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
  107.                 if (mFileUploadCallbackFirst != null) {
  108.                     mFileUploadCallbackFirst.onReceiveValue(null);
  109.                     mFileUploadCallbackFirst = null;
  110.                 }
  111.                 else if (mFileUploadCallbackSecond != null) {
  112.                     mFileUploadCallbackSecond.onReceiveValue(null);
  113.                     mFileUploadCallbackSecond = null;
  114.                 }
  115.             }
  116.         }
  117.     }

  118. }
复制代码
怎么为 WebView 的加载添加进度条
这里的 onPageFinished() 有个问题,不能在这里监听页面是否加载完毕(我自己测试的时候,好像在重定向和加载完 iframes 时都会调用这个方法)。
把页面加载完毕的判断放在 onProgressChanged() 里可能会更为准确。
  1. webView.setWebChromeClient(new WebChromeClient() {

  2.     @Override
  3.     public void onProgressChanged(WebView view, int position) {
  4.         progressBar.setProgress(position);
  5.         if (position == 100) {
  6.             progressBar.setVisibility(View.GONE);
  7.         }
  8.         super.onProgressChanged(view, position);
  9.     }
  10. });

  11. webView.setWebViewClient(new WebViewClient(){
  12.     @Override
  13.     public void onPageStarted(WebView view, String url, Bitmap favicon) {
  14.         progressBar.setVisibility(View.VISIBLE);
  15.         super.onPageStarted(view, url, favicon);
  16.     }
  17. });
复制代码
怎样对页面进行 Js 注入?
首先你要在 WebView 开启 JavaScript,然后搭建桥梁
  1. WebSettings webSettings = webView.getSettings();
  2. webSettings.setJavaScriptEnabled(true);
  3. webView.addJavascriptInterface(new WebAppBridge(new WebAppBridge.OauthLoginImpl() {
  4.             @Override
  5.             public void getResult(String s) {
  6.                 //TODO
  7.             }
  8.         }),
  9.         "oauth");
  10. webView.loadUrl("javascript:" + getAssetsJs("autologin.js"));
  11. webView.loadUrl("javascript:adduplistener()");
复制代码
WebAppBridge的代码
  1. public class WebAppBridge {

  2.     private OauthLoginImpl oauthLogin;

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

  6.     @JavascriptInterface
  7.     public void getResult(String str) {
  8.         if (oauthLogin != null)
  9.             oauthLogin.getResult(str);
  10.     }

  11.     public interface OauthLoginImpl {
  12.         void getResult(String s);
  13.     }
  14. }
复制代码

简单的说就是向网页注入一段 js, 在这段 js 里面设置回调到java中的方法 getResult(),由 WebAppBridge.getResult 来回收。
其中js的核心代码为
  1. oauth.getResult(str);
复制代码
其中 oauth 这个名称要与 webView.addJavascriptInterface()方法的第二个参数一样。
具体的代码可以参考这个项目中写的 js 注入逻辑 OauthDialog
如何手动添加 Cookie
需要获得 CookieManager 的对象并将 cookie 设置进去。
从服务器的返回头中取出 cookie 根据Http请求的客户端不同,获取 cookie 的方式也不同,请自行获取。
  1. /**
  2. * 将cookie设置到 WebView
  3. * @param url 要加载的 url
  4. * @param cookie 要同步的 cookie
  5. */
  6. public static void syncCookie(String url,String cookie) {
  7.     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  8.         CookieSyncManager.createInstance(context);
  9.     }
  10.     CookieManager cookieManager = CookieManager.getInstance();
  11.     cookieManager.setAcceptCookie(true);

  12.     /**
  13.      * cookie 设置形式
  14.      * cookieManager.setCookie(url, "key=value;" + "domain=[your domain];path=/;")
  15.     **/
  16.     cookieManager.setCookie(url, cookie);
  17. }
复制代码
删除 Cookie 的方法
  1. /**
  2. * 这个两个在 API level 21 被抛弃
  3. * CookieManager.getInstance().removeSessionCookie();
  4. * CookieManager.getInstance().removeAllCookie();
  5. *
  6. * 推荐使用这两个, level 21 新加的
  7. * CookieManager.getInstance().removeSessionCookies();
  8. * CookieManager.getInstance().removeAllCookies();
  9. **/
  10. public static void removeCookies() {
  11.     CookieManager cookieManager = CookieManager.getInstance();
  12.     cookieManager.removeAllCookie();
  13.     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  14.         cookieManager.flush();
  15.     } else {
  16.         CookieSyncManager.createInstance(Application.getInstance());
  17.         CookieSyncManager.getInstance().sync();
  18.     }
  19. }
复制代码

如何使 HTML5 video 在 WebView 全屏显示
当网页全屏播放视频时会调用 WebChromeClient.onShowCustomView() 方法,所以可以通过将 video 播放的视图全屏达到目的。
  1. @Override
  2. public void onShowCustomView(View view, CustomViewCallback callback) {
  3.     if (view instanceof FrameLayout && fullScreenView != null) {
  4.         // A video wants to be shown
  5.         this.videoViewContainer = (FrameLayout) view;
  6.         this.videoViewCallback = callback;
  7.         fullScreenView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  8.         fullScreenView.setVisibility(View.VISIBLE);
  9.         isVideoFullscreen = true;
  10.     }
  11. }

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

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

  22.         isVideoFullscreen = false;
  23.         videoViewContainer = null;
  24.         videoViewCallback = null;
  25.     }
  26. }
复制代码

但是很多的手机版本在网页视频播放时是不会调用这个方法的,所以这个方法局限性很大。
Android5.0上 WebView中Http和Https混合问题
  1. /**
  2. * MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的;
  3. * MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源;
  4. * MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。
  5. **/
  6. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  7.      webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
  8. }
复制代码
如何避免 WebView 的内存泄露问题
  • 可以将 Webview 的 Activity 新起一个进程,结束的时候直接System.exit(0);退出当前进程;
  • 不在xml中定义 WebView,而是在代码中创建,使用 getApplicationgContext() 作为传递的 Conetext;
  • 在 Activity 销毁的时候,将 WebView 置空
  1. @Override
  2. protected void onDestroy() {
  3. if (webView != null) {
  4.      webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
  5.      webView.clearHistory();
  6.      ((ViewGroup) webView.getParent()).removeView(webView);
  7.      webView.destroy();
  8.      webView = null;
  9. }
  10. super.onDestroy();
  11. }
复制代码
总结
如果你踩到了 WebView 上的坑,请先默哀一分钟,然后努力找找解决方法吧,总会有人体验过你的悲剧,也会有人重蹈你的覆辙。
当然 WebView 里肯定不止我上面列出来的这些问题,如果你有更多的 WebView 问题解决方案欢迎评论交流。
http://www.jianshu.com/p/fea5e829b30a


捡代码论坛-最全的游戏源码下载技术网站! - 论坛版权郑重声明:
1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者与捡代码论坛-最全的游戏源码下载技术网站!享有帖子相关版权
3、捡代码论坛版权,详细了解请点击。
4、本站所有内容均由互联网收集整理、网友上传,并且以计算机技术研究交流为目的,仅供大家参考、学习,不存在任何商业目的与商业用途。
5、若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。 我们不承担任何技术及版权问题,且不对任何资源负法律责任。
6、如无法链接失效或侵犯版权,请给我们来信:jiandaima@foxmail.com

回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立 即 注 册

本版积分规则

技术支持
在线咨询
QQ咨询
3351529868

QQ|手机版|小黑屋|捡代码论坛-专业源码分享下载 ( 陕ICP备15015195号-1|网站地图

GMT+8, 2024-3-29 00:09

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表