-
Robotium中webview源码分析
2017-04-06 18:36:17记得好久以前分析了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嵌套 ListView、RecyclerView、GridView 、WebView 源码分析解决方案
2018-03-06 21:40:25ScrollView-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);
-
Android6.0 WebView播放视频源码分析
2017-09-17 02:44:50Android WebView调用视频的过程最近因为项目的需要研究一下WebView播放视频的调用过程。同时也对研究问题的方法有了新的认识,研究问题应该先有自己的研究主题,应该从易到难,由表及里(好像初中、还是高中政治中说过这个,最近才明白,哈哈),不能违背这个定律,要不然自己会越研究越糊涂,也会很有挫败感。这篇博客就想从简单的问题和现象开始然后通过日志来证明自己的判断。
问题: WebView播放视频是调用自带的播放器还是调用Android的系统的播放器?
方法:
1、先从WebView开始,具体如下图:
1) WebViewFactory是对WebViewFactoryProvider的工厂类,是WebView主要的实现类。
2) NullWebViewFactoryProvider什么都没有做,应该是预留的吧。
3) WebViewChromiumFactoryProvider是调用Chromium。通过这个可以看出来,Android6.0已经切到Chrome内核了。
注意:WebViewChromiumFactoryProvider类在Andrid6.0中是找不到源码的,费了九牛二虎之力终于在external/chrome-weview 查找到webview.apk。通过反编译看到Chromium相关的代码。
以上是webView简单的流程,是通过研究代码可以得到的。
2. Chromium源码内调用视频的个过程, 日志如下:
09-25 14:28:28.119 1821-2699/ D/MediaResourceGetter: ethernet/wifi connection detected
09-25 14:28:28.206 1821-2699/ E/MediaResourceGetter: Error configuring data source: setDataSource failed: status = 0x80000000
09-25 14:28:28.206 1821-2699/ E/MediaResourceGetter: Unable to configure metadata extractor通过日志可以发现调用Chromium中的MediaResourceGetter类。通过源码搜索只能找到第一处日志的打印代码,其他两处都找不到。该问题的分析不能进行下。
3. 最后通过查看日志发现webView最后的调用MediaPlayer,日志如下:
09-11 14:34:24.334 13625-13625/com.smartcar.nightmode.browser W/MediaPlayer: Couldn't open file on client side; trying server side: java.io.FileNotFoundException: No content provider: http://img0.singulato.com/video/goods.mp4
09-11 14:34:25.584 13625-13625/com.smartcar.nightmode.browser D/MediaPlayer: setSubtitleAnchor in MediaPlayer
09-11 14:34:25.593 13625-13625/com.smartcar.nightmode.browser D/MediaPlayer: getMetadata
09-11 14:34:25.835 13625-13770/com.smartcar.nightmode.browser W/MediaPlayer: info/warning (3, 0)同时通过代码简单实现VideoView播放本地视频,抓取日志如下:
09-11 14:32:05.980 12855-12919/com.test I/OpenGLRenderer: Initialized EGL, version 1.4
09-11 14:32:05.981 12855-12919/com.test W/OpenGLRenderer: Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...
09-11 14:32:06.064 12855-12855/com.test W/MediaPlayer: Couldn't open file on client side; trying server side: java.io.FileNotFoundException: No content provider: http://img0.singulato.com/video/goods.mp4
09-11 14:32:09.197 12855-12855/com.test D/MediaPlayer: getMetadata
09-11 14:32:09.448 12855-12867/com.test W/MediaPlayer: info/warning (3, 0)
通过以上日志对比基本上可以断定:webview播放视频最后调用的是Android本身的播放器。
-
Android9.0万年历毕业设计H5小应用webview应用源码分析已运行通过
2020-06-19 13:01:21主要记录一下的是在Android中如何使用webview? webview要处理的问题包括加载本地html文件,处理JavaScript,缩放问题。 关键代码如下,然后我们就可以直接嵌入html文件了。 完整例子:Calendar2020.zip: 有不懂的...Android9.0万年历,使用的是html+webview的形式,轻松做出一个好看又好用的实实在在的小日历。
主要记录一下的是在Android中如何使用webview?
webview要处理的问题包括加载本地html文件,处理JavaScript,缩放问题。
关键代码如下,然后我们就可以直接嵌入html文件了。
完整例子:Calendar2020.zip:
有不懂的可以留言哦。
开发环境为Android Studio4.0WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = (WebView)findViewById(R.id.webView); webView.loadUrl("file:///android_asset/html/index.html"); //支持App内部javascript交互 webView.getSettings().setJavaScriptEnabled(true); //自适应屏幕 webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); webView.getSettings().setLoadWithOverviewMode(true); //设置可以支持缩放 webView.getSettings().setSupportZoom(true); //扩大比例的缩放 webView.getSettings().setUseWideViewPort(true); //设置是否出现缩放工具 webView.getSettings().setBuiltInZoomControls(true); }
加载本地html文件的时候,请注意,新建一个assets目录,然后务必注意这个地址哦:
webView.loadUrl("file:///android_asset/html/index.html");
最终结果展示:
-
Android webview 内存泄漏源码分析及处理办法
2020-12-30 17:21:00我们分析一下这两个泄漏: 从图一我们可以发现是WebView的ContentViewCore中的成员变量mContainerView引用着AccessibilityManager的mAccessibilityStateChangeListeners导致activity不能被回收造成 -
WebView跨进程通信框架源码分析
2020-08-05 01:30:14我们都知道webView是加载解析网页代码用的,但是如果webView加载的网页数据过大的话就会消耗本进程的内存空间,从而影响app的性能;因为,系统给每个app的进程分配的空间是有限的,过多的使用空间会造成进程空间资源... -
Android源码下的WebView分析
2013-09-03 22:42:33Android的WebView.java是一个内置的支持浏览器的视图View,查看源码目录frameworks\base\core\java\android\webkit 下面有多个java源文件,第一个为WebView.java,这个类可不小,将近8000行; WebView provide -
Weex Android SDK源码分析之Module(webview)
2016-06-18 18:15:06我才知道weex module中还包括个webview,那就介绍下webview吧,是一个web操作相关的api~ -
nodebb-webview:适用于Android的NodeBB WebView-源码
2021-02-04 15:58:25NodeBB WebView应用 欢迎使用NodeBB WebView应用程序。 使用此代码,您可以创建自己的Android应用程序以显示任何网站。...分析工具 AdMob 全面的JavScript支持 多种颜色 侧边栏支持 文件 学分 视频 -
Android webView与js 交互以及jsbridge框架源码分析
2018-12-20 18:16:59最近在处理android webView与js的通信上的问题,作为总结 1.简单篇 如何实现简单的android 调用js 与js调用android 让webview做一下操作 private void init(Context context){ WebSettings setting =getSettings... -
iOS webView使用分析
2013-10-10 15:08:41正常webView加载一个页面会加载一整个网页 我们查看网页的源码吗看到这么几句话 content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> 就是所加载的页面的宽度是 设备的...