用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權限)</span>
package com.lsj.adb;

import java.io.DataOutputStream;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity {

    private String[] search = {
            "input keyevent 3",// 返回到主界面,數值與按鍵的對應關係可查閱KeyEvent
            "sleep 1",// 等待1秒
            "am start -n com.tencent.mm/com.tencent.mm.ui.LauncherUI",// 打開微信的啓動界面,am命令的用法可自行百度、Google
            "sleep 3",// 等待3秒
            "am start -n com.tencent.mm/com.tencent.mm.plugin.search.ui.SearchUI",// 打開微信的搜索
            "input text 123",// 像搜索框中輸入123,但是input不支持中文,蛋疼,而且這邊沒做輸入法處理,默認會自動彈出輸入法
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //如果input text中有中文,可以將中文轉成unicode進行input,沒有測試,只是覺得這個思路是可行的
        search[5] = chineseToUnicode(search[5]);
        execShell(search);
    }

    /**
     * 執行Shell命令
     * 
     * @param commands
     *            要執行的命令數組
     */
    public void execShell(String[] commands) {
        // 獲取Runtime對象
        Runtime runtime = Runtime.getRuntime();

        DataOutputStream os = null;
        try {
            // 獲取root權限,這裏大量申請root權限會導致應用卡死,可以把Runtime和Process放在Application中初始化
            Process process = runtime.exec("su");
            os = new DataOutputStream(process.getOutputStream());
            for (String command : commands) {
                if (command == null) {
                    continue;
                }

                // donnot use os.writeBytes(commmand), avoid chinese charset
                // error
                os.write(command.getBytes());
                os.writeBytes("\n");
                os.flush();
            }
            os.writeBytes("exit\n");
            os.flush();
            process.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 把中文轉成Unicode碼
     * @param str
     * @return
     */
    public String chineseToUnicode(String str){
        String result="";
        for (int i = 0; i < str.length(); i++){
            int chr1 = (char) str.charAt(i);
            if(chr1>=19968&&chr1<=171941){//漢字範圍 \u4e00-\u9fa5 (中文)
                result+="\\u" + Integer.toHexString(chr1);
            }else{
                result+=str.charAt(i);
            }
        }
        return result;
    }

    /**
     * 判斷是否爲中文字符
     * @param c
     * @return
     */
    public  boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
            return true;
        }
        return false;
    }


}

模擬用戶打開微信,並進行搜索就這麼完成了。其實這裏用shell命令模擬用戶操作還是有些問題的,比如說控件長按(sendevent),好難理解,而且需要跟其中傳遞的控件座標參數應該要跟屏幕分辨率聯繫起來,實際應用範圍不是很廣泛。PS:那種大量需要重複操作的除外,如:自動化測試,遊戲刷圖(PS:騰訊的仙劍手遊把我大仙劍毀了啊,麻花騰你妹啊,你全家都是麻花騰)。

  最後,其實可以參考下按鍵精靈,這款應用做的還不錯,除了不給root權限就崩外....

 

補充:以上模擬用戶操作的代碼在交互不頻繁的情況下是完全沒有問題的,但是如果使用頻繁的話,會發生多次申請root權限,導致系統卡死的現象,後面在google上找到了一個開源項目,可以解決這個問題(它使用的是單例模式),代碼如下:

/**
 * 類名 RootContext.java 說明 獲取root權限 創建日期 2012-8-21 作者 LiWenLong Email
 * [email protected] 更新時間 $Date$ 最後更新者 $Author$
 */
public class RootContext {
    private static RootContext instance = null;
    private static Object mLock = new Object();
    String mShell;
    OutputStream o;
    Process p;

    private RootContext(String cmd) throws Exception {
        this.mShell = cmd;
        init();
    }

