那些年在WebView上踩过的坑

那些年在WebView上踩过的坑

1.WebView的内存泄露问题 问题描述: webview内存泄露的情况还是很严重的,尤其是当你加载的页面比较庞大的时候。 解决方案: 1) 展示webview的activity可以另开一个进程,这样就能和我们app的主进程分开了,即使webview产生了oom崩溃等问题也不会影响到主程序,如何实现呢,其实很简单,在Androidmanifest.xml的activity标签里加上Android:process=”packagename.web”就可以了,并且当这个 进程结束时,请手动调用System.exit(0)。

  1. 如果实在不想用开额外进程的方式解决webview 内存泄露的问题,那么下面的方法很大程度上可以避免这种情况
public void releaseAllWebViewCallback() {
         if (android.os.Build.VERSION.SDK_INT < 16) {
             try {
                 Field field = WebView.class.getDeclaredField("mWebViewCore");
                 field = field.getType().getDeclaredField("mBrowserFrame");
                 field = field.getType().getDeclaredField("sConfigCallback");
                 field.setAccessible(true);
                 field.set(null, null);
             } catch (NoSuchFieldException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             }
         } else {
             try {
                 Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
                 if (sConfigCallback != null) {
                     sConfigCallback.setAccessible(true);
                     sConfigCallback.set(null, null);
                 }
             } catch (NoSuchFieldException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (ClassNotFoundException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             }
         }
     }

在webview的 destroy方法里 调用这个方法就行了。

2.慎重在shouldoverrideurlloading中返回true

当设置了WebviewClient时,在shouldoverrideurlloading中如果不需要对url进行拦截做处理,而是简单的继续加载此网址,则建议采用返回false的方式而不是loadUrl的方式进行加载网址。 1) 当请求的方式是”POST”方式时这个回调是不会通知的。 2) 因为如果采用loadUrl的方式进行加载,那么对于加载有跳转的网址时,进行webview.goBack就会特别麻烦。 例如加载链接如下: A1->(A2->A3->A4)->A5 括号内为跳转 如果采用return false的方式,那么在goBack的时候,可以从第二步直接回到A1网页。从A5回到A1只需要执行两次goBack 而如果采用的是loadUrl,则没办法直接从第二步回到A网页。因为loadUrl把第二步的每个跳转都认为是一个新的网页加载,因此从A5回到A1需要执行四次goBack

只有当不需要加载网址而是拦截做其他处理,如拦截tel:xxx等特殊url做拨号处理的时候,才应该返回true。

3.getSettings().setBuiltInZoomControls(true) 引发的crash。 问题描述: 这个方法调用以后 如果你触摸屏幕 弹出的提示框还没消失的时候 你如果activity结束了 就会报错了。3.0以上 4.4以下很多手机会出现这种情况

解决方案: 在activity的onDestroy方法里手动的将webiew设置成 setVisibility(View.GONE)

4.onPageFinished 函数的问题 问题描述: 你永远无法确定当WebView调用这个方法的时候,网页内容是否真的加载完毕了。当前正在加载的网页产生跳转的时候这个方法可能会被多次调用,多数开发者都是参考的http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android 这个上面的高票答案,但其中列举的解决方法并不完美。

解决方案: 当你的WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。如果哪位大神有更好的解决方法,欢迎留言。

5.WebView后台耗电问题。

问题描述: 当你的程序调用了WebView加载网页,WebView会自己开启一些线程,如果你没有正确地将WebView销毁的话,这些残余的线程会一直在后台运行,由此导致你的应用程序耗电量居高不下。

解决方案: 在Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机,这样就不会有任何问题了。

6.后台无法释放js 导致耗电

问题描述: 在有的手机里,你如果webview加载的html里 有一些js 一直在执行比如动画之类的东西,如果此刻webview 挂在了后台,这些资源是不会被释放 用户也无法感知,导致一直占有cpu 耗电特别快。

解决方案: 在Activity的onstop和onresume里分别把setJavaScriptEnabled();给设置成false和true。

7.怎么用网页的标题来设置自己的标题栏?

WebChromeClient mWebChromeClient = new WebChromeClient() {    
    @Override    
    public void onReceivedTitle(WebView view, String title) {    
        super.onReceivedTitle(view, title);    
        txtTitle.setText(title);    
    }    
};  

mWedView.setWebChromeClient(mWebChromeClient());

但是发现在部分的手机上,当通过webview.goBack()回退的时候,并没有触发onReceiveTitle(),这样会导致标题仍然是之前子页面的标题,没有切换回来.

