NGMsoftware

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

    학습


    C# C# .NET 매크로 프로그램 만들기. (안드로이드 기기에서 앱을 실행하거나 중지하는 방법 with ADB 3부)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 오늘은 안드로이드 기기에 설치되어 있는 앱을 실행하고, 종료시키는 방법을 알아볼께요. 사실, 이 기능을 구현할 때 몇가지 문제가 있었어요. 이전 버전에서 지원하던 기능들이 사라지고, 새로운 기능들이 추가되었기 때문인데요. 이미 한번 만들어본 기능이라서 크게 문제가 안될줄 알았는데... 생각보다 많은 시간을 들여서 겨우 완성하게 되었습니다.

     

    일단, 이전 시간에 ADB 명령 액션을 만들어 두었기 때문에 몇가지만 코드를 더 추가하면 됩니다. 그리고, 개발자라면 대충 내용을 보면 이해할 수 있는 것들이지만 일반인 분이라면 기본적이 내용부터 이해가 안갈수도 있어요. 이 부분은 다음에 만들 앱 설치와 삭제 기능을 구현할 때 다시한번 언급해보도록 할께요. 그리고, 앱 실행과 중지는 앱의 패키지명을 미리 알아야 합니다. 이 부분을 사용자가 직접 알아낼 수 있지만, 그렇게 하려면 ADB에 대한 공부를 해야 합니다. 대부분의 사용자는 이런 내용을 공부하는데 시간을 허비하고 싶어하지 않을거예요. 그래서, 자동으로 설치된 앱 목록을 가져와서 보여줄 수 있어야 할거 같습니다.

     

    이전 시간에 추가한 switch ~ case에 AppStart와 AppStop을 추가하고, 아래와 같이 코딩을 추가해줍니다.

    switch (AdbCommand)
    {
        case Definition.AdbCommand.Home:
            if (player.Manager.AdbInfo.AdbFilter == null || player.Manager.AdbInfo.AdbFilter.Length == 0)
                player.Manager.AdbInfo.ADB.ClickHomeButtonAsync(device, player.CT);
            else
            {
                if (player.Manager.AdbInfo.AdbFilter.Contains(device.Model) || player.Manager.AdbInfo.AdbFilter.Contains(device.Serial))
                    player.Manager.AdbInfo.ADB.ClickHomeButtonAsync(device);
            }
            break;
        case Definition.AdbCommand.Back:
            if (player.Manager.AdbInfo.AdbFilter == null || player.Manager.AdbInfo.AdbFilter.Length == 0)
                player.Manager.AdbInfo.ADB.ClickBackButtonAsync(device, player.CT);
            else
            {
                if (player.Manager.AdbInfo.AdbFilter.Contains(device.Model) || player.Manager.AdbInfo.AdbFilter.Contains(device.Serial))
                    player.Manager.AdbInfo.ADB.ClickBackButtonAsync(device);
            }
            break;
        case Definition.AdbCommand.StartApp:
            if (player.Manager.AdbInfo.AdbFilter == null || player.Manager.AdbInfo.AdbFilter.Length == 0)
                player.Manager.AdbInfo.ADB.StartAppAsync(device, ADBPackageName);
            else
            {
                if (player.Manager.AdbInfo.AdbFilter.Contains(device.Model) || player.Manager.AdbInfo.AdbFilter.Contains(device.Serial))
                    player.Manager.AdbInfo.ADB.StartAppAsync(device, ADBPackageName);
            }
            break;

     

    ADBPackageName 속성은 AndroidAppConverter 어트리뷰트(Attribute)가 부여되어 있습니다.

    [LocalizedCategory("Package")]
    [LocalizedDisplayName("ADBPackageName")]
    [LocalizedDescription("ADBPackageName")]
    [Browsable(true)]
    [DefaultValue(null)]
    [TypeConverter(typeof(TypeConverter.AndroidAppConverter))]
    public string? ADBPackageName { get; set; }

     

     

    AndroidAppConverter 어트리뷰트는 현재 연결된 ADB 서버의 모든 클라이언트에서 설치된 앱의 패키지명을 가져옵니다. 좀 더 사용자 친화적인 이름을 알려주면 좋겠지만, 워낙에 많은 앱들이 있고 일부 앱들은 이름이 동일하기도 해서 구글도 어쩔 수 없는 선택이었을거 같아요. 패키지명은 유니크하기 때문에 큰 문제가 되지는 않을겁니다. 참고로, 안드로이드 앱 개발을 해보신 분들은 아시겠지만, 패키지명은 폴더와 프로젝트로 만들어진 명칭입니다. 닷넷으로 치면 네임스페이스와 클래스정도 되겠네요.

     

    컨버터는 PropertyGrid의 Property(속성)을 커스터마이징 할 수 있습니다. 그리고, 데이터를 가져와서 표시할 수 있는데요. 런타임에 처리할 수 있어서 많이 사용하는 기능입니다.

    internal class AndroidAppConverter : System.ComponentModel.TypeConverter

     

    컨버터는 프라이빗 맴버를 2개 가지고 있습니다. _items는 사용자에게 표시할 목록이고, _filterKey는 목록에서 사용자가 입력한 내용으로 필터링해서 목록을 표시하기 위한 값입니다. 목록의 내용이 많으면 불필요하게 찾는데 오래 걸리는데요. 필터 키워드를 사용하면 좀 더 빠르게 원하는 값을 탐색할 수 있습니다.

    private List<Data.BaseItem>? _items;
    private string? _filterKey = string.Empty;

     

    안드로이드 장치에 설치되어 있는 모든 앱의 패키지명을 가져오려면 아래와 같이 코드를 작성하면 됩니다.

    try
    {
        if (_items == null)
            _items = new List<Data.BaseItem>();
    
        _items.Clear();
    
        ProcessStartInfo processInfo = new ProcessStartInfo();
    
        processInfo.FileName = "cmd.exe";
    
        if (model.AllPackage)
            processInfo.Arguments = "/c adb shell cmd package list packages";
        else
            processInfo.Arguments = "/c adb shell cmd package list packages -3";
    
        processInfo.UseShellExecute = false;
        processInfo.ErrorDialog = true;
        processInfo.CreateNoWindow = true;
        processInfo.WindowStyle = ProcessWindowStyle.Hidden;
        processInfo.RedirectStandardOutput = true;
    
        var proc = Process.Start(processInfo);
    
        while (!proc.StandardOutput.EndOfStream)
        {
            string result = proc.StandardOutput.ReadLine();
    
            if (!string.IsNullOrEmpty(result))
            {
                result = result.Replace("package:", "");
    
                if (string.IsNullOrEmpty(_filterKey))
                    _items.Add(new Data.TextItem() { Text = result });
                else
                {
                    if (result.Contains(_filterKey))
                        _items.Add(new Data.TextItem() { Text = result });
                }
            }
        }
    
        _items.Sort();
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
        if (_items != null && _items.Count > 0)
            _items.Clear();
    }

     

    여기서 핵심 코드는 아래 ADB 명령입니다. 사용자에게 목록으로 보여주기 위해서 엔지엠 매크로는 프로세스로 한번 감싸서 실행하고, 반환 값을 가져와서 목록으로 가공합니다.

    adb shell cmd package list packages

     

    참고로, 아래와 같이 옵션을 추가하면 기본 설치된 앱을 제외하고 사용자가 설치한 앱 목록만 볼 수 있습니다.

    adb shell cmd package list packages -3

     

    그리고, ADB가 복잡한 이유중에 하나가 대부분 멀티로 작업을 진행하기 때문이라고 생각해요. 컴퓨터에 안드로이드 기기가 하나만 존재하면 복잡하고, 어려운 문제가 발생하지 않습니다. 모든 일이 다 그래요. 동시에 뭔가를 진행할 때 항상 문제가 발생합니다. 그렇다고 해서 해결할 수 없는 문제도 아닌데요. 다만, 해결 방법을 찾는것 그리고, 해결 방법을 적용하고 테스트 해보는것이 생각보다 오래걸리고 스트레스가 될 수 있어요.

     

    인생에서 대부분 겪어보는 것들인데요. 프로그래밍이든 매크로 제작이든 항상 비슷합니다. 알면 간단하지만 모르면 답답하고 포기하고 싶어지고 더 쉬운 길을 찾아가려고 해요. 아무튼, 위의 명령이 항상 잘 동작하지만 멀티로 환경을 구성하면 문제가 될 수 있습니다. 어떤 기기를 대상으로 정보를 가져올지를 선택 해줘야 합니다. 그리고, ADB에서 직접 정보를 확인할 수 있기도 합니다.

     

    ADB 연결 액션의 속성에는 연결된 모든 기기의 시리얼을 목록으로 보여줍니다. 목록에서 원하는 기기를 찾아서 변수로 넘겨받아서 처리해도 됩니다. 방식은 다양하니까요. 문제를 해결할 수 있는 방법을 알아내기만 하면 그 이후로는 문제될게 없습니다. 그러나, 좀 더 편리하게 방법을 제공하려면 아래와 같이 명령 액션에 기기의 정보를 가져올 수 있는 방법을 알려줘야 합니다.

     

    패키지 카테고리에 디바이스를 선택할 수 있는 속성을 하나 만들고 컨버터도 추가할께요.

    [LocalizedCategory("Package")]
    [LocalizedDisplayName("AdbDeviceSerial")]
    [LocalizedDescription("AdbDeviceSerial")]
    [Browsable(true)]
    [DefaultValue(null)]
    [TypeConverter(typeof(TypeConverter.AndroidDeviceConverter))]
    public string? AdbDeviceSerial { get; set; }

     

    컨버터 코드는 아래와 같습니다. 이미 ADB 서버에 연결되어 있다면 서버로부터 목록을 받아오고, 서버에 연결이 안되어 있다면 윈도우 커멘드에서 쿼리하는 방식입니다.

    var model = context.Instance as Ai.Model.ExternalAPI.ADB.CommandModel;
    var client = System.Windows.Forms.Application.OpenForms.Cast<System.Windows.Forms.Form>().First() as Ai.Interface.IClient;
    var manager = client.PlayerManager;
    
    try
    {
        if (_items == null)
            _items = new List<Data.BaseItem>();
    
        _items.Clear();
    
        if (manager.AdbInfo == null)
        {
            ProcessStartInfo processInfo = new ProcessStartInfo();
            processInfo.FileName = "cmd.exe";
            processInfo.Arguments = $"/c adb get-serialno";
    
            processInfo.UseShellExecute = false;
            processInfo.ErrorDialog = true;
            processInfo.CreateNoWindow = true;
            processInfo.WindowStyle = ProcessWindowStyle.Hidden;
            processInfo.RedirectStandardOutput = true;
    
            var proc = Process.Start(processInfo);
    
            while (!proc.StandardOutput.EndOfStream)
            {
                string result = proc.StandardOutput.ReadLine();
    
                if (!string.IsNullOrEmpty(result))
                {
                    result = result.Replace("package:", "");
    
                    if (string.IsNullOrEmpty(_filterKey))
                        _items.Add(new Data.TextItem() { Text = result });
                    else
                    {
                        if (result.Contains(_filterKey))
                            _items.Add(new Data.TextItem() { Text = result });
                    }
                }
            }
        }
        else
            _items.AddRange(manager.AdbInfo.ADB.GetDevices().Select(s => new Data.TextItem() { Text = s.Serial }));
    
        _items.Sort();
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    
        if (_items != null && _items.Count > 0)
            _items.Clear();
    }

     

     

    완성된 매크로를 실행하고, 아래 동영상과 같이 테스트 해보세요. 녹스 앱플레어는 Port가 62001이고, 안드로이드 기기의 Port는 5703입니다. 서로 포트가 달라서 따로 연결 설정을 가지고 있어야 동시에 제어할 수 있습니다. 이 부분은 나중에 테스트하면서 다시 알아보도록 할께요. 하지만, 일반적인 환경에서는 녹스, 미뮤등등... 앱플레어만 사용하거나 핸드폰만 연결해서 사용하시는걸 추천합니다.

     

     

    개발자에게 후원하기

    MGtdv7r.png

     

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

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

    감사합니다~

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

    댓글목록

    등록된 댓글이 없습니다.