UiAutomator 由 uiautomator 二次开发得到的启发以及完善

saii · August 03, 2015 · Last by 群主 replied at March 18, 2017 · 3687 hits
本帖已被设为精华帖!

最近看到cpfeng0124 的关于二次开发之自动生成控件定位符,心里感概真是相当棒的思路,但是可能由于是公司的项目所以就点到为止并没有深入去解释其他一些功能。
这两天抽空研究了下吧,将部分的功能也一一的进行了研究完善。
uiautomatorviewer的原理我就不在这里说明里,首先说一下右键单击弹出菜单。我们要动手脚的地方是

UiAutomatorView 是用来显示出截图后的界面的,

mScreenshotCanvas.addMouseListener(new MouseAdapter() {
@SuppressWarnings("unused")
@Override
public void mouseUp(MouseEvent e) {
if (mModel != null) {
mModel.toggleExploreMode();
redrawScreenshot();
}
}
}

我们找到这个鼠标监听事件,可以看到readrawScreenshot()这函数的从字面上就能明白它的作用就是将你鼠标点击的区域绘制的红色,那么我就从这里入手了,修改代码如下:

mScreenshotCanvas.addMouseListener(new MouseAdapter() {
@SuppressWarnings("unused")
@Override
public void mouseUp(MouseEvent e) {
if (mModel != null) {
mModel.toggleExploreMode();
redrawScreenshot();
}
//如果为鼠标右键点击那么就弹出右键菜单
if (e.button==3){
Menu menu = new Menu(mScreenshotCanvas);
mScreenshotCanvas.setMenu(menu);
MenuItem item = new MenuItem(menu, SWT.PUSH);
item.setText("控件点击");
item.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
//获取选择的矩形区域
Rectangle rect = mModel.getCurrentDrawingRect();
objectClick(rect);
};
});
}
}
}


以上的代码就是实现右键弹出菜单,按钮叫控件点击,接着实现该按钮的点击事件,获取到所选择控件的矩形区域,进行点击操作。
那下来我们来看看到底要如何实现点击的操作呢,实际也很点击,通过adb shell input 就可以了。

public void objectClick(Rectangle rectangle){
String adbStr = "adb shell input tap "+(rectangle.x+rectangle.width/2)+" " +(rectangle.y+rectangle.height/2);
execCmd(adbStr);
}

置于execCmd做什么操作就不用多说了,直接java调用cmd运行就可以了。

再就是一个脚本录制的功能,实际上这东西也不麻烦。只要你模范com.android.uiautomator.actions中的类写一个

package com.android.uiautomator.actions;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;

import com.android.uiautomator.UiAutomatorViewer;

public class RecordAction extends Action {


private static boolean isRun = false;
public RecordAction(UiAutomatorViewer viewer){
super("&录制/停止");
}

@Override
public ImageDescriptor getImageDescriptor() {

if (isRun){
return ImageHelper.loadImageDescriptorFromResource("images/stop.png");

}else{
return ImageHelper.loadImageDescriptorFromResource("images/play.png");
}
}

@Override
public void run() {


if (isRun){
StringBuffer sb = new StringBuffer();
sb.append("if __name__ == \"__main__\":\n");
sb.append(" unittest.main()\n");
writeFile(sb);
isRun = false;
this.setImageDescriptor(ImageHelper.loadImageDescriptorFromResource("images/play.png"));
}else{
isRun = true;
this.setImageDescriptor(ImageHelper.loadImageDescriptorFromResource("images/stop.png"));
try{
//生成部分初始化话的代码
File file = new File("C:\\a.py");
try{
file.delete();
}catch(Exception e){
e.printStackTrace();
}
file.createNewFile();
FileOutputStream out=new FileOutputStream(file,true);
StringBuffer sb = new StringBuffer();
String deviceName = getDeviceList();
sb.append("# -*- coding: utf-8 -*-\n");
sb.append("from appium import webdriver\n");
sb.append("from appium.webdriver.common import touch_action\n");
sb.append("import unittest\n");
sb.append("class testDemo(unittest.TestCase):\n");
sb.append(" def setUp(self):\n");
sb.append(" desired_caps = {}\n");
sb.append(" desired_caps['deviceName'] = '"+deviceName+"'\n");
sb.append(" desired_caps['udid'] = '"+deviceName+"'\n");
sb.append(" desired_caps['appPackage'] = 'com.seewo.teachercare'\n");
sb.append(" desired_caps['platformName'] = 'Android'\n");
sb.append(" desired_caps['platformVersion'] = '4.4.2'\n");
sb.append(" desired_caps['appActivity'] = '.ui.TeacherMainActivity'\n");
sb.append(" self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)\n");
sb.append(" def tearDown(self):\n");
sb.append(" self.driver.quit()\n");
sb.append(" def testa(self):\n");
out.write(sb.toString().getBytes("utf-8"));
out.close();
}catch(IOException ex){
System.out.println(ex.getStackTrace());
}

}
}


/**
* 获取设备列表
* @throws IOException
*/

@SuppressWarnings("resource")
private String getDeviceList() throws IOException{
Process p = Runtime.getRuntime().exec("cmd.exe /c adb devices");
InputStream inStream = p.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream,"UTF-8");
BufferedReader inBuffer = new BufferedReader(inReader);
String s = null;
while((s = inBuffer.readLine()) != null){

if(s.endsWith("device")){
s =s.substring(0, s.lastIndexOf("device")).trim();
System.out.println(s);
return s;
}

}

return null;
}
/**
* 写文件操作
* @param sb
*/

