精华内容
下载资源
问答
  • 记得好久以前分析了robotium对native控件的支持,最近在研究webview的测试,所以继续看下robotium对webview部分的支持。那么我们稍微复习一下上次分析到结论: 1.利用decoderView获取控件树的方式完成对控件的查找...

    记得好久以前分析了robotium对native控件的支持,最近在研究webview的测试,所以继续看下robotium对webview部分的支持。那么我们稍微复习一下上次分析到结论:
    1.利用decoderView获取控件树的方式完成对控件的查找
    2.最后调用的instrumentation的inject注入event事件完成对控件的操作
    3.利用junit的assert类或者hamcreast框架进行结果判断。
    上一篇详情可以查看:Robotium整体源码浅析
    (写的没这次好,毕竟水平是越来越高的是不,看有没有时间重新分析下 :) )

    照着这个思路,今天一起来学习下robotium中webview的部分
    源码地址:robotiumgit主页

    概览

    打开工程目录,涉及到的web相关的内容就是下图表示的这几个
    这里写图片描述

    RobotiumWeb.js:

    这个js文件是要注入到webview里面的,定义了一堆方法,用来查找元素和操作元素

    RobotiumWebClient.java:

    继承WebChromeClient类,主要是为了重写onJsPrompt方法,把查找的元素信息通过回调给到robotium,然后进行封装

    WebElement.java:

    把查找到的元素信息封装成实体类,这个就是那个类了

    WebElementCreator.java:

    看名字就知道是创建webelement的,把解析过来的element信息string,包装成WebElement对象

    WebUtils

    工具类,入口类,包含执行查找,js注入的函数,文本注入等,需要重点关注的入口

    最后,提上和webview相关的api
    这里写图片描述
    webview支持的原理离不开上面这个api了,这几个api排除重载的方法后,可以分成下面一下几个:
    - 查找所有或者当前屏幕的的webElement对象,返回列表
    - 查找一个webElement对象,返回这个element对象
    - 注入文字
    - 点击Element
    - 等待element元素出现

    查找

    查找全部元素

    首先是solo.getCurrentWebElements,这个方法和getWebElements一致,只是是否可见作为一个形参传入,所以直接看solo.getWebElements这个方法。

    public ArrayList<WebElement> getWebElements(boolean onlySufficientlyVisible){
            boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");
    
            return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
        }

    那么跟踪这个方法,最终调用的是executeJavaScriptFunction这个方法,executeJavaScriptFunction就是执行js注入的方法:

    private boolean executeJavaScriptFunction(final String function) {
            List<WebView> webViews = viewFetcher.getCurrentViews(WebView.class, true);
            final WebView webView = viewFetcher.getFreshestView((ArrayList<WebView>) webViews);
    
            if(webView == null) {
                return false;
            }
    
            final String javaScript = setWebFrame(prepareForStartOfJavascriptExecution(webViews));
    
            inst.runOnMainSync(new Runnable() {
                public void run() {
                    if(webView != null){
                        webView.loadUrl("javascript:" + javaScript + function);
                    }
                }
            });
            return true;
        }

    那么上面可以看到主要做了有下面3个事情:

    • 通过getCurrentViews查找所有WebView.class对象,并通过绘制时间(View.getDrawingTime())判断哪个才是最近使用的webview,作为被测的webview对象
    • 初始化WebView对象,设置允许加载js脚本,并把WebChromeClient设置为robotiumWebCLient,并读入RobotiumWeb.js
    • 然后通过webview.loadUrl方式注入js脚本,执行查找操作,结果通过robotiumWebCLient重写的onJsPromtp方法读取出查找的内容

    所以,重点就变成这个注入的js脚本是什么,直接打开RobotiumWeb.js
    这里写图片描述
    那么可以看到这里定义了一堆方法,看这些名称,看起来功能实现不就是和一开始的api相呼应么,看到这里也就明白了,执行js注入查找,本质上是通过dom里面的api查找的,我们打开allWebElements这个js函数看下:

    function allWebElements() {
        for (var key in document.all){
            try{
                promptElement(document.all[key]);           
            }catch(ignored){}
        }
        finished();
    }

    嗯,到这里终于明确了:

    • document.all方法获取html所有元素
    • promptElement方法把取到的元素解析,用;,分割合并成一个string,最后调用prompt方法发送出来(后续使用onJsprompt回调方式取到内容),这个方法的截取部分实现如下:
    prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height + ';,' + attributes);
    
    • 最终,调用finish()方法,finish方法也是发送prompt消息,标记为完成
    function finished(){
        prompt('robotium-finished');
    }

    好了,有了上面的分析,我们很明确下一部就是要把prompt发出来的消息接收并封装成WebElement对象
    在executeJavaScriptFunction方法中我们有一步是初始化WebChromeView,这一步使用的是robotium自定义的robotiumWebCLient对象,里面就有重写的onJsPrompt方法。

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
    
            if(message != null && (message.contains(";,") || message.contains("robotium-finished"))){
    
                if(message.equals("robotium-finished")){
                    webElementCreator.setFinished(true);
                }
                else{
                    webElementCreator.createWebElementAndAddInList(message, view);
                }
                r.confirm();
                return true;
            }
            else {
                if(originalWebChromeClient != null) {
                    return originalWebChromeClient.onJsPrompt(view, url, message, defaultValue, r); 
                }
                return true;
            }
    
        }

    这个方法最后调用了WebElementCreator.createWebElementAndAddInList方法:

    • 拿到prompt的message,也就是包含Element信息的string,并解析
    • 封装成WebElement对象

    最后,好像好剩下一个方法:getCurrentWebElements
    这个方法和getWebElements相比,在返回element列表过程中多加了一步过滤,做的工作是把Element的x,y坐标和webview坐标做比较,把坐标在webview之外的过滤掉,也就是返回的是当前展示的内容啦。

    好的,查找核心的东西就这么多了,然后上面是查找全部或者一组,那么查找单个也是同理的,在js文件中封装id、xpath、cssSelector等方法,这些方式刚好都是调用document相关的api,写web端的同学相信会非常熟悉这些定位方式。

    点击

    robotium对webview的点击支持两种方式,一种和native一样,点击坐标点;另一种则是js脚本的方式。
    坐标点:
    在RobotiumWeb.js的promptElement方法中,会调用:
    ”’
    var rect = element.getBoundingClientRect();
    ”’
    这就可以得到元素的x,y坐标啦,然后就可以调用solo.clickOnScreen方法点击了
    js脚本:
    ”’
    function clickElement(element){
    var e = document.createEvent(‘MouseEvents’);
    e.initMouseEvent(‘click’, true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
    element.dispatchEvent(e);
    }
    ”’
    需要注意的是,solo其实并没有提供入口给我们使用第二种方法点击,如果需要使用js方式点击,需要在初始化的时候配置config对象,默认都是会去查找然后转成坐标点进行操作的。

    文本 && 其他 相关

    这里写图片描述
    这是solo提供关于text的api,所以提供了两种方式注入文本
    enterTextInWebElement
    typeTextInWebElement
    有啥区别呢,对于enterxxx来说:
    这里写图片描述
    是调用类似setText的方法设置的,在robotiumWeb.js可以看到是直接设置By定位到的元素的value设置的,而typeTextInWebElement方式则是:
    ”’
    public void typeTextInWebElement(By by, String text, int match){
    if(config.commandLogging){
    Log.d(config.commandLoggingTag, “typeTextInWebElement(“+by+”, \”“+text+”\”, “+match+”)”);
    }

        clicker.clickOnWebElement(by, match, true, false);
        dialogUtils.hideSoftKeyboard(null, true, true);
        instrumentation.sendStringSync(text);
    }
    

    ”’
    点击–隐藏弹出的输入法–调用instrumentation.sendStringSync方法,这个方法是拆分组个字符,然后输入,也就是会看到输入轨迹。
    最后一个清除text,用的是enterTextIntoWebElement方法,就是把value设置为“”,咱们理解成setText(“”)就行了。

    还有最后一个api没说,waitForWebElement

        public WebElement waitForWebElement(final By by, int minimumNumberOfMatches, int timeout, boolean scroll){
            final long endTime = SystemClock.uptimeMillis() + timeout;
    
            while (true) {  
    
                final boolean timedOut = SystemClock.uptimeMillis() > endTime;
    
                if (timedOut){
                    searcher.logMatchesFound(by.getValue());
                    return null;
                }
                sleeper.sleep();
    
                WebElement webElementToReturn = searcher.searchForWebElement(by, minimumNumberOfMatches); 
    
                if(webElementToReturn != null)
                    return webElementToReturn;
    
                if(scroll) {
                    scroller.scrollDown();
                }
            }
        }

    这个等待元素出现的比较简单,循环查找并等待超时,自带向下滚动操作,超时就返回null,否则返回找到的元素,当然,这个可以做成断言的,assertthat(xxx,Matcher.isNotNull())用来判断界面元素的展示与否。

    总结

    好了,分析得差不多了,最后来总结一下。

    robotium对webview是支持的,原理是通过获取最近绘制的webview.class作为当前测试的webview,并通过js注入的方式获取webview里面的Element信息,通过prompt回调的方式通讯,获取到元素信息后之后通过onJSPrompt回调取得元素信息,并封装成WebElement对象,最后取得element对象的坐标点调用clickOncreen方法实现点击,当然也可以通过配置config对象让robotium通过dispatchEvent方式点击,不过不是很推荐。其他的类似文本输入可以通过js注入然后domcument的相关api设置value值方式,也可以调用instarumentation的sendKey方式输入文本。

    展开全文
  • ScrollView-Nested-Problems点击打开链接解决Android中出现ScrollView嵌套 ListView、RecyclerView、GridView 、WebView出现的高度问题。开篇语:最近开始想写一些技术总结了,一方面分享给其他同学,另一方面也作为...

    ScrollView-Nested-Problems点击打开链接

    解决Android中出现ScrollView嵌套 ListView、RecyclerView、GridView 、WebView出现的高度问题。

    开篇语:最近开始想写一些技术总结了,一方面分享给其他同学,另一方面也作为自己的技术积累。 今天我分享的是日常遇到的问题,ScrollView组件里面嵌套GridView、WebView、ListView等本身具有滑动的组件时,所引发的高度显示不全的问题。

    对于GridView、WebView、ListView这三类组件来说,大家都知道通常情况下这三类组件本身是具有滑动特性的,其实际占用的高度也只是屏幕内显示的高度,当嵌套在ScrollView组件里时就造成了冲突。 所以解决的思路可以从其onMeasure方法入手,onMeasure方法是重写自定义View中用到的一个非常重要的方法,onMeasure方法的作用就是计算出自定义View的宽度和高度,这个计算的过程参照父布局给出的大小,以及自己特点算出结果。onMeasure方法是在父视图计算子视图大小时被调用的,其中的细节就不在此详细讲述。

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, mExpandSpec);
        
    }
    

       

    上面方法的2个参数,来自于父视图发过来给子视图的限制条件,这涉及到一个知识点MeauseSpec这个类.

    一.MeasureSpec的构成

    MeasureSpec代表一个32位的int值,前俩位代表SpecMode,后30位代表SpecSize.其中:SpecMode代表测量的模式,SpecSize值在某种测量模式下的规格大小。

    共有三种测量模式:

    1.EXACTLY: 父容器已经检测出子View所需要的精确大小,这个时候view的大小即为SpecSize的大小,他对应于布局参数中的MATCH_PARENT,或者精确大小值;

    2.AT_MOST: 父容器指定了一个大小,即SpecSize,子view的大小不能超过这个SpecSize的大小;

    3.UNSPECIFIED: 父容器对子View的大小没有任何要求,子View想多大都可以。

    二.如何创建MeasureSpec MeasureSpec内部提供了创建MeasureSpec的方法:

    public static int makeMeasureSpec(int size, int mode) {

            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
            
        }
    

    private static final int MODE_SHIFT = 30;

    private static final int MODE_MASK = 0x3 << MODE_SHIFT; 二进制 1100....00 32位

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;   二进制 0000....00 32位

    public static final int EXACTLY     = 1 << MODE_SHIFT;   二进制 0100....00 32位

    public static final int AT_MOST = 2 << MODE_SHIFT; 二进制 1000....00 32位

    MeasureSpec代表一个32位的int值,前俩位代表SpecMode,后30位代表SpecSize.通过巧妙的位运算,即可通过MeasureSpec来得到SpecSize,SpecMode.

    public static int getMode(int measureSpec) {

            return (measureSpec & MODE_MASK);  
    

          }   public static int getSize(int measureSpec) {

            return (measureSpec & ~MODE_MASK);
        }
    

    对于RecyclerView来说,重写onMeasure()方法就不管用了。

    1.一种解决办法是在RecyclerView的外部套上一层RelativeLayout

    <RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:descendantFocusability="blocksDescendants">
     </RelativeLayout>
    

    android:descendantFocusability属性的值有三种:

    beforeDescendants:viewgroup会优先其子类控件而获取到焦点

    blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

    afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点

    但是这个方案recyclerView时有卡顿的问题 原因还是滑动冲突的问题,可以重写LinearLayoutManager,设置让其不可滑动,外部滑动靠ScrollView,这样就解决了滑动时卡顿的问题

    代码如下: public class ScrollLinearLayoutManager extends LinearLayoutManager { private boolean isScrollEnabled = true;

        public ScrollLinearLayoutManager(Context context) {
            super(context);
        }
    
        public ScrollLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
        }
    
        public ScrollLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        public void setScrollEnabled(boolean flag) {
            this.isScrollEnabled = flag;
        }
    
        @Override
        public boolean canScrollVertically() {
            return isScrollEnabled && super.canScrollVertically();
        }
    }
    

    简单使用: ScrollLinearLayoutManager scrollLinearLayoutManager = new ScrollLinearLayoutManager(this);

    scrollLinearLayoutManager.setScrollEnabled(false);
    mRecyclerView.setLayoutManager(scrollLinearLayoutManager);
    

    2.完美方案是这样的:首先在xml布局中将你的ScrollView替换成android.support.v4.widget.NestedScrollView,并在java代码中设置recyclerView.setNestedScrollingEnabled(false);

    展开全文
  • Android源码下的WebView分析

    千次阅读 2013-09-03 22:42:33
    Android的WebView.java是一个内置的支持浏览器的视图View,查看源码目录frameworks\base\core\java\android\webkit 下面有多个java源文件,第一个为WebView.java,这个类可不小,将近8000行;  WebView provide

    浏览器在各个平台都有,最容易拿到源码的就是Android的;虽然有现成的可用,还是自己分析一下;

    Android的WebView.java是一个内置的支持浏览器的视图View,查看源码目录frameworks\base\core\java\android\webkit

    下面有多个java源文件,第一个为WebView.java,这个类可不小,将近8000行;

     WebView provides no browser-like widgets, does not  enable JavaScript and web page errors are ignored. If your goal is only
     to display some HTML as a part of your UI, this is probably fine

    但是该WebView只支持展示Web Page,不支持JavaScript和页面内容;不过可以借鉴一下进行改造。

    @Widget
    public class WebView extends AbsoluteLayout
            implements ViewTreeObserver.OnGlobalFocusChangeListener,
            ViewGroup.OnHierarchyChangeListener {

    WebView继承AbsoluteLayout,实现OnGlobalFocusChangeListener和OnHierarchyChangeListener

    构造函数

     protected WebView(Context context, AttributeSet attrs, int defStyle,
                Map<String, Object> javascriptInterfaces) {
            super(context, attrs, defStyle);
            init();//初始化

            mCallbackProxy = new CallbackProxy(context, this);//回调
            mViewManager = new ViewManager(this);//View manager
            mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); //WebViewCore
            mDatabase = WebViewDatabase.getInstance(context);//WebViewDataBase
            mScroller = new OverScroller(context);

            updateMultiTouchSupport(context);
        }

      看看如下几个方法

    private void init() {
            setWillNotDraw(false);
            setFocusable(true);
            setFocusableInTouchMode(true);
            setClickable(true);
            setLongClickable(true);

            final ViewConfiguration configuration = ViewConfiguration.get(getContext());
            int slop = configuration.getScaledTouchSlop();
            mTouchSlopSquare = slop * slop;
            mMinLockSnapReverseDistance = slop;
            slop = configuration.getScaledDoubleTapSlop();
            mDoubleTapSlopSquare = slop * slop;
            final float density = getContext().getResources().getDisplayMetrics().density;
            // use one line height, 16 based on our current default font, for how
            // far we allow a touch be away from the edge of a link
            mNavSlop = (int) (16 * density);
            // density adjusted scale factors
            DEFAULT_SCALE_PERCENT = (int) (100 * density);
            mDefaultScale = density;
            mActualScale = density;
            mInvActualScale = 1 / density;
            mTextWrapScale = density;
            DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
            DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
            mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
            mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
            mMaximumFling = configuration.getScaledMaximumFlingVelocity();
            mOverscrollDistance = configuration.getScaledOverscrollDistance();
            mOverflingDistance = configuration.getScaledOverflingDistance();
        }

      void updateMultiTouchSupport(Context context) {
            WebSettings settings = getSettings();
            final PackageManager pm = context.getPackageManager();
            mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
                    && settings.supportZoom() && settings.getBuiltInZoomControls();
            mAllowPanAndScale = pm.hasSystemFeature(
                    PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
            if (mSupportMultiTouch && (mScaleDetector == null)) {
                mScaleDetector = new ScaleGestureDetector(context,
                        new ScaleDetectorListener());
            } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
                mScaleDetector = null;
            }
        }

       

       一个大方法,该PrivateHandler内部多达600多行代码

     /**
         * General handler to receive message coming from webkit thread
         */
        class PrivateHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {

      

        还有一个DataSetObserver

    private class SingleDataSetObserver extends DataSetObserver {

    最后面多个被JNI调用的方法

    和native的接口方法

     private native int nativeCacheHitFramePointer();
        private native Rect nativeCacheHitNodeBounds();
        private native int nativeCacheHitNodePointer();
        /* package */ native void nativeClearCursor();
        private native void     nativeCreate(int ptr);
        private native int      nativeCursorFramePointer();
        private native Rect     nativeCursorNodeBounds();
        private native int nativeCursorNodePointer();
        /* package */ native boolean nativeCursorMatchesFocus();
        private native boolean  nativeCursorIntersects(Rect visibleRect);
        private native boolean  nativeCursorIsAnchor();
        private native boolean  nativeCursorIsTextInput();
        private native Point    nativeCursorPosition();
        private native String   nativeCursorText();
        /**
         * Returns true if the native cursor node says it wants to handle key events
         * (ala plugins). This can only be called if mNativeClass is non-zero!
         */
        private native boolean  nativeCursorWantsKeyEvents();
        private native void     nativeDebugDump();
        private native void     nativeDestroy();
        private native boolean  nativeEvaluateLayersAnimations();
        private native void     nativeExtendSelection(int x, int y);
        private native void     nativeDrawExtras(Canvas canvas, int extra);
        private native void     nativeDumpDisplayTree(String urlOrNull);
        private native int      nativeFindAll(String findLower, String findUpper);
        private native void     nativeFindNext(boolean forward);
        /* package */ native int      nativeFocusCandidateFramePointer();
        /* package */ native boolean  nativeFocusCandidateHasNextTextfield();
        /* package */ native boolean  nativeFocusCandidateIsPassword();
        private native boolean  nativeFocusCandidateIsRtlText();
        private native boolean  nativeFocusCandidateIsTextInput();
        /* package */ native int      nativeFocusCandidateMaxLength();
        /* package */ native String   nativeFocusCandidateName();
        private native Rect     nativeFocusCandidateNodeBounds();
        /**
         * @return A Rect with left, top, right, bottom set to the corresponding
         * padding values in the focus candidate, if it is a textfield/textarea with
         * a style.  Otherwise return null.  This is not actually a rectangle; Rect
         * is being used to pass four integers.
         */
        private native Rect     nativeFocusCandidatePaddingRect();
        /* package */ native int      nativeFocusCandidatePointer();
        private native String   nativeFocusCandidateText();
        private native int      nativeFocusCandidateTextSize();
        /**
         * Returns an integer corresponding to WebView.cpp::type.
         * See WebTextView.setType()
         */
        private native int      nativeFocusCandidateType();
        private native boolean  nativeFocusIsPlugin();
        private native Rect     nativeFocusNodeBounds();
        /* package */ native int nativeFocusNodePointer();
        private native Rect     nativeGetCursorRingBounds();
        private native String   nativeGetSelection();
        private native boolean  nativeHasCursorNode();
        private native boolean  nativeHasFocusNode();
        private native void     nativeHideCursor();
        private native boolean  nativeHitSelection(int x, int y);
        private native String   nativeImageURI(int x, int y);
        private native void     nativeInstrumentReport();
        /* package */ native boolean nativeMoveCursorToNextTextInput();
        // return true if the page has been scrolled
        private native boolean  nativeMotionUp(int x, int y, int slop);
        // returns false if it handled the key
        private native boolean  nativeMoveCursor(int keyCode, int count,
                boolean noScroll);
        private native int      nativeMoveGeneration();
        private native void     nativeMoveSelection(int x, int y);
        private native boolean  nativePointInNavCache(int x, int y, int slop);
        // Like many other of our native methods, you must make sure that
        // mNativeClass is not null before calling this method.
        private native void     nativeRecordButtons(boolean focused,
                boolean pressed, boolean invalidate);
        private native void     nativeResetSelection();
        private native void     nativeSelectAll();
        private native void     nativeSelectBestAt(Rect rect);
        private native int      nativeSelectionX();
        private native int      nativeSelectionY();
        private native int      nativeFindIndex();
        private native void     nativeSetExtendSelection();
        private native void     nativeSetFindIsEmpty();
        private native void     nativeSetFindIsUp(boolean isUp);
        private native void     nativeSetFollowedLink(boolean followed);
        private native void     nativeSetHeightCanMeasure(boolean measure);
        private native void     nativeSetRootLayer(int layer);
        private native void     nativeSetSelectionPointer(boolean set,
                float scale, int x, int y);
        private native boolean  nativeStartSelection(int x, int y);
        private native void     nativeSetSelectionRegion(boolean set);
        private native Rect     nativeSubtractLayers(Rect content);
        private native int      nativeTextGeneration();
        // Never call this version except by updateCachedTextfield(String) -
        // we always want to pass in our generation number.
        private native void     nativeUpdateCachedTextfield(String updatedText,
                int generation);
        private native boolean  nativeWordSelection(int x, int y);
        // return NO_LEFTEDGE means failure.
        private static final int NO_LEFTEDGE = -1;
        private native int      nativeGetBlockLeftEdge(int x, int y, float scale);

     

    另外一个类WebViewCore.java,代码大概2000多行

    static {
            // Load libwebcore during static initialization. This happens in the
            // zygote process so it will be shared read-only across all app
            // processes.
            try {
                System.loadLibrary("webcore");
            } catch (UnsatisfiedLinkError e) {
                Log.e(LOGTAG, "Unable to load webcore library");
            }
        }

    典型的加载native的code的代码库webcore

    看下构造行数

     public WebViewCore(Context context, WebView w, CallbackProxy proxy,
                Map<String, Object> javascriptInterfaces) {
            // No need to assign this in the WebCore thread.
            mCallbackProxy = proxy;
            mWebView = w;
            mJavascriptInterfaces = javascriptInterfaces;
            // This context object is used to initialize the WebViewCore during
            // subwindow creation.
            mContext = context;

            // We need to wait for the initial thread creation before sending
            // a message to the WebCore thread.
            // XXX: This is the only time the UI thread will wait for the WebCore
            // thread!
            synchronized (WebViewCore.class) {
                if (sWebCoreHandler == null) {
                    // Create a global thread and start it.
                    Thread t = new Thread(new WebCoreThread());
                    t.setName(THREAD_NAME);
                    t.start();
                    try {
                        WebViewCore.class.wait();
                    } catch (InterruptedException e) {
                        Log.e(LOGTAG, "Caught exception while waiting for thread " +
                               "creation.");
                        Log.e(LOGTAG, Log.getStackTraceString(e));
                    }
                }
            }
            // Create an EventHub to handle messages before and after the thread is
            // ready.
            mEventHub = new EventHub();
            // Create a WebSettings object for maintaining all settings
            mSettings = new WebSettings(mContext, mWebView);
            // The WebIconDatabase needs to be initialized within the UI thread so
            // just request the instance here.
            WebIconDatabase.getInstance();
            // Create the WebStorage singleton and the UI handler
            WebStorage.getInstance().createUIHandler();
            // Create the UI handler for GeolocationPermissions
            GeolocationPermissions.getInstance().createUIHandler();
            // Send a message to initialize the WebViewCore.
            Message init = sWebCoreHandler.obtainMessage(
                    WebCoreThread.INITIALIZE, this);
            sWebCoreHandler.sendMessage(init);
        }

       该类中存在大量的JNI方法

      一个WebCoreThread

       private static class WebCoreThread implements Runnable {

    public void run() {
                Looper.prepare();
                Assert.assertNull(sWebCoreHandler);
                synchronized (WebViewCore.class) {
                    sWebCoreHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                            switch (msg.what) {
                                case INITIALIZE:
                                    WebViewCore core = (WebViewCore) msg.obj;
                                    core.initialize();
                                    break;

                                case REDUCE_PRIORITY:
                                    // 3 is an adjustable number.
                                    Process.setThreadPriority(
                                            Process.THREAD_PRIORITY_DEFAULT + 3 *
                                            Process.THREAD_PRIORITY_LESS_FAVORABLE);
                                    break;

                                case RESUME_PRIORITY:
                                    Process.setThreadPriority(
                                            Process.THREAD_PRIORITY_DEFAULT);
                                    break;
                            }
                        }
                    };
                    WebViewCore.class.notify();
                }
                Looper.loop();
            }

       事件处理类

     class EventHub {

     转换消息方法

    private void transferMessages() {
                mTid = Process.myTid();
                mSavedPriority = Process.getThreadPriority(mTid);

                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        if (DebugFlags.WEB_VIEW_CORE) {

     多达400行;

     

     WebView的官方文档

    http://developer.android.com/reference/android/webkit/WebView.html

    专注设计,专注实现。

    需要权限:

    <uses-permission android:name="android.permission.INTERNET" />

    用例1:

    点击某个按钮或者图片,触发显示对应的网页

     Uri uri = Uri.parse("http://www.example.com"); 
     
    Intent intent = new Intent(Intent.ACTION_VIEW, uri); 
     startActivity
    (intent);


    用例2:

     WebView控件加载指定的URL的网页

    webview.loadUrl("http://slashdot.org/"); 
     
     
    // OR, you can also load from an HTML string: 
     
    String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 
     webview
    .loadData(summary, "text/html", null);

    使用案例:

    创建设置WebChromeClient子类或者WebViewClient子类

    修改WebSetting,setJavaScriptEnabled

    在WebView中注入Java Object

    addJavaScriptInterface(Object,String),该对象可以在页面对应的context中的JavaScript所获取到

    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/");




    展开全文
  • webview获取html源码

    2015-08-23 21:27:56
    webview获取html源码,解析xml,完成 html和webview的交互。
  • WebViewJS源码分析以及代码 利用webView加载JS代码
  • android WebView详解,常见漏洞详解和安全源码(上)

    万次阅读 多人点赞 2017-02-12 18:19:55
    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析。  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView 的使用,下篇讲述...

      这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析。
      由于博客内容长度,这次将分为上下两篇,上篇详解 WebView 的使用,下篇讲述 WebView 的漏洞和坑,以及修复源码的解析。
      下篇:android WebView详解,常见漏洞详解和安全源码(下)
      转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371
      对技术感兴趣的同鞋加群 544645972 一起交流。

    Android Hybrid 和 WebView 解析

      现在市面上的 APP 根据类型大致可以分为 3 类:Native APP、Web APP 和 Hybrid APP,而 Hybrid APP 兼具 “Native APP 良好用户交互体验的优势”和 “Web APP 跨平台开发的优势”,现在很多的主流应用也是使用 Hybrid 模式开发的。

    Hybrid 的优势与原生的体验差距

    Hybrid 的优势

      为什么要使用 Hybrid 开发呢,这就要提到 native 开发的限制:
      1.客户端发板周期长
        众所周知,客户端的发板周期在正常情况下比较长,就算是创业公司的迭代也在一到两个星期一次,大公司的迭代周期一般都在月这个数量级别上,而且 Android 还好,iOS 的审核就算变短了也有几天,而且可能会有审核不通过的意外情况出现,所谓为了应对业务的快速发展,很多业务比如一些活动页面就可以使用 H5 来进行开发。
      2.客户端大小体积受限
        如果所有的东西都使用 native 开发,比如上面提到的活动页面,就会造成大量的资源文件要加入到 APK 中,这就造成 APK 大小增加,而且有的活动页面更新很快,造成资源文件可能只会使用一个版本,如果不及时清理,就会造成资源文件的残留。
      3.web 页面的体验问题
        使用纯 Web 开发,比以前迭代快速很多,但是从某种程度上来说,还是不如原生页面的交互体验好;
      4.无法跨平台
        一般情况下,同一样的页面在 android 和 iOS 上需要写两份不同的代码,但是现在只需要写一份即可,Hybrid 具有跨平台的优势。

      所以综上这两种方式单独处理都不是特别好,考虑到发版周期不定,而且体验交互上也不能很差,所以就把两种方式综合起来,让终端和前端共同开发一个 APP,这样一些迭代很稳定的页面就可以使用原生,增加体验性;一些迭代很快速的页面就可以使用 H5,让两种优点结合起来,弥补原来单个开发模式的缺点。
    这里写图片描述

    H5 与 Native 的体验差距

      H5 和 Native 的体验差距主要在两个方面:
      1.页面渲染瓶颈
        第一个是前端页面代码渲染,受限于 JS 的解析效率,以及手机硬件设备的一些性能,所以从这个角度来说,我们应用开发者是很难从根本上解决这个问题的;
      2.资源加载缓慢
        第二个方面是 H5 页面是从服务器上下发的,客户端的页面在内存里面,在页面加载时间上面,根据网络状况的不同,H5 页面的体验和 Native 在很多情况下相比差距还是不小的,但是这种问题从某种程度上来说也是可以弥补的,比如说我们可以做一些资源预加载的方案,在资源预加载方面,其实也有很多种方式,下面主要列举了一些:

    • 第一种方式是使用 WebView 自身的缓存机制:
    • 如果我们在 APP 里面访问一个页面,短时间内再次访问这个页面的时候,就会感觉到第二次打开的时候顺畅很多,加载速度比第一次的时间要短,这个就是因为 WebView 自身内部会做一些缓存,只要打开过的资源,他都会试着缓存到本地,第二次需要访问的时候他直接从本地读取,但是这个读取其实是不太稳定的东西,关掉之后,或者说这种缓存失效之后,系统会自动把它清除,我们没办法进行控制。基于这个 WebView 自身的缓存,有一种资源预加载的方案就是,我们在应用启动的时候可以开一个像素的 WebView ,事先去访问一下我们常用的资源,后续打开页面的时候如果再用到这些资源他就可以从本地获取到,页面加载的时间会短一些。
    • 第二种方案是,我们自己去构建,自己管理缓存:
    • 把这些需要预加载的资源放在 APP 里面,他可能是预先放进去的,也可能是后续下载的,问题在于前端这些页面怎么去缓存,两个方案,第一种是前端可以在 H5 打包的时候把里面的资源 URL 进行替换,这样可以直接访问本地的地址;第二种是客户端可以拦截这些网页发出的所有请求做替换:
      这里写图片描述
      这个是美团使用的预加载方案(详情请看: 美团大众点评 Hybrid 化建设),归属于第二种加载方案,每当 WebView 发起资源请求的时候,我们会拦截这些资源的请求,去本地检查一下我们这些静态资源本地离线包有没有。针对本地的缓存文件我们有些策略能够及时的去更新它,为了安全考虑,也需要同时做一些预下载和安全包的加密工作。预下载有以下几点优势:
      1. 我们拦截了 WebView 里面发出的所有的请求,但是并没有替换里面的前端应用的任何代码,前端这套页面代码可以在 APP 内,或者其他的 APP 里面都可以直接访问,他不需要为我们 APP 做定制化的东西;
      2. 这些 URL 请求,他会直接带上先前用户操作所留下的 Cookie ,因为我们没有更改资源原始 URL 地址;
      3. 整个前端在用离线包和缓存文件的时候是完全无感知的,前端只用管写一个自己的页面,客户端会帮他处理好这样一些静态资源预加载的问题,有这个离线包的话,加载速度会变快很多,特别是在弱网情况下,没有这些离线包加载速度会慢一些。而且如果本地离线包的版本不能跟 H5 匹配的话,H5 页面也不会发生什么问题。
        实际资源预下载也确实能够有效的增加页面的加载速度,具体的对比可以去看美团的那片文章。
      那么什么地方需要使用 Native 开发,什么地方需要使用 H5 开发呢:一般来说 Hybrid 是用在一些快速迭代试错的地方,另外一些非主要产品的页面,也可以使用 Hybrid 去做;但是如果是一些很重要的流程,使用频率很高,特别核心的功能,还是应该使用 Native 开发,让用户得到一个极致的产品体验。

    WebView 详细介绍

      我们来看看 Google 官网关于 WebView 的介绍:

    A View that displays web pages. This class is the basis upon which you can roll your own web browser
     or simply display some online content within your Activity. It uses the WebKit rendering engine 
     to display web pages and includes methods to navigate forward and backward through a history, 
     zoom in and out, perform text searches and more.

    可以看到 WebView 是一个显示网页的控件,并且可以简单的显示一些在线的内容,并且基于 WebKit 内核,在 Android4.4(API Level 19) 引入了一个基于 Chromium 的新版本 WebView ,这让我们的 WebView 能支持 HTML5 和 CSS3 以及 Javascript,有一点需要注意的是由于 WebView 的升级,对于我们的程序也带来了一些影响,如果我们的 targetSdkVersion 设置的是 18 或者更低, single and narrow column 和 default zoom levels 不再支持。Android4.4 之后有一个特别方便的地方是可以通过 setWebContentDebuggingEnabled() 方法让我们的程序可以进行远程桌面调试。

    WebView 加载页面

      WebView 有四个用来加载页面的方法:

      使用起来较为简单,loadData 方法会有一些坑,在下面的内容会介绍到。

    WebView 常见设置

      使用 WebView 的时候,一般都会对其进行一些设置,我们来看看常见的设置:

    WebSettings webSettings = webView.getSettings();
    //设置了这个属性后我们才能在 WebView 里与我们的 Js 代码进行交互,对于 WebApp 是非常重要的,默认是 false,
    //因此我们需要设置为 true,这个本身会有漏洞,具体的下面我会讲到
    webSettings.setJavaScriptEnabled(true);
    
    //设置 JS 是否可以打开 WebView 新窗口
    webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
    
    //WebView 是否支持多窗口,如果设置为 true,需要重写 
    //WebChromeClient#onCreateWindow(WebView, boolean, boolean, Message) 函数,默认为 false
    webSettings.setSupportMultipleWindows(true);
    
    //这个属性用来设置 WebView 是否能够加载图片资源,需要注意的是,这个方法会控制所有图片,包括那些使用 data URI 协议嵌入
    //的图片。使用 setBlockNetworkImage(boolean) 方法来控制仅仅加载使用网络 URI 协议的图片。需要提到的一点是如果这
    //个设置从 false 变为 true 之后,所有被内容引用的正在显示的 WebView 图片资源都会自动加载,该标识默认值为 true。
    webSettings.setLoadsImagesAutomatically(false);
    //标识是否加载网络上的图片(使用 http 或者 https 域名的资源),需要注意的是如果 getLoadsImagesAutomatically() 
    //不返回 true,这个标识将没有作用。这个标识和上面的标识会互相影响。
    webSettings.setBlockNetworkImage(true);
    
    //显示WebView提供的缩放控件
    webSettings.setDisplayZoomControls(true);
    webSettings.setBuiltInZoomControls(true);
    
    //设置是否启动 WebView API,默认值为 false
    webSettings.setDatabaseEnabled(true);
    
    //打开 WebView 的 storage 功能,这样 JS 的 localStorage,sessionStorage 对象才可以使用
    webSettings.setDomStorageEnabled(true);
    
    //打开 WebView 的 LBS 功能,这样 JS 的 geolocation 对象才可以使用
    webSettings.setGeolocationEnabled(true);
    webSettings.setGeolocationDatabasePath("");
    
    //设置是否打开 WebView 表单数据的保存功能
    webSettings.setSaveFormData(true);
    
    //设置 WebView 的默认 userAgent 字符串
    webSettings.setUserAgentString("");
    
    //设置是否 WebView 支持 “viewport” 的 HTML meta tag,这个标识是用来屏幕自适应的,当这个标识设置为 false 时,
    //页面布局的宽度被一直设置为 CSS 中控制的 WebView 的宽度;如果设置为 true 并且页面含有 viewport meta tag,那么
    //被这个 tag 声明的宽度将会被使用,如果页面没有这个 tag 或者没有提供一个宽度,那么一个宽型 viewport 将会被使用。
    webSettings.setUseWideViewPort(false);
    
    //设置 WebView 的字体,可以通过这个函数,改变 WebView 的字体,默认字体为 "sans-serif"
    webSettings.setStandardFontFamily("");
    //设置 WebView 字体的大小,默认大小为 16
    webSettings.setDefaultFontSize(20);
    //设置 WebView 支持的最小字体大小,默认为 8
    webSettings.setMinimumFontSize(12);
    
    //设置页面是否支持缩放
    webSettings.setSupportZoom(true);
    //设置文本的缩放倍数,默认为 100
    webSettings.setTextZoom(2);

      然后还有最常用的 WebViewClient 和 WebChromeClient,WebViewClient主要辅助WebView执行处理各种响应请求事件的,比如:

    • onLoadResource
    • onPageStart
    • onPageFinish
    • onReceiveError
    • onReceivedHttpAuthRequest
    • shouldOverrideUrlLoading
    WebChromeClient 主要辅助 WebView 处理J avaScript 的对话框、网站 Logo、网站 title、load 进度等处理:
    • onCloseWindow(关闭WebView)
    • onCreateWindow
    • onJsAlert
    • onJsPrompt
    • onJsConfirm
    • onProgressChanged
    • onReceivedIcon
    • onReceivedTitle
    • onShowCustomView
    WebView 只是用来处理一些 html 的页面内容,只用 WebViewClient 就行了,如果需要更丰富的处理效果,比如 JS、进度条等,就要用到 WebChromeClient,我们接下来为了处理在特定版本之下的 js 漏洞问题,就需要用到 WebChromeClient。
      接着还有 WebView 的几种缓存模式:
    • LOAD_CACHE_ONLY
    • 不使用网络,只读取本地缓存数据;
    • LOAD_DEFAULT
    • 根据 cache-control 决定是否从网络上取数据;
    • LOAD_CACHE_NORMAL
    • API level 17 中已经废弃, 从 API level 11 开始作用同 LOAD_DEFAULT 模式 ;
    • LOAD_NO_CACHE
    • 不使用缓存,只从网络获取数据;
    • LOAD_CACHE_ELSE_NETWORK
    • 只要本地有,无论是否过期,或者 no-cache,都使用缓存中的数据。
    www.baidu.com 的 cache-control 为 no-cache,在模式 LOAD_DEFAULT 下,无论如何都会从网络上取数据,如果没有网络,就会出现错误页面;在 LOAD_CACHE_ELSE_NETWORK 模式下,无论是否有网,只要本地有缓存,都会加载缓存。本地没有缓存时才从网络上获取,这个和 Http 缓存一致,我不在过多介绍,如果你想自定义缓存策略和时间,可以尝试下,volley 就是使用了 http 定义的缓存时间。
      清空缓存和清空历史记录,CacheManager 来处理 webview 缓存相关: mWebView.clearCache(true);;清空历史记录 mWebview.clearHistory();,这个方法要在 onPageFinished() 的方法之后调用。

    WebView 与 native 的交互

      使用 Hybrid 开发的 APP 基本都需要 Native 和 web 页面的 JS 进行交互,下面介绍一下交互的方式。

    js 调用 native

      如何让 web 页面调用 native 的代码呢,有三种方式:

      第一种方式:通过 addJavascriptInterface 方法进行添加对象映射
      这种是使用最多的方式了,首先第一步我们需要设置一个属性:

    mWebView.getSettings().setJavaScriptEnabled(true);
    这个函数会有一个警告,因为在特定的版本之下会有非常危险的漏洞,我们下面将会着重介绍到,设置完这个属性之后,Native 需要定义一个类:
    public class JSObject {
        private Context mContext;
        public JSObject(Context context) {
            mContext = context;
        }
    
        @JavascriptInterface
        public String showToast(String text) {
            Toast.show(mContext, text, Toast.LENGTH_SHORT).show();
            return "success";
        }
    }
    ...
    //特定版本下会存在漏洞
    mWebView.addJavascriptInterface(new JSObject(this), "myObj");
    需要注意的是在 API17 版本之后,需要在被调用的地方加上 @addJavascriptInterface 约束注解,因为不加上注解的方法是没有办法被调用的,JS 代码也很简单:
    function showToast(){
        var result = myObj.showToast("我是来自web的Toast");
    }

    可以看到,这种方式的好处在于使用简单明了,本地和 JS 的约定也很简单,就是对象名称和方法名称约定好即可,缺点就是下面要提到的漏洞问题。

      第二种方式:利用 WebViewClient 接口回调方法拦截 url
      这种方式其实实现也很简单,使用的频次也很高,上面我们介绍到了 WebViewClient ,其中有个回调接口 shouldOverrideUrlLoading (WebView view, String url) ,我们就是利用这个拦截 url,然后解析这个 url 的协议,如果发现是我们预先约定好的协议就开始解析参数,执行相应的逻辑,我们先来看看这个函数的介绍:

    Give the host application a chance to take over the control when a new url is about to be loaded in 
    the current WebView. If WebViewClient is not provided, by default WebView will ask Activity Manager 
    to choose the proper handler for the url. If WebViewClient is provided, return true means the host 
    application handles the url, while return false means the current WebView handles the url. This 
    method is not called for requests using the POST "method".

    注意这个方法在 API24 版本已经废弃了,需要使用 shouldOverrideUrlLoading (WebView view, WebResourceRequest request) 替代,使用方法很类似,我们这里就使用 shouldOverrideUrlLoading (WebView view, String url) 方法来介绍一下:

    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //假定传入进来的 url = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
        Uri uri = Uri.parse(url);
        String scheme = uri.getScheme();
        //如果 scheme 为 js,代表为预先约定的 js 协议
        if (scheme.equals("js")) {
              //如果 authority 为 openActivity,代表 web 需要打开一个本地的页面
            if (uri.getAuthority().equals("openActivity")) {
                  //解析 web 页面带过来的相关参数
                HashMap<String, String> params = new HashMap<>();
                Set<String> collection = uri.getQueryParameterNames();
                for (String name : collection) {
                    params.put(name, uri.getQueryParameter(name));
                }
                Intent intent = new Intent(getContext(), MainActivity.class);
                intent.putExtra("params", params);
                getContext().startActivity(intent);
            }
            //代表应用内部处理完成
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
    代码很简单,这个方法可以拦截 WebView 中加载 url 的过程,得到对应的 url,我们就可以通过这个方法,与网页约定好一个协议,如果匹配,执行相应操作,我们看一下 JS 的代码:
    function openActivity(){
        document.location = "js://openActivity?arg1=111&arg2=222";
    }
    这个代码执行之后,就会触发本地的 shouldOverrideUrlLoading 方法,然后进行参数解析,调用指定方法。这个方式不会存在第一种提到的漏洞问题,但是它也有一个很繁琐的地方是,如果 web 端想要得到方法的返回值,只能通过 WebView 的 loadUrl 方法去执行 JS 方法把返回值传递回去,相关的代码如下:
    //java
    mWebView.loadUrl("javascript:returnResult(" + result + ")");
    //javascript
    function returnResult(result){
        alert("result is" + result);
    }

    所以说第二种方式在返回值方面还是很繁琐的,但是在不需要返回值的情况下,比如打开 Native 页面,还是很合适的,制定好相应的协议,就能够让 web 端具有打开所有本地页面的能力了。
      第三种方式:利用 WebChromeClient 回调接口的三个方法拦截消息

      这个方法的原理和第二种方式原理一样,都是拦截相关接口,只是拦截的接口不一样:

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }
    
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }
    
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        //假定传入进来的 message = "js://openActivity?arg1=111&arg2=222",代表需要打开本地页面,并且带入相应的参数
        Uri uri = Uri.parse(message);
        String scheme = uri.getScheme();
        if (scheme.equals("js")) {
            if (uri.getAuthority().equals("openActivity")) {
                HashMap<String, String> params = new HashMap<>();
                Set<String> collection = uri.getQueryParameterNames();
                for (String name : collection) {
                    params.put(name, uri.getQueryParameter(name));
                }
                Intent intent = new Intent(getContext(), MainActivity.class);
                intent.putExtra("params", params);
                getContext().startActivity(intent);
                //代表应用内部处理完成
                result.confirm("success");
            }
            return true;
        }
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
    和 WebViewClient 一样,这次添加的是 WebChromeClient 接口,可以拦截 JS 中的几个提示方法,也就是几种样式的对话框,在 JS 中有三个常用的对话框方法:
    • onJsAlert 方法是弹出警告框,一般情况下在 Android 中为 Toast,在文本里面加入\n就可以换行;
    • onJsConfirm 弹出确认框,会返回布尔值,通过这个值可以判断点击时确认还是取消,true表示点击了确认,false表示点击了取消;
    • onJsPrompt 弹出输入框,点击确认返回输入框中的值,点击取消返回 null。
    但是这三种对话框都是可以本地拦截到的,所以可以从这里去做一些更改,拦截这些方法,得到他们的内容,进行解析,比如如果是 JS 的协议,则说明为内部协议,进行下一步解析然后进行相关的操作即可,prompt 方法调用如下所示:
    function clickprompt(){
        var result=prompt("js://openActivity?arg1=111&arg2=222");
        alert("open activity " + result);
    }

    这里需要注意的是 prompt 里面的内容是通过 message 传递过来的,并不是第二个参数的 url,返回值是通过 JsPromptResult 对象传递。为什么要拦截 onJsPrompt 方法,而不是拦截其他的两个方法,这个从某种意义上来说都是可行的,但是如果需要返回值给 web 端的话就不行了,因为 onJsAlert 是不能返回值的,而 onJsConfirm 只能够返回确定或者取消两个值,只有 onJsPrompt 方法是可以返回字符串类型的值,操作最全面方便。
      以上三种方案的总结和对比
      以上三种方案都是可行的,在这里总结一下

    • 第一种方式:
    • 是现在目前最普遍的用法,方便简洁,但是唯一的不足是在 4.2 系统以下存在漏洞问题;
    • 第二种方式:
    • 通过拦截 url 并解析,如果是已经约定好的协议则进行相应规定好的操作,缺点就是协议的约束需要记录一个规范的文档,而且从 Native 层往 Web 层传递值比较繁琐,优点就是不会存在漏洞,iOS7 之下的版本就是使用的这种方式。
    • 第三种方式:
    • 和第二种方式的思想其实是类似的,只是拦截的方法变了,这里拦截了 JS 中的三种对话框方法,而这三种对话框方法的区别就在于返回值问题,alert 对话框没有返回值,confirm 的对话框方法只有两种状态的返回值,prompt 对话框方法可以返回任意类型的返回值,缺点就是协议的制定比较麻烦,需要记录详细的文档,但是不会存在第二种方法的漏洞问题。

    native 调用 js

      第一种方式
      native 调用 js 的方法上面已经介绍到了,方法为:

    //java
    mWebView.loadUrl("javascript:show(" + result + ")");
    //javascript
    <script type="text/javascript">
    
    function show(result){
        alert("result"=result);
        return "success";
    }
    
    </script>

    需要注意的是名字一定要对应上,要不然是调用不成功的,而且还有一点是 JS 的调用一定要在 onPageFinished 函数回调之后才能调用,要不然也是会失败的
      第二种方式
      如果现在有需求,我们要得到一个 Native 调用 Web 的回调怎么办,Google 在 Android4.4 为我们新增加了一个新方法,这个方法比 loadUrl 方法更加方便简洁,而且比 loadUrl 效率更高,因为 loadUrl 的执行会造成页面刷新一次,这个方法不会,因为这个方法是在 4.4 版本才引入的,所以我们使用的时候需要添加版本的判断:

    final int version = Build.VERSION.SDK_INT;
    if (version < 18) {
        mWebView.loadUrl(jsStr);
    } else {
        mWebView.evaluateJavascript(jsStr, new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                //此处为 js 返回的结果
            }
        });
    }

      两种方式的对比
      一般最常使用的就是第一种方法,但是第一种方法获取返回的值比较麻烦,而第二种方法由于是在 4.4 版本引入的,所以局限性比较大。

    WebView 常见漏洞和坑

      常见漏洞和坑请看下篇博客:android WebView详解,常见漏洞详解和安全源码(下)

    源码

      源码解析请看下篇博客:android WebView详解,常见漏洞详解和安全源码(下)
      下载源码:https://github.com/zhaozepeng/SafeWebView;参考自:https://github.com/yushiwo/WebViewBugDemo,在此基础上做了一些优化。
      转载请注明出处:http://blog.csdn.net/self_study/article/details/54928371

    引用

    http://group.jobbole.com/26417/?utm_source=android.jobbole.com&utm_medium=sidebar-group-topic
    http://blog.csdn.net/jiangwei0910410003/article/details/52687530
    http://blog.csdn.net/leehong2005/article/details/11808557
    https://github.com/yushiwo/WebViewBugDemo/blob/master/src/com/lee/webviewbug/WebViewEx.java
    http://blog.csdn.net/sk719887916/article/details/52402470
    https://zhuanlan.zhihu.com/p/24202408
    https://github.com/lzyzsd/JsBridge
    http://www.jianshu.com/p/93cea79a2443#
    http://www.codexiu.cn/android/blog/33214/
    https://github.com/pedant/safe-java-js-webview-bridge
    http://blog.sina.com.cn/s/blog_777f9dbb0102v8by.html
    http://www.cnblogs.com/chaoyuehedy/p/5556557.html
    http://blogs.360.cn/360mobile/2014/09/22/webview%E8%B7%A8%E6%BA%90%E6%94%BB%E5%87%BB%E5%88%86%E6%9E%90/
    https://my.oschina.net/zhibuji/blog/100580
    http://www.cnblogs.com/punkisnotdead/p/5062631.html?utm_source=tuicool&utm_medium=referral

    展开全文
  • WebView全面解析

    万次阅读 多人点赞 2018-08-24 00:58:40
    WebView全面解析 简介 WebView是android中一个非常重要的控件,它的作用是用来展示一个web页面。它使用的内核是webkit引擎,4.4版本之后,直接使用Chrome作为内置网页浏览器。 作用 显示和渲染网页; 可与...
  • 我们分析一下这两个泄漏: 从图一我们可以发现是WebView的ContentViewCore中的成员变量mContainerView引用着AccessibilityManager的mAccessibilityStateChangeListeners导致activity不能被回收造成
  • NodeBB WebView应用 欢迎使用NodeBB WebView应用程序。 使用此代码,您可以创建自己的Android应用程序以显示任何网站。...分析工具 AdMob 全面的JavScript支持 多种颜色 侧边栏支持 文件 学分 视频
  • 我们都知道webView是加载解析网页代码用的,但是如果webView加载的网页数据过大的话就会消耗本进程的内存空间,从而影响app的性能;因为,系统给每个app的进程分配的空间是有限的,过多的使用空间会造成进程空间资源...
  • 效果
  • Android6.0 WebView播放视频源码分析

    千次阅读 2017-09-17 02:44:50
    Android WebView调用视频的过程
  • Webview解析html源码

    千次阅读 2015-08-23 21:33:03
    webview加载网页的时候,如何获取html的源码,可以通过js来实现。 1:设置webview,js。 webView = (WebView) findViewById(R.id.wv); webView.getSettings().setJavaScriptEnabled(true); webView....
  • 源码分析 H5发送消息给Android const setupWebViewJavascriptBridge = function (e) { console.log("window.WebViewJavascriptBridge", window.WebViewJavascriptBridge); if (window.WebViewJavascriptBridge)...
  • AgentWeb是一个基于的Android WebView,极度容易使用以及功能强大的库,提供了Android WebView一系列的问题解决方案,并且轻量和极度灵活,体验请下载的 ,或者您也可以到Google Play里面下载 ,详细使用请参照上面...
  • 简单介绍WebView控件中的一些属性跟作用,以便于跟Js交互使用~
  • 主要记录一下的是在Android中如何使用webviewwebview要处理的问题包括加载本地html文件,处理JavaScript,缩放问题。 关键代码如下,然后我们就可以直接嵌入html文件了。 完整例子:Calendar2020.zip: 有不懂的...
  • android WebView详解,常见漏洞详解和安全源码(下)

    万次阅读 多人点赞 2017-02-13 11:29:23
    上篇博客主要分析WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑。  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:...
  • 出于某些场景需要,有时候,我们需要从 WebView 获取源码,本文将简单介绍如何从 WebView 中获取源码,以及遇到的问题的分析和总结。获取源码的方法•WebView 没有提供直接获...
  • 我才知道weex module中还包括个webview,那就介绍下webview吧,是一个web操作相关的api~
  • 最近在处理android webView与js的通信上的问题,作为总结 1.简单篇 如何实现简单的android 调用js 与js调用android 让webview做一下操作 private void init(Context context){ WebSettings setting =getSettings...
  • 带着重重疑惑,进入微信小程序源码分析吧! 开发平台 这个 IDE 是如何保证我们小程序的开发和预览的?简要分析两点。 1. 文件目录 打开 微信web开发者工具目录 ,进入 package.nw ,嗯?熟悉的味道来了。里面就3...
  • android 中的webkit介绍,有需要的可以看看

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,449
精华内容 3,379
关键字:

webview源码分析