NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET 매크로 프로그램 만들기. (안드로이드 휴대폰 매크로 만들기 with ADB 1부)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 이제 드디어 모바일 매크로 다시 말해서 안드로이드 핸드폰용 매크로입니다. 아쉽게도 iOS쪽은 매크로로 자동화하기가 쉽지 않습니다. 일단 쓸만한 에뮬레이터도 없을뿐더러 맥용 컴퓨터에서 매크로를 제작하려면 또다른 언어와 프로그램을 만들어야 하기 때문이예요. 그리고, 여러가지 API 사용면에서 안드로이드 모바일 기기들이 월등하게 편리하기 때문에 자동화 업무용 폰은 안드로이드로 가는게 좋습니다.

     

    일단, ADB가 무엇인지 알아야겠죠? ADB(Android Debug Bridge)를 간단하게 설명하자면 에뮬레이터나 단말기에 연결해주는 명령줄 도구입니다. 설치된 App 리스트를 볼 수 있고, Shell 명령어를 직접 입력할 수 있습니다. 이를 통해 안드로이드 앱 개발을 테스트하고, 쉽게 검증할 수 있습니다. 또한, 자동화 테스트도 가능하게 해줍니다.

     

    안드로이드폰을 WiFi 또는 USB로 컴퓨터에 연결한 후 화면 미러링으로 매크로를 만드는 방법에 대해 알아볼건데요. 생각보다 간단하게 환경을 구성할 수 있으니 천천히 이 글을 따라서 만들어보시기 바랍니다. 자~ 우선 ADB를 설치 해볼까요? 아래 링크에서 윈도우 플렛폼(32bit 또는 64bit)에 맞는 ADB를 다운로드 해줍니다.

    안드로이드 ADB 다운로드 ]

     

    다운로드 버튼을 클릭하세요.

    kAorC6S.png

     

     

    Windows용 SDK 플랫폼 도구 다운로드를 클릭하세요.

    KH1JSpD.png

     

     

    사용 약관에 동의하고, 다운로드를 클릭하세요.

    qv5ZSnk.png

     

     

    다운로드 받은 파일의 압축을 해제한 후 폴더 이름을 ADB로 변경 했습니다. 그리고, C드라이브의 용량이 부족해서 E드라이브에 폴더를 이동시켰습니다.

    M9nD8km.png

     

     

    그 다음 ADB를 CMD에서 자신이 어느 위치에 있던지 상관없이 사용할 수 있겠끔 환경 변수를 수정해줍니다. 키보드에서 Windows Key+r을 눌러 실행을 켠 뒤 아래의 사진과 같이 sysdm.cpl을 입력하고 확인을 눌러줍니다.

    8E3wtYz.png

     

     

    시스템 속성에서 고급 탭을 클릭하고, 환경 변수를 클릭하세요.

    ZIZ9GIW.png

     

     

    시스템 변수의 ①Path를 더블 클릭하세요. 환경 변수 편집 창이 표시되면, ②찾아보기를 클릭하세요. 폴더 찾아보기 창에서 ③ADB 폴더를 선택 해줍니다. ④확인을 누르세요.

    yacq5tF.png

     

     

    ADB가 정상적으로 설치되었는지 확인해야 하는데요. CMD에서 아래 명령으로 확인이 가능합니다. adb를 입력하고 엔터를 치면 에러 없이 정보가 표시되어야 합니다.

    eeSpVab.png

     

     

    이제 scrcpy(스크린 카피, Screen Copy)를 설치하면 작업은 마무리 됩니다. 아래 링크에서 scrcpy를 다운로드 받습니다. 우측의 releases(릴리즈)를 클릭하세요.

    scrcpy 다운로드 ]

    nrVVwtJ.png

     

     

    현재 최신 버전이 1.23이네요. 저는 최신 버전으로 선택 했습니다.

    oPPveLd.png

     

     

    스크롤을 끝까지 내리면 scrcpy-win64-v1.23.zip 파일이 있습니다. 클릭해서 다운로드 하세요.

    k1CBSiQ.png

     

     

    압축을 푼 후 폴더 안의 모든 파일을 E드라이브 ADB 폴더에 덮어쓰기 해줍니다. 아래 파일 목록은 버전에 따라 다를수도 있습니다.

    jA4Rqm3.png

     

     

    마지막으로 화면을 미러링할 안드로이드 폰을 하나 준비한 후 개발자 옵션에서 USB 디버깅 모드를 활성화 해야 합니다. 안드로이드 디버깅 모드를 활성화 하는 방법은 아래 블로그에 잘 설명되어 있으니 참고하셔서 설정을 변경 해주세요. 저는 안드로이드폰이 없어서... 어쩔 수 없이 녹스(Nox)로 진행하도록 하겠습니다. 만약, 녹스가 없다면 엘디플레이어나 블루스택, 미뮤, 코플레이어등등... 다른걸 사용해도 상관 없습니다.

     

    ADB와 SCRCPY 설치가 완료 되었다면 테스트를 위해 안드로이드 휴대폰을 USB로 컴퓨터에 연결하고, 추가로 녹스 앱플레이어도 실행 해보겠습니다. 하나는 실물이 존재하는 핸드폰이고 하나는 윈도우 컴퓨터에서 실행되는 안드로이드 애뮬레이터입니다.

    dp6REB3.png

     

     

    정상적으로 기기를 인식하는지 테스트 해봐야겠죠? 윈도우의 CMD를 실행하고, 아래 명령을 수행 해보세요.

    > adb start-server

    4N5kf01.png

     

     

    서버가 실행되었으면, 컴퓨터에 연결된 장치 목록을 표시해볼께요.

    > adb devices

    IYj95sW.png

     

     

    USB로 연결한 안드로이드폰과 윈도우에서 실행중인 녹스 에뮬레이터가 정상적으로 인식되고 있습니다. 이제 개발 준비를 끝냈으므로, 실제로 코딩을 해보고 몇가지 테스트를 해볼께요. 참고로, 마이크로소프트의 nuget에서 adb를 검색 해봅시다.

    Vu0tzDu.png

     

     

    ADB를 검색해서 안드로이드 공식 에뮬레이터 패키지를 솔루션에 설치 해줍니다.

    BevzSoV.png

     

     

    안드로이드 장치(Device: 디바이스)를 제어하는 방식은 크게 2가지로 나눌 수 있습니다. 장치에 앱을 설치하거나 삭제, 와이파이를 켜거나 끄기, 비행기 모드를 전환해서 아이피 바꾸기등등... 뭔가 복잡적인 작용이 필요하거나 기기를 직접 제어해야 하는 경우와 단순히 인풋 명령을 처리하는 기능입니다. 다시 말해서 장치를 제어하는 것과 인풋(Input: 입력) 2가지입니다. 인풋은 마우스와 키보드와 같은 장치 입력을 말합니다.

     

    이제 엔지엠 매크로 7에서 안드로이드 장치에 연결할 수 있도록 해야 하는데요. ConnectionModel과 DisconnectionModel을 만들어 둡니다. 만드는김에 CommandModel도 추가할께요.

    N4dq6WG.png

     

     

    Adb에 연결한 클라이언트를 관리할 수 있는 클래스가 하나 필요합니다. 그리고, 이 클래스는 시리얼라이즈를 지원하지 않기 때문에 NonSerialized 특성을 부여해야 합니다.

    [NonSerialized]
    private AdbClient? _adb = null;
    
    [Browsable(false)]
    public AdbClient? ADB { get { return _adb; } set { _adb = value; } }

     

    Adb 클라이언트는 하나의 기기와 1:1로 매칭됩니다.

    public class AdbClient : IAdbClient, ICloneable<AdbClient>, ICloneable

     

    Adb 정보를 가져올 수 있도록 메소드를 하나 추가할께요.

    public async Task<int> GetAdbVersionAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        using IAdbSocket socket = CreateAdbSocket();
        await socket.SendAdbRequestAsync("host:version", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        return int.Parse(await socket.ReadStringAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false), NumberStyles.HexNumber);
    }

     

    나중에 알아볼 내용이지만, Adb 명령에 대한 기능들도 하나씩 만들어줘야 합니다.

    public async Task KillAdbAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        using IAdbSocket socket = CreateAdbSocket();
        await socket.SendAdbRequestAsync("host:kill", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
    }

     

    기본적인 내용은 동일하지만, Adb를 통해서 안드로이드 기기에 소켓을 연결한 후 명령을 보내야 합니다. 그리고, 결과 값을 반환받아서 처리하는 방식입니다.

    public async Task<int> CreateForwardAsync(DeviceData device, string local, string remote, bool allowRebind, CancellationToken cancellationToken = default(CancellationToken))
    {
        EnsureDevice(in device);
        using IAdbSocket socket = CreateAdbSocket();
        string value = (allowRebind ? string.Empty : "norebind:");
        await socket.SendAdbRequestAsync($"host-serial:{device.Serial}:forward:{value}{local};{remote}", cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        string text = await socket.ReadStringAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        int result;
        return (text != null && int.TryParse(text, out result)) ? result : 0;
    }

     

    루트 권한 유무에 관계없이 장치에서 실행 중인 ADB 데몬을 다시 시작합니다.

    protected async Task RootAsync(string request, DeviceData device, CancellationToken cancellationToken = default(CancellationToken))
    {
        EnsureDevice(in device);
        using IAdbSocket socket = CreateAdbSocket();
        await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        await socket.SendAdbRequestAsync(request, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        byte[] buffer = new byte[1024];
        int length = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        string @string = Encoding.GetString(buffer.AsSpan(0, length));
        if (!@string.Contains("restarting", StringComparison.OrdinalIgnoreCase))
        {
            throw new AdbException(@string);
        }
    
        await TaskExExtensions.Delay(3000, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
    }

     

    현재 연결된 디바이스들의 정보를 표시하기 위해 아래와 같이 속성을 추가 해줍니다.

    [LocalizedCategory("Data")]
    [LocalizedDisplayName("ConnectionDeviceCount")]
    [LocalizedDescription("ConnectionDeviceCount")]
    [Browsable(true)]
    [DefaultValue(0)]
    [ReadOnly(true)]
    public int ConnectionDeviceCount { get; set; }
    
    [LocalizedCategory("Data")]
    [LocalizedDisplayName("AdbDeviceNames")]
    [LocalizedDescription("AdbDeviceNames")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string[]? AdbDeviceNames { get; set; }
    
    [LocalizedCategory("Data")]
    [LocalizedDisplayName("AdbDeviceSerials")]
    [LocalizedDescription("AdbDeviceSerials")]
    [Browsable(true)]
    [DefaultValue(null)]
    public string[]? AdbDeviceSerials { get; set; }

     

    안드로이드 디바이스도 인코딩 문제가 발생할 수 있습니다. 그래서, 장치에 인코딩을 설정할 수 있는 기능도 필요할거 같은데요. 아래와 같이 정의를 미리 해두었습니다. 실제 디바이스에 인코딩을 적용하는 방법은 나중에 코딩하면서 알아보도록 할께요.

    public enum EncodingType
    {
        UTF7 = 0,
        BigEndianUnicode = 1,
        Unicode = 2,
        Default = 3,
        ASCII = 4,
        UTF8 = 5,
        UTF32 = 6,
        EUC_KR = 7,
        MS949 = 8,
        KS_C_5601 = 9
    }

     

    ADB 서버에 클라이언트가 연결할 주소를 호스트와 포트로 입력해줍니다. 이 부분은 디바이스 종류에 따라서 수동으로 처리해야 할수도 있습니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("Host")]
    [LocalizedDescription("Host")]
    [Browsable(true)]
    [DefaultValue("127.0.0.1")]
    public string Host { get; set; } = "127.0.0.1";
    
    [LocalizedCategory("Action")]
    [LocalizedDisplayName("Port")]
    [LocalizedDescription("Port")]
    [Browsable(true)]
    [DefaultValue("62001")]
    public string Port { get; set; } = "62001";

     

    마지막으로 ADB 명령을 수행하기 위한 ADB 설치 위치의 파일을 선택해야 합니다.

    [LocalizedCategory("Action")]
    [LocalizedDisplayName("AdbFile")]
    [LocalizedDescription("AdbFile")]
    [Browsable(true)]
    [DefaultValue(null)]
    [Editor(typeof(TypeEditor.OpenFileSelectorEditor), typeof(UITypeEditor))]
    public string? AdbFile { get; set; }

     

    ADB 서버에 연결하고, 정보를 받아옵니다.

    var id = base.Execute(player);
    
    if (player.Manager.AdbInfo == null)
        player.Manager.AdbInfo = new ConnectionModel();
    
    if (!AdbServer.Instance.GetStatus().IsRunning)
    {
        AdbServer server = new AdbServer();
        StartServerResult result = server.StartServer(AdbFile, false);
        if (result != StartServerResult.Started)
        {
            player.Manager.Output.WriteLine("Can't start adb server", log4net.Core.Level.Error);
            return id;
        }
    }

     

    이미 ADB 서버에 연결되어 있는지 체크하는 로직을 만들었는데요. ADB 서버가 실행중이 아니라면 서버를 실행하고, 해당 서버에 연결 해줍니다.

    if (player.Manager.AdbInfo.ADB == null)
    {
        player.Manager.AdbInfo.ADB = new AdbClient();
        player.Manager.AdbInfo.ADB.Connect($"{Host}:{Port}");
    }

     

    ADB 서버에 연결된 디바이스의 목록을 가져옵니다. 그리고, 정보들을 사용자가 확인할 수 있도록 각각의 내용을 채워줍니다.

    var devices = player.Manager.AdbInfo.ADB.GetDevices();
    
    this.ConnectionDeviceCount = devices.Count();
    this.AdbDeviceNames = devices.Select(s => s.Model).ToArray();
    this.AdbDeviceSerials = devices.Select(s => s.Serial).ToArray();

     

    이제 완성된 코드를 실행 해볼까요? 아래 동영상을 참고해서 ADB 연결 액션을 테스트 해보세요. 참고로, 위에서 윈도우 CMD로 ADB 서버를 실행 해두었기 때문에 아래 테스트에서는 ADB 파일 선택 없이도 정상 동작합니다. 자동으로 내부에서 실행중인 서버를 알 수 있기 때문인데요. 만약, 서버를 adb kill-server 명령으로 죽이면 정상 동작하지 않습니다. 이런 경우 ADB 서버까지 실행하려면 ADB 파일을 직접 선택해줘야 합니다.

     

     

    이렇게해서 안드로이드폰과 녹스나 미뮤와 같은 앱플레이어에서 안드로이드 ADB를 연결하는 방법을 알아봤습니다. 다음에는 실제로 테스트 해볼 수 있는 여러가지 명령들을 개발 해볼께요. 다음에 할 내용은 CommandModel에서 작업하게 될거 같아요. 어느정도 미리 만들어두긴 했지만, 아직 테스트가 완료된게 아니라서 이 부분이 정리되면 다음 시간에는 몇가지 재미있는 기능들을 테스트하고 확인할 수 있을듯합니다. 2부 내용도 많이 기대해주세요^^

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.