public static void writeFile(StringBuffer sb){

if(isRun){
try{
//生成部分初始化话的代码
File file = new File("C:\\a.py");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream out=new FileOutputStream(file,true);
out.write(sb.toString().getBytes("utf-8"));
out.close();

}catch(IOException ex){
System.out.println(ex.getStackTrace());
}
}
}
}

上面有些代码我都写死了。因为这也是仓促完成,另外本人真是新手,所有代码写的烂求轻点拍。
上面主要的功能就是点击录制按钮时,初始化就写入内容,点击结束时,将main方法补上。其他写文件就是在右键时进行控件点击等时同时进行写文件操作就可以了。
最后在Uiautomatorviewer类中实例化该类

//加上按钮
toolBarManager.add(new RecordAction(this));

第一次发帖,写的可能很乱,代码也写的很渣,不知道各位能不能看的懂。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 32 条回复 时间 点赞

Good Job!UIAutomatorViewer 本身的架构确实很方便扩展。

录制那块有个疑问:看你写的是 Appium 的 python 脚本,但没看到具体是哪些代码把每个操作步骤 write 到文件中的。是不是把 writeFile 分别放在了各个点击事件中了?

saii #2 · August 04, 2015 作者

#1楼 @chenhengjie123 是的 目前我做的就是每一个点击的操作做writeFile,我觉得我写的还是比较麻烦。

学习了,厉害。

#2楼 @zsx10110

每个点击都写一次文件,是不是有点耗时,占用资源了。

可以将点击后生成的脚本直接放在StringBuilder中,这样最后再直接生成。或则将每行的脚本放在一个数据结构中,最后保存脚本的时候,再写入文件。

有新意

果然是粗糙的,还有好多地方可以优化。可以用它本身抓到的idevice来执行指令嘛。录制的方法在实际中会遇到细节上的问题,我是指拖拽等较复杂的动作的时候。

有值得借鉴的地方,哈哈哈哈,感谢分享

saii #8 · August 04, 2015 作者

#6楼 @carl 没办法 花一天的时间匆匆忙忙写的,而且对swt/jface实际上我并不是很了解。
另外你说的一点通过拿到的idevice可以做什么操作呢?

saii #9 · August 04, 2015 作者

#4楼 @kilmer 嗯 是的。

#8楼 @zsx10110
adb 指令都在里面的,还有一些其他的,我也是最近在做这一工具,不过先做了基本的东西。比如添加一块区域不编辑脚本。你才做一天就这样了,牛人呀!

saii #11 · August 04, 2015 作者

#10楼 @carl 有点明白了,类似于uiautomator的截图就是通过device实现的吧。我当时一开始的想法也是通过IDevice进行操作的, 但是我去看了IDevice这个类了,没找到我需要的操作啊,难道说是需要用executeShellCommand(String, IShellOutputReceiver) 这个方法吗?不太会,能够举个例子吗