    public static RootContext getInstance() {
        if (instance != null) {
            return instance;
        }
        synchronized (mLock) {
            try {
                instance = new RootContext("su");
            } catch (Exception e) {
                while (true)
                    try {
                        instance = new RootContext("/system/xbin/su");
                    } catch (Exception e2) {
                        try {
                            instance = new RootContext("/system/bin/su");
                        } catch (Exception e3) {
                            e3.printStackTrace();
                        }
                    }
            }
            return instance;
        }
    }

    private void init() throws Exception {
        if ((this.p != null) && (this.o != null)) {
            this.o.flush();
            this.o.close();
            this.p.destroy();
        }
        this.p = Runtime.getRuntime().exec(this.mShell);
        this.o = this.p.getOutputStream();
        system("LD_LIBRARY_PATH=/vendor/lib:/system/lib ");
    }

    private void system(String cmd) {
        try {
            this.o.write((cmd + "\n").getBytes("ASCII"));
            return;
        } catch (Exception e) {
            while (true)
                try {
                    init();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
        }
    }

    public void runCommand(String cmd) {
        system(cmd);
    }

    /**
     * 判斷是否已經root了
     * */
    public static boolean hasRootAccess(Context ctx) {
        final StringBuilder res = new StringBuilder();
        try {
            if (runCommandAsRoot(ctx, "exit 0", res) == 0)
                return true;
        } catch (Exception e) {
        }
        return false;
    }

    /**
     * 以root的權限運行命令
     * */
    public static int runCommandAsRoot(Context ctx, String script,
            StringBuilder res) {
        final File file = new File(ctx.getCacheDir(), "secopt.sh");
        final ScriptRunner runner = new ScriptRunner(file, script, res);
        runner.start();
        try {
            runner.join(40000);
            if (runner.isAlive()) {
                runner.interrupt();
                runner.join(150);
                runner.destroy();
                runner.join(50);
            }
        } catch (InterruptedException ex) {
        }
        return runner.exitcode;
    }

    private static final class ScriptRunner extends Thread {
        private final File file;
        private final String script;
        private final StringBuilder res;
        public int exitcode = -1;
        private Process exec;

        public ScriptRunner(File file, String script, StringBuilder res) {
            this.file = file;
            this.script = script;
            this.res = res;
        }

        @Override
        public void run() {
            try {
                file.createNewFile();
                final String abspath = file.getAbsolutePath();
                Runtime.getRuntime().exec("chmod 777 " + abspath).waitFor();
                final OutputStreamWriter out = new OutputStreamWriter(
                        new FileOutputStream(file));
                if (new File("/system/bin/sh").exists()) {
                    out.write("#!/system/bin/sh\n");
                }
                out.write(script);
                if (!script.endsWith("\n"))
                    out.write("\n");
                out.write("exit\n");
                out.flush();
                out.close();

                exec = Runtime.getRuntime().exec("su");
                DataOutputStream os = new DataOutputStream(
                        exec.getOutputStream());
                os.writeBytes(abspath);
                os.flush();
                os.close();

                InputStreamReader r = new InputStreamReader(
                        exec.getInputStream());
                final char buf[] = new char[1024];
                int read = 0;
                while ((read = r.read(buf)) != -1) {
                    if (res != null)
                        res.append(buf, 0, read);
                }

                r = new InputStreamReader(exec.getErrorStream());
                read = 0;
                while ((read = r.read(buf)) != -1) {
                    if (res != null)
                        res.append(buf, 0, read);
                }

                if (exec != null)
                    this.exitcode = exec.waitFor();
            } catch (InterruptedException ex) {
                if (res != null)
                    res.append("\nOperation timed-out");
            } catch (Exception ex) {
                if (res != null)
                    res.append("\n" + ex);
            } finally {
                destroy();
            }
        }

        public synchronized void destroy() {
            if (exec != null)
                exec.destroy();
            exec = null;
        }
    }
}

本文轉載於:http://www.cnblogs.com/travellife/p/4108208.html

發佈了30 篇原創文章 · 獲贊 18 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章