这里可以分两种情况去处理: 1) 可以确定webview中子页面只有二级页面,没有更深的层次,这里只需要判断当前页面是否为初始的主页面,可以goBack的话,只要将标题设置回来即可. 2)webview中可能有多级页面或者以后可能增加多级页面,这种情况处理起来要复杂一些: 因为正常顺序加载的情况onReceiveTitle是一定会触发的,所以就需要自己来维护webview loading的一个url栈及url与title的映射关系。那么就需要一个ArrayList来保持加载过的url,一个HashMap保存url及对应的title。 正常顺序加载时,将url和对应的title保存起来,webview回退时,移除当前url并取出将要回退到的web 页的url,找到对应的title进行设置即可。

这里还要说一点,当加载出错的时候,比如无网络,这时onReceiveTitle中获取的标题为 找不到该网页,因此建议当触发onReceiveError时,不要使用获取到的title.

8.怎么隐藏缩放控件?

if (DeviceUtils.hasHoneycomb()) {
      mWebView.getSettings().setDisplayZoomControls(false);  
}
最好把这两句加上
mWebView.getSettings().setSupportZoom(true);  
mWebView.getSettings().setBuiltInZoomControls(true);

9.怎么知道WebView是否已经滚动到页面底端?

if (mWebView.getContentHeight() * mWebView.getScale()  
    == (mWebView.getHeight() + mWebView.getScrollY())) {
    }

附上官网对几个方法的注释

getContentHeight() @return the height of the HTML content
getScale() @return the current scale
getHeight() @return The height of your view
getScrollY() @return The top edge of the displayed part of your view, in pixels.

10.怎么清理cache 和历史记录?

mWebView.clearCache(true);   
mWebView.clearHistory();

11.怎么清理Cookie?

CookieSyncManager.createInstance(this);   
CookieSyncManager.getInstance().startSync();   
CookieManager.getInstance().removeSessionCookie();

12.为什么打包之后JS调用失败? 原因:没在proguard-rules.pro中添加混淆吧

-keep class con.demo.activity.web.utils.JsShareInterface {
   public void share(java.lang.String);
}

13.WebView页面中播放了音频,退出Activity后音频仍然在播放

需要在Activity的onDestory()中调用以下方法

1.  webView.destroy();  

但可能会出现报错:

10-10 15:01:11.402: E/ViewRootImpl(7502): sendUserActionEvent() mView == null  
10-10 15:01:26.818: E/webview(7502): java.lang.Throwable: Error: WebView.destroy() called while still attached!  
10-10 15:01:26.818: E/webview(7502):    at android.webkit.WebViewClassic.destroy(WebViewClassic.java:4142)  
10-10 15:01:26.818: E/webview(7502):    at android.webkit.WebView.destroy(WebView.java:707)  
10-10 15:01:26.818: E/webview(7502):    at com.didi.taxi.ui.webview.OperatingWebViewActivity.onDestroy(OperatingWebViewActivity.java:236)  
10-10 15:01:26.818: E/webview(7502):    at android.app.Activity.performDestroy(Activity.java:5543)  
10-10 15:01:26.818: E/webview(7502):    at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1134)  
10-10 15:01:26.818: E/webview(7502):    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3619)  
10-10 15:01:26.818: E/webview(7502):    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3654)  
10-10 15:01:26.818: E/webview(7502):    at android.app.ActivityThread.access$1300(ActivityThread.java:159)  
10-10 15:01:26.818: E/webview(7502):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1369)  
10-10 15:01:26.818: E/webview(7502):    at android.os.Handler.dispatchMessage(Handler.java:99)  
10-10 15:01:26.818: E/webview(7502):    at android.os.Looper.loop(Looper.java:137)  
10-10 15:01:26.818: E/webview(7502):    at android.app.ActivityThread.main(ActivityThread.java:5419)  
10-10 15:01:26.818: E/webview(7502):    at java.lang.reflect.Method.invokeNative(Native Method)  
10-10 15:01:26.818: E/webview(7502):    at java.lang.reflect.Method.invoke(Method.java:525)  
10-10 15:01:26.818: E/webview(7502):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)  
10-10 15:01:26.818: E/webview(7502):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)  
10-10 15:01:26.818: E/webview(7502):    at dalvik.system.NativeStart.main(Native Method)  

webview调用destory时,webview仍绑定在Activity上.这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview:

rootLayout.removeView(webView);  
webView.destroy(); 