#9楼 @zsx10110
源代码中有例子的

一个优化点:页面元素的获取可以不用dump来获取,那样会有1-2s的时间耗时,可以通过将信息获取存储到内存中再进行解析,这样可以提升执行效率

是个不错的创新!

我也在做相关的二次开发,但看了您的过后很多东西更明白了

@kasi 没有很懂你的意思,怎么通过信息获取?

我反编译了这个包,在重新打包的时候有几个保报错,修改之后打包正常,但是有如下几个问题,请赐教!1.多个手机连接不管选哪个序列号都是默认第一个2.图片显示界面没有自动缩放,鼠标点在上面也没有方框显示。

—— 来自TesterHome官方 安卓客户端

楼主,你这个方法可以在PC上录制测试脚本,但是如果从手机上操作app,就无法录制了。

saii #19 · December 16, 2015 作者

#18楼 @heavennash 是的。而且其实这个框架也是不太适合这么去改这个的,只是说提供一个思路而已

#19楼 @zsx10110 那如果是在手机上录制,请问有什么思路吗?

saii #21 · January 05, 2016 作者

#17楼 @everflier 你为什么要反编译那个包,这个是开源的。

uiautomator获取页面元素中文乱码,机型安卓5.0.2,请问能解决吗

@alisawu 中文乱码的原因是Android_5.0.*的AccessibilityNodeInfoDumper.java#stripInvalidXMLChars(CharSequence cs)中文字符处理的不好,Android6.0已经改回4.4原来的方法了,所以你把android4.4或者6.0版本的uiautomator重编就OK,简单的方法就是找一个非5.0的手机把/system/framework/uiautomator.jar替换掉你5.0的手机的uiautomator.jar

你好,能给个QQ吗,我也想做uiautomatorview的修改,但是工程翻遍后导进来还需要很多jar包,我已经导入了一些,但感觉还差很多,想具体请教下还需要导入哪些jar包,谢谢啦,哪位兄弟伙能帮个忙

#23楼 @plasma 怎么替换 uiautomator.jar 这个文件,root手机了都不行,提示 Read-only file system

#23楼 @plasma 我的三星Note4 5.0系统的时候中文也是乱码升级官方5.1之后就不乱码了呢

saii #27 · April 15, 2016 作者

#26楼 @niuniudd 这个是android 5.0都有的问题。5.1已经解决了。

感谢分享,看完几个帖子后受到启发不少,于是最近也开始动手折腾功能扩展
楼主,想问下你的输入框输入文本是怎么处理的?adb shell input text不支持中文,而且还有手机输入法的干扰。。

saii #29 · April 18, 2016 作者

#28楼 @snowmaster

确实解决不了,不过如果你真想要做好的话,就拿appium中的输入法将这个输入法安装上并且设置成默认输入法 试试看。不过我没试过 ,所以不敢说可以。

@utopia root手机后要adb remount啊,然后再push

@niuniudd 是的,Android 5.0.*版本都是乱码

JoeJoe [Topic was deleted] 中提及了此贴 04 Jul 14:13
雪怪 [Topic was deleted] 中提及了此贴 19 Aug 16:31
JoeJoe 打造专属 uiautomatorviewer 中提及了此贴 25 Nov 16:46
雪怪 uiautomatorviewer 功能扩展实践 中提及了此贴 06 Dec 22:35
伊釨言 java 初学者-uiautomatorviewer 的改造 中提及了此贴 17 Jan 17:02


我想实现二级菜单的功能。
先创建一个一级菜单
Menu menu = new Menu(mScreenshotCanvas);

mScreenshotCanvas.setMenu(menu);

添加了一个菜单选项
MenuItem newItem = new MenuItem(menu , SWT.CASCADE);
newItem .setText("物理按键");

Menu submenu = new Menu(mScreenshotCanvas, SWT.DROP_DOWN);
这里写的时候报错了,说是mScreenshotCanvas的类型不对

newItem .setMenu(submenu );

请问下你的二级菜单怎么实现的啊

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up