Android随笔之——用shell脚本模拟用户按键、触摸操作
之前写过两篇关于Android中模拟用户操作的博客(其实用一篇是转载的),现在就来讲讲用shell脚本来模拟用户按键操作。本次的目标是用shell脚本打开微信并在其搜索框中搜索相关内容。
本文的模拟功能主要是用adb的input命令来实现,如果你adb的环境变量配置正确的话,在cmd中输入 adb shell input 就可以看见input的用法了。
usage: input ...
input text //输入文字(中文不支持)
input keyevent //keyevent按键
input [touchscreen|touchpad|touchnavigation] tap <x> <y>//点击屏幕
input [touchscreen|touchpad|touchnavigation] swipe <x1> <y1> <x2> <y2> //屏幕滑动
input trackball press //滚球已经不用了
input trackball roll //滚球已经不用了
input rotationevent 0 1->90 2->180 3->270> //顺时针旋转
下面直接上安卓用户操作的代码,就一个MainActivity而已,UI、Mainfest都不用配置(可能需要root权限)
1 package com.lsj.adb; 2 3 import java.io.DataOutputStream; 4 5 import android.app.Activity; 6 import android.os.Bundle; 7 import android.view.Menu; 8 9 public class MainActivity extends Activity { 10 11 private String[] search = { 12 "input keyevent 3",// 返回到主界面,数值与按键的对应关系可查阅KeyEvent 13 "sleep 1",// 等待1秒 14 "am start -n com.tencent.mm/com.tencent.mm.ui.LauncherUI",// 打开微信的启动界面,am命令的用法可自行百度、Google 15 "sleep 3",// 等待3秒 16 "am start -n com.tencent.mm/com.tencent.mm.plugin.search.ui.SearchUI",// 打开微信的搜索 17 "input text 123",// 像搜索框中输入123,但是input不支持中文,蛋疼,而且这边没做输入法处理,默认会自动弹出输入法 18 }; 19 20 @Override 21 protected void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 //如果input text中有中文,可以将中文转成unicode进行input,没有测试,只是觉得这个思路是可行的 25 search[5] = chineseToUnicode(search[5]); 26 execShell(search); 27 } 28 29 /** 30 * 执行Shell命令 31 * 32 * @param commands 33 * 要执行的命令数组 34 */ 35 public void execShell(String[] commands) { 36 // 获取Runtime对象 37 Runtime runtime = Runtime.getRuntime(); 38 39 DataOutputStream os = null; 40 try { 41 // 获取root权限,这里大量申请root权限会导致应用卡死,可以把Runtime和Process放在Application中初始化 42 Process process = runtime.exec("su"); 43 os = new DataOutputStream(process.getOutputStream()); 44 for (String command : commands) { 45 if (command == null) { 46 continue; 47 } 48 49 // donnot use os.writeBytes(commmand), avoid chinese charset 50 // error 51 os.write(command.getBytes()); 52 os.writeBytes("\n"); 53 os.flush(); 54 } 55 os.writeBytes("exit\n"); 56 os.flush(); 57 process.waitFor(); 58 } catch (Exception e) { 59 e.printStackTrace(); 60 } 61 } 62 63 /** 64 * 把中文转成Unicode码 65 * @param str 66 * @return 67 */ 68 public String chineseToUnicode(String str){ 69 String result=""; 70 for (int i = 0; i < str.length(); i++){ 71 int chr1 = (char) str.charAt(i); 72 if(chr1>=19968&&chr1<=171941){//汉字范围 \u4e00-\u9fa5 (中文) 73 result+="\\u" + Integer.toHexString(chr1); 74 }else{ 75 result+=str.charAt(i); 76 } 77 } 78 return result; 79 } 80 81 /** 82 * 判断是否为中文字符 83 * @param c 84 * @return 85 */ 86 public boolean isChinese(char c) { 87 Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); 88 if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS 89 || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS 90 || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A 91 || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION 92 || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION 93 || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { 94 return true; 95 } 96 return false; 97 } 98 99 100 }
效果图:
模拟用户打开微信,并进行搜索就这么完成了。其实这里用shell命令模拟用户操作还是有些问题的,比如说控件长按(sendevent),好难理解,而且需要跟其中传递的控件坐标参数应该要跟屏幕分辨率联系起来,实际应用范围不是很广泛。PS:那种大量需要重复操作的除外,如:自动化测试,游戏刷图(PS:腾讯的仙剑手游把我大仙剑毁了啊,麻花腾你妹啊,你全家都是麻花腾)。
最后,其实可以参考下按键精灵,这款应用做的还不错,除了不给root权限就崩外....
补充:以上模拟用户操作的代码在交互不频繁的情况下是完全没有问题的,但是如果使用频繁的话,会发生多次申请root权限,导致系统卡死的现象,后面在google上找到了一个开源项目,可以解决这个问题(它使用的是单例模式),代码如下:
1 /** 2 * 类名 RootContext.java 说明 获取root权限 创建日期 2012-8-21 作者 LiWenLong Email 3 * lendylongli@gmail.com 更新时间 $Date$ 最后更新者 $Author$ 4 */ 5 public class RootContext { 6 private static RootContext instance = null; 7 private static Object mLock = new Object(); 8 String mShell; 9 OutputStream o; 10 Process p; 11 12 private RootContext(String cmd) throws Exception { 13 this.mShell = cmd; 14 init(); 15 } 16 17 public static RootContext getInstance() { 18 if (instance != null) { 19 return instance; 20 } 21 synchronized (mLock) { 22 try { 23 instance = new RootContext("su"); 24 } catch (Exception e) { 25 while (true) 26 try { 27 instance = new RootContext("/system/xbin/su"); 28 } catch (Exception e2) { 29 try { 30 instance = new RootContext("/system/bin/su"); 31 } catch (Exception e3) { 32 e3.printStackTrace(); 33 } 34 } 35 } 36 return instance; 37 } 38 } 39 40 private void init() throws Exception { 41 if ((this.p != null) && (this.o != null)) { 42 this.o.flush(); 43 this.o.close(); 44 this.p.destroy(); 45 } 46 this.p = Runtime.getRuntime().exec(this.mShell); 47 this.o = this.p.getOutputStream(); 48 system("LD_LIBRARY_PATH=/vendor/lib:/system/lib "); 49 } 50 51 private void system(String cmd) { 52 try { 53 this.o.write((cmd + "\n").getBytes("ASCII")); 54 return; 55 } catch (Exception e) { 56 while (true) 57 try { 58 init(); 59 } catch (Exception e1) { 60 e1.printStackTrace(); 61 } 62 } 63 } 64 65 public void runCommand(String cmd) { 66 system(cmd); 67 } 68 69 /** 70 * 判断是否已经root了 71 * */ 72 public static boolean hasRootAccess(Context ctx) { 73 final StringBuilder res = new StringBuilder(); 74 try { 75 if (runCommandAsRoot(ctx, "exit 0", res) == 0) 76 return true; 77 } catch (Exception e) { 78 } 79 return false; 80 } 81 82 /** 83 * 以root的权限运行命令 84 * */ 85 public static int runCommandAsRoot(Context ctx, String script, 86 StringBuilder res) { 87 final File file = new File(ctx.getCacheDir(), "secopt.sh"); 88 final ScriptRunner runner = new ScriptRunner(file, script, res); 89 runner.start(); 90 try { 91 runner.join(40000); 92 if (runner.isAlive()) { 93 runner.interrupt(); 94 runner.join(150); 95 runner.destroy(); 96 runner.join(50); 97 } 98 } catch (InterruptedException ex) { 99 } 100 return runner.exitcode; 101 } 102 103 private static final class ScriptRunner extends Thread { 104 private final File file; 105 private final String script; 106 private final StringBuilder res; 107 public int exitcode = -1; 108 private Process exec; 109 110 public ScriptRunner(File file, String script, StringBuilder res) { 111 this.file = file; 112 this.script = script; 113 this.res = res; 114 } 115 116 @Override 117 public void run() { 118 try { 119 file.createNewFile(); 120 final String abspath = file.getAbsolutePath(); 121 Runtime.getRuntime().exec("chmod 777 " + abspath).waitFor(); 122 final OutputStreamWriter out = new OutputStreamWriter( 123 new FileOutputStream(file)); 124 if (new File("/system/bin/sh").exists()) { 125 out.write("#!/system/bin/sh\n"); 126 } 127 out.write(script); 128 if (!script.endsWith("\n")) 129 out.write("\n"); 130 out.write("exit\n"); 131 out.flush(); 132 out.close(); 133 134 exec = Runtime.getRuntime().exec("su"); 135 DataOutputStream os = new DataOutputStream( 136 exec.getOutputStream()); 137 os.writeBytes(abspath); 138 os.flush(); 139 os.close(); 140 141 InputStreamReader r = new InputStreamReader( 142 exec.getInputStream()); 143 final char buf[] = new char[1024]; 144 int read = 0; 145 while ((read = r.read(buf)) != -1) { 146 if (res != null) 147 res.append(buf, 0, read); 148 } 149 150 r = new InputStreamReader(exec.getErrorStream()); 151 read = 0; 152 while ((read = r.read(buf)) != -1) { 153 if (res != null) 154 res.append(buf, 0, read); 155 } 156 157 if (exec != null) 158 this.exitcode = exec.waitFor(); 159 } catch (InterruptedException ex) { 160 if (res != null) 161 res.append("\nOperation timed-out"); 162 } catch (Exception ex) { 163 if (res != null) 164 res.append("\n" + ex); 165 } finally { 166 destroy(); 167 } 168 } 169 170 public synchronized void destroy() { 171 if (exec != null) 172 exec.destroy(); 173 exec = null; 174 } 175 } 176 }
作者:登天路