14.处理WebView中的非超链接请求(如Ajax请求) 有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(String url,HashMap headers)方法添加请求头 目前用了一个临时的办法解决: 首先需要在url中加特殊标记/协议, 如在onWebViewResource方法中拦截对应的请求,然后将要添加的请求头,以get形式拼接到url末尾 在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求,比如加载JS,图片以及Ajax请求等等

    @SuppressLint("NewApi")  
    @Override  
    public WebResourceResponse shouldInterceptRequest(WebView view,String url) {  
        // 非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例  

        String ajaxUrl = url;  
        // 如标识:req=ajax  
        if (url.contains("req=ajax")) {  
           ajaxUrl += "&imei=" + imei;  
        }  

        return super.shouldInterceptRequest(view, ajaxUrl);  

    }  

15.屏蔽webview长按事件,因为webview长按时将会调用系统的复制控件

mWebView.setOnLongClickListener(new OnLongClickListener() {  

          @Override  
          public boolean onLongClick(View v) {  
              return true;  
          }  
      });  

16.在页面中先显示图片

@Override  
public void onLoadResource(WebView view, String url) {  
  mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_LOAD_RESOURCE, url);  
    if (url.indexOf(".jpg") > 0) {  
     hideProgress(); //请求图片时即显示页面  
     mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_HIDE_PROGRESS, view.getUrl());  
     }  
    super.onLoadResource(view, url);  
}  

17.为WebView自定义错误显示界面 覆写WebViewClient中的onReceivedError()方法:

/** 
 * 显示自定义错误提示页面,用一个View覆盖在WebView 
 */  
protected void showErrorPage() {  
    LinearLayout webParentView = (LinearLayout)mWebView.getParent();  

    initErrorPage();  
    while (webParentView.getChildCount() > 1) {  
        webParentView.removeViewAt(0);  
    }  
    LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);  
    webParentView.addView(mErrorView, 0, lp);  
    mIsErrorPage = true;  
}  
protected void hideErrorPage() {  
    LinearLayout webParentView = (LinearLayout)mWebView.getParent();  

    mIsErrorPage = false;  
    while (webParentView.getChildCount() > 1) {  
        webParentView.removeViewAt(0);  
    }  
}  


   protected void initErrorPage() {  
    if (mErrorView == null) {  
        mErrorView = View.inflate(this, R.layout.online_error, null);  
        Button button = (Button)mErrorView.findViewById(R.id.online_error_btn_retry);  
        button.setOnClickListener(new OnClickListener() {  
            public void onClick(View v) {  
                mWebView.reload();  
            }  
        });  
        mErrorView.setOnClickListener(null);  
    }  
}  

@Override  
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
<span style="white-space:pre">                  </span>  
<span style="white-space:pre">          </span>mErrorView.setVisibility(View.VISIBLE);  
<span style="white-space:pre">          </span>super.onReceivedError(view, errorCode, description, failingUrl);  
}          

最后提一下WebView的一些小技巧: 1.webview的创建也是有技巧的,最好不要在layout.xml中使用webview,可以通过一个viewgroup容器,使用代码动态往容器里addview(webview),这样可以在onDestory()里销毁掉webview及时清理内存,另外需要注意创建webview需要使用applicationContext而不是activity的context,销毁时不再占有activity对象,这个大家应该都知道了,最后离开的时候需要及时销毁webview,onDestory()中应该先从viewgroup中remove掉webview,再调用webview.removeAllViews();webview.destory();

创建

ll = new LinearLayout(getApplicationContext());   
ll.setOrientation(LinearLayout.VERTICAL);  
wv = new WebView(getApplicationContext());  

销毁

@Override  
rotected void onDestroy() {  
   ll.removeAllViews();  
   wv.stopLoading();  
   wv.removeAllViews();  
   wv.destroy();  
   wv = null;  
   ll = null;  
   super.onDestroy();  

2.另外很多人 不知道webview 实际上有自己一套完整的cookie机制的,利用好这个 可以大大增加对客户端的访问速度。

这里写图片描述

这里写图片描述

实际上cookie就是存放在这个表里的。 很多人都想要一个效果:网页更新cookie 设置完cookie以后 不刷新页面即可生效。这个在2.3以下和2.3以上要实现的方法不太一样,不过现在的安卓版本已经基本没有2.3的啦

public void updateCookies(String url, String value) {
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { // 2.3及以下
             CookieSyncManager.createInstance(getContext().getApplicationContext());
         }
         CookieManager cookieManager = CookieManager.getInstance();
         cookieManager.setAcceptCookie(true);
         cookieManager.setCookie(url, value);
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
             CookieSyncManager.getInstance().sync();
         }
     }

3.进一步的优化,activity被动被杀之后,最好能够保存webview状态,这样用户下次打开时就看到之前的状态了,嗯,就这么干,webview支持saveState(bundle)和restoreState(bundle)方法,所以就简单了,看看代码吧: 保存状态:

