安卓模拟按键操作

33

如何在Droid上通过编程模拟按键操作?我想模拟手动按键操作(类似于某人在按键,但实际上是通过程序完成的)。

尽管涉及到IWindowManager的解决方案,在新SDK中已不再是可行选项。


测试仪器、修改应用程序以响应除实际触摸之外的其他操作,并在Linux级别注入事件的“rooting”是您的三个选择。 - Chris Stratton
4个回答

45

你可以使用仪器,比如在你的Activity的onCreate方法中调用以下代码将会导致菜单被多次打开和关闭:

    new Thread(new Runnable() {         
        @Override
        public void run() {
            try {
            Instrumentation inst = new Instrumentation();
            for ( int i = 0; i < 10; ++i ) {
                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
                Thread.sleep(2000);
                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
                Thread.sleep(2000);
            }
            }
            catch(InterruptedException e){
            }
        }   
    }).start();

... 但我不确定这是否是您想要的。


5
这正是我所需的,但是……仪器化(instrumentation)难道不是严格用于测试吗?我在寻找的是一种可用于实时应用程序的解决方案,而非测试。谢谢。 - calcrisk33
@calcrisk33 我还没有找到任何关于这是否是一个适用于实时应用的可行解决方案的信息。你最终是否以这种方式使用它了? - Kirk
1
@Kirk,我试过了,对于模拟按键/击键非常有效。 - calcrisk33
1
完美地满足了我的需求(JNI自定义键盘),尽管我不需要try/catch块,但发现它必须在一个线程中。 - Adamski
4
需要INJECT_EVENTS权限,该权限仅适用于系统应用程序。 - anemomylos
显示剩余5条评论

15
如果您想消费事件,可以使用BaseInputConnection类及其sendKeyEvent方法。
要使用它,您需要指定一个目标视图(例如EditText),该视图将接收KeyEvent。例如:
EditText editText;
BaseInputConnection inputConnection = new BaseInputConnection(editText, true);
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POUND));

这样做的结果就像用户实际上按下了#键(同时保持编辑文本聚焦)。


2
让我想知道,你是否也需要分派 KeyEvent.ACTION_UP。我猜这样做是合乎逻辑的。 - Peter
1
这种方法也适用于其他视图吗?例如:如果我想在RecyclerView上模拟DPAD_UP? - theapache64

2

在我看来,使用仪器并不能像预期的那样起作用,当editText被聚焦时,它有时会导致软键盘弹出。

在我的项目中,我有一个数字键盘片段,应该像普通键盘一样工作,这是我实现所需解决方案的方式:

我在三个安卓7+设备上测试了这个解决方案:

键盘片段onClick():

@Override
public void onClick(View v) {

    switch(v.getId()) {

        case R.id.button0:
            simulateKeyPress(KeyEvent.KEYCODE_0);
            break;
        case R.id.button1:
            simulateKeyPress(KeyEvent.KEYCODE_1);
            break;
        case R.id.button2:
            simulateKeyPress(KeyEvent.KEYCODE_2);
            break;
        case R.id.button3:
            simulateKeyPress(KeyEvent.KEYCODE_3);
            break;
        case R.id.button4:
            simulateKeyPress(KeyEvent.KEYCODE_4);
            break;
        case R.id.button5:
            simulateKeyPress(KeyEvent.KEYCODE_5);
            break;
        case R.id.button6:
            simulateKeyPress(KeyEvent.KEYCODE_6);
            break;
        case R.id.button7:
            simulateKeyPress(KeyEvent.KEYCODE_7);
            break;
        case R.id.button8:
            simulateKeyPress(KeyEvent.KEYCODE_8);
            break;
        case R.id.button9:
            simulateKeyPress(KeyEvent.KEYCODE_9);
            break;
    }

}

public void simulateKeyPress(int key){
    Activity a = (Activity) getContext();
    a.getWindow().getDecorView().getRootView();
    BaseInputConnection inputConnection = new BaseInputConnection(a.getWindow().getDecorView().getRootView(),
            true);
    KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, key);
    KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, key);
    inputConnection.sendKeyEvent(downEvent);
    inputConnection.sendKeyEvent(upEvent);
}

我将事件发送到活动的根视图,然后再传递到所需的聚焦editText。

这是一种简单粗暴的解决方案,但效果很好。


0

如果您正在运行UI Automator测试,则可以根据设备的Android版本使用两种技术:

API 21+

如果您只针对API 18或更高版本进行目标设置,则可以直接使用shell:

UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("input text 1234"); // Type '1234'
device.executeShellCommand("input keyevent 66"); // Press the Enter key

API 18+

如果您还支持API 18-19,则不能使用shell,因为它不可用,如果您正在与不属于自己的应用程序交互,例如系统UI,则无法使用仪器键注入。而是使用UiAutomation.injectInputEvent()

获取UiAutomation的实例并将其存储在某个地方:

UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();

然后定义一些辅助方法:

private void sendKey(int keyCode) {
    sendKeyEvent(keyCode, KeyEvent.ACTION_DOWN);
    sendKeyEvent(keyCode, KeyEvent.ACTION_UP);
}

private void sendKeyEvent(int keyCode, int action) {
    long downTime = SystemClock.uptimeMillis();
    KeyEvent event = new KeyEvent(
            downTime,
            downTime,
            action,
            keyCode,
            0,
            0,
            KeyCharacterMap.VIRTUAL_KEYBOARD,
            0,
            KeyEvent.FLAG_FROM_SYSTEM,
            InputDevice.SOURCE_KEYBOARD
    );

    uiAutomation.injectInputEvent(event, true);
}

然后像这样使用:

sendKey(KeyEvent.KEYCODE_1);
sendKey(KeyEvent.KEYCODE_2);
sendKey(KeyEvent.KEYCODE_3);
sendKey(KeyEvent.KEYCODE_4);
sendKey(KeyEvent.KEYCODE_ENTER);

UiDevice有许多辅助方法,比如pressEnter()pressHome()等等。 - Ishaan Kumar
想知道执行Shell命令input keyevent 66pressEnter()哪个更快?"66"是回车键的代码。 - Ishaan Kumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接