안드로이드 앱 자동화 (상점 스크랩, 도어락 자동화) – 2편
※ 시작하기 전에...
본 글의 전체 소스를 공개할까 하다가 비밀번호 하드코딩 등 보안사항 이유로 완전히 공개할 수 없어 일부 내용들만 발췌하여 공개합니다. 추후 정상빌드 가능한 범용 패키지로 정리하여 공개할 예정입니다.
[ 1편보기 ]
1. 어쩌다가...
오토마우스 처럼 이라는 말에 순간 설득되어버린 나는 처음에 구상한 것들을 모두 버리고 새판을 짜야 했다.
2. 그래서 다시 구상...
-
"오토마우스" 방식 을 참고해서, 클릭 자동화 방식으로 구현 할거다.
-
기본적으로는 에뮬레이터에서 클릭, 키보드입력 등을 사용한다.
-
화면이 뜨는 타이밍을 고려하여 화면별, 상황별로 클릭해야 하는 좌표를 판단해야 한다.
-
스크린샷 기능으로 현재 화면 이미지를 분석하여 무얼 클릭해야 할지 판단하는 기준을 만든다
-
여러가지 상황이 발생할 수 있으므로, 다소의 오작동은 감안해야 한다.
-
단. 클릭되지 말아야 할것을 클릭하는 상황은 반드시 막아야 한다. (계정 삭제 버튼 같은....)
써놓고 보니 왠지 삽질이 깊게 들어갈거 같은 느낌이 든다...
3. 그리고 다시 조사 (가능성 테스트)
이번에도 대충만 생각해 놓고 실제 프로젝트 (Springboot Application) 를 만들어놓고 단위 기능이 정상적으로 작동하는지 하나하나 테스트 해 보면서 진행 해 볼 것이다.
-
qemu
에서qmp
프로토콜을 사용해서 할수 있는기능들을 조사한다. -
가상 HW 마우스 이동 및 클릭 : 가능
-
가상 HW 키보드 입력 : 가능
-
에뮬레이터 리셋 : 가능
-
스크린샷 : 가능
-
qmp
기능은 json 데이터의 socket 통신 으로 구현하며, 초기화는 다음과 같다.
{
"execute" : "qmp_capabilities"
}
qmp
기능 실행의 예는 다음과 같다. (마우스 이동 명령)
{
"execute" : "input-send-event",
"arguments" : {
"events" : [
{ "type" : "abs", "data" : { "axis" : "x", "value" : "1000" } },
{ "type" : "abs", "data" : { "axis" : "y", "value" : "1000" } }
]
}
}
-
adb
를 통해 할 수 있는 기능들을 조사한다. -
마우스 이동 및 클릭 : 가능 (복잡)
-
키보드 입력 : 가능 (복잡)
-
파일이동 : 가능
-
스크린샷 : 가능
-
앱구동 및 중단 : 가능
-
안드로이드 내부 시스템 커맨드 : 가능
-
클립보드 복사 : 가능
-
adb
명령은 쉘 에서(Runtime.exec
) 작동하며, 초기화는 다음과 같이 수행한다.
adb connect localhost:5555
adb devices
adb -s localhost:5555 shell
```
- 다음은 `adb` 기능 실행 예제이다.
```bash
## 키보드 스페이스바 입력
adb shell input keyboard keyevent KEYCODE_SPACE
## 키보드 1235 문자열 입력
adb shell input text 1235
## 키보드 엔터 입력
adb shell input keyboard keyevent KEYCODE_ENTER
※ 주로 하드웨어계열 기능은 `qmp` 로, 소프트웨어 계열 기능은 `adb` 로 하면 될듯 하다.
※ 중복된 기능들은 둘 다 사용해 본 후 안정적인 것을 사용함.
- 이후 Springboot Application 을 생성하고 막바로 Junit 테스트 케이스 부터 만들었다.
- build.gradle 은 다음과 같다
--- 중략 ---
dependencies {
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.json:json:20230618'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.logback-extensions:logback-ext-spring:0.1.5'
implementation 'org.apache.poi:poi:5.2.4'
implementation 'org.apache.poi:poi-ooxml:5.2.4'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'org.apache.httpcomponents:httpmime:4.5.14'
implementation 'org.seleniumhq.selenium:selenium-java:4.18.0'
implementation 'commons-codec:commons-codec:1.16.0'
implementation 'org.apache.derby:derby:10.15.2.0'
implementation 'org.apache.derby:derbytools:10.15.2.0'
implementation fileTree(dir: 'libs', includes: ['*.jar'])
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testAnnotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.apache.commons:commons-dbcp2:2.10.0'
testImplementation 'org.apache.poi:poi:5.2.4'
testImplementation 'org.apache.poi:poi-ooxml:5.2.4'
testImplementation fileTree(dir: 'libs', includes: ['*.jar'])
}
--- 중략 ---
- `adb` 와 `qmp` 를 통합해서 다룰 `Commands.java` 클래스를 생성한다.
--- 중략 ---
public class Commands {
--- 중략 ---
/** 통합 명령어 실행기 클래스 */
public static class Commander implements Closeable {
public String qmpServer;
public int qmpPort;
public String adbServer;
public int adbPort;
public static Commander instance;
/** adb 명령어 실행기 */
public AdbCommander adbCmd;
/** qmp 명령어 실행기 */
public QmpCommander qmpCmd;
--- 중략 ---
}
/** adb 명령어 실행기 클래스 */
public static class AdbCommander implements Closeable {
String server;
int port;
Runtime rtm;
String adb;
--- 중략 ---
}
/** qmp 명령어 실행기 클래스 */
public static class QmpCommander implements Closeable {
Socket sock;
Writer writer;
BufferedReader reader;
--- 중략 ---
}
--- 중략 ---
}
--- 중략 ---
- 대략적인 구도는 다음과 같다. (Commander 에서 adb / qmp 메소드를 모두 실행)
classDiagram
class Commander {
- adbCmd: AdbCommander
- qmpCmd: QmpCommander
+ initAdb()
+ initQmp()
+ click()
+ mouseMove()
+ screenDump()
+ ...중략...()
+ close()
}
class AdbCommander {
- rtm: Runtime
+ screenDump()
+ startDoorLockApp()
+ ...중략...()
+ close()
}
class QmpCommander {
- sock: Socket
+ click();
+ mouseMove();
+ ...중략...()
+ close()
}
Commander <-- AdbCommander
Commander <-- QmpCommander
아.. 이제 막 코딩하기 시작 했을 뿐인데 토나올거 같다.
3편에서는 실제 에뮬레이터 접속해서 마우스 움직이고 클릭해보고 할거다.
[ 3편에서 계속.. ]
[ 관련 소스 저장소 ]