@Override  
protected void onSaveInstanceState(Bundle outState) {  
super.onSaveInstanceState(outState);  
wv.saveState(outState);  
Log.e(TAG, "save state...");  
}  

恢复状态: 在activity的onCreate(bundle savedInstanceState)里,这么调用:

if(null!=savedInstanceState){  
    wv.restoreState(savedInstanceState);  
    Log.i(TAG, "restore state");  
}else{  
    wv.loadUrl("http://3g.cn");  
}  

4.WebView 图片延迟加载: 有些页面如果包含网络图片,在移动设备上我们等待加载图片的时间可能会很长,所以我们需要让图片延时加载,这样不影响我们加载页面的速度: 定义变量:

boolean blockLoadingNetworkImage=false;

在WebView初始化的时候设置:

blockLoadingNetworkImage = true;

webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
                return true;
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                if (!blockLoadingNetworkImage){
                    webView.getSettings().setBlockNetworkImage(true);
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                if (blockLoadingNetworkImage){
                    webView.getSettings().setBlockNetworkImage(false);
                }
            }
        });

文末

更多关于Framework的学习资料可以扫描下方二维码免费领取!!!

在这里插入图片描述

《Android Framework源码开发揭秘》

本学习手册深入剖析了Android系统源代码,详细讲解了Android框架初始化过程及主要组件的工作原理,旨在通过实例和案例介绍 Android Framework 的核心概念和技术,从而帮助开发者更好地理解 Android 应用程序的设计和开发。 该手册适合具有一定 Android 应用开发经验的程序员,希望能通过深入学习 Android Framework 来帮助开发者更好地理解和掌握这一技术。

img

在这里插入图片描述

第一章 系统启动流程分析

Android系统完整的启动过程,从系统层次角度可分为 Linux 系统层、Android 系统服务层、Zygote进程模型三个阶段; 知识要点:

  • 第一节 Android启动概括、

  • 第二节 init.rc解析、

  • 第三节 Zygote、

  • 第四节 面试题

img

第二章 跨进程通信IPC解析

Binder作为Android进程间通信的机制,可以看做是一个驱动。在Android中,常见的进程间通信例如系统类的:打电话、闹钟等;自己创建的:像WebView、视频播放、音频播放、大图浏览等。

img

第三章 Handler源码解析

  • 第一节 源码分析
  • 第二节 难点问题
  • 第三节Handler常问面试题

在这里插入图片描述

第四章 AMS源码解析
  • 第一节 引言
  • 第二节 Android架构
  • 第三节 通信方式
  • 第四节 系统启动系列
  • 第五节 AMS
  • 第六节 AMS面试题解析在这里插入图片描述
第五章 WMS源码解析
  • 第一节 WMS与activity启动流程
  • 第二节 WMS绘制原理
  • 第三节 WMS角色与实例化过程
  • 第四节 WMS工作原理在这里插入图片描述
第六章 Surface源码解析
  • 第一节 创建流程及软硬件绘制
  • 第二节 双缓冲及Surface View解析
  • 第三节 Android图形系统综述在这里插入图片描述
第七章 基于Android12.0的SurfaceFlinger源码解析
  • 第一节 应用建立和SurfaceFlinger的沟通桥梁
  • 第二节 SurfaceFlinger的启动和消息队列处理机制
  • 第三节 SurfaceFlinger之VSyns(上)
  • 第四节 SurfaceFlinger之VSyns(中)
  • 第五节 SurfaceFlinger之VSyns(下)在这里插入图片描述
第八章 PKMS源码解析
  • 第一节 PKMS调用方式
  • 第二节 PKMS启动过程分析
  • 第三节 APK的扫描
  • 第四节 APK的安装
  • 第五节 PKMS之权限扫描
  • 第六节 静默安装
  • 第七节 requestPermissions源码流程解析
  • 第八节 PKMS面试题在这里插入图片描述
第九章 InputManagerService源码解析
  • 第一节 Android Input输入事件处理流程(1)
  • 第二节 Android Input输入事件处理流程(2)
  • 第三节 Android Input输入事件处理流程(3)在这里插入图片描述
第十章 DisplayManagerService源码解析
  • 第一节 DisplayManagerService启动
  • 第二节 DisplayAdepter和DisplayDevice的创建
  • 第三节 DMS部分亮灭屏流程
  • 第四节 亮度调节
  • 第五节 Proximity Sensor灭屏原理
  • 第六节 Logical Display和Physical Display配置的更新在这里插入图片描述

搭建了一个基于chatGPT的微信群聊机器人,24小时为大家解答疑难技术问题, 需要的可以扫描二维码进群。

图片

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值