ADB Uninstall плагин для Android Studio (IntelliJ IDEA)


    Так случилось, что я проиграл один спор и мне пришлось побриться налысо. Т.к. больше ничего не сдерживало поток свежих идей к моей голове, а на улицу показаться было стыдно, то я задался целью написать общественно-полезный плагин к одной из лучших сред разработки. Функционал прост, как паренный початок маиса, но очень нужный (ума не приложу, почему это еще не встроено по-умолчанию). Итак, сегодня я постараюсь поделиться опытом разработки плагина, который позволит вам элегантно удалять приложения над которыми вы работаете с подключенных андроид устройств.

    Для тех, кто просто хочет начать пользоваться, вот как это выглядит (вызывается по AltGraph+U):

    Качать отсюда (ver. 1.0.1): github.com/Ghedeon/ADB-Uninstall/releases
    Установка: Settings → Plugins → Install plugin from disk…
    Важно: Плагин сырой, поэтому очень надеюсь, что установившие отпишутся о проблемах. Буду исправлять по мере возможноcти. Отзывы, замечания, предложения по расширению функционала — все приветствуется!

    Приведу несколько повседневных сценариев андроид-разработчиков (особенно при работе в команде):

    Use-Case 1:
    Команда работает на одним приложением. Вы только что обменялись с кем-то девайсами и терпеливо ждете пока закончится установка. Что в итоге? Правильно, Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES]. Логично, потому что на девайсе осталось приложение от коллеги, debug keys, которыми подписаны приложения у всех разные, соответсвенно update не проходит. Чертыхаясь, вы лезете в командную строку, набираете "adb uninstall <package>" и почти физически ощущаете, как по байтам сносится программа.

    Use-Case 2:
    Все тоже самое, только после "adb uninstall" ничего не происходит, потому что у вас в системе не одно, а два устройства и adb не знает с какого удалять. Обняв мозгом всю глупость ситуации, в расстроенных чувствах вы набираете "adb devices", получаете список устройств, потом "adb -s <deviceId> uninstall <package>" и мир сдается перед вами.

    Use-Case 3:
    У вас несколько подключенных устройств и удалить нужно со всех. Ну, тут уже самый ленивый пойдет писать скрипт, который хоть как-то автоматизирует всю эту кухню.

    Use-Case 4:
    Никаких проблем нет, но вам просто лень писать "adb uninstall <package>".

    Решения:
    • Использовать один debug key всеми членами команды, тогда, с точки зрения системы, все будет выглядеть как-будто вы работаете за один компьютером.
    • Самописный скрипт и, желательно, достаточно умный, чтобы не спрашивать вас об имени пакета.

    Собственно скриптом я и перебивался до последнего времени и меня он полностью устраивал, пока начальство не потребовало активнее налаживать контакты в коллективе. Вариант интересоваться здоровьем чьего-то попугая, из последних сил стараясь выдержать искренне-озабоченный вид был отвергнут, как безперспективный, поэтому я решил сваять более кроссплатформенное решение своей поделки и уже с ним пойти в люди.
    Сам скрипт
    #!/bin/bash
    package=`grep -o 'package=".*"' AndroidManifest.xml | grep -o \".* | sed 's/"//g'`
    #adb uninstall $package
    adb devices | tail -n +2 | awk '{print $1}' | xargs -I {} adb -s {} uninstall $package
    


    Разработка


    О плагинах для IDEA на хабре уже писалось, чего только стоит монументальный перевод в семи частях от Lucyfer, поэтому общую структуру и настройку IDE я опущу. Для того чтобы добавить собственный пункт в меню и/или тулбар наследуемся от AnAction и переопределяем метод actionPerformed(). Первым делом узнаем абсолютный путь до Android SDK:
    ProjectRootManager.getInstance(event.getProject()).getProjectSdk().getHomePath();
    

    если к этому добавить /platfrom-tools, то у нас будет путь к рабочей папке, откуда можно будет запустить adb.
    Примечание
    Изначально я решил идти иным путем: понадеяться на то, что у пользователя правильно настроены все пути до Android SDK в переменных окружения и просто передать их в свой процесс. Тогда нам не нужен абсолютный путь, интерпретатор будет знать о командах, которые мы хотим запустить. Однако, под Linux и Mac OS переменные окружения, как известно, можно запихнуть в тысячу и одно сравнительно приличное место, что мешало мне c определенностью сказать, что у меня будет в System.getenv(). В общем, при тех или иных настройках системы этот метод заваливался и я решил отступить. Позже, покопавшись в исходниках андроидовского плагина для IDEA, я обнаружил, что они, как и я, используют абсолютные пути.

    Получаем список подключенных устройств:
    Process pr = Runtime.getRuntime().exec(toolPath + File.separator + "adb devices");
    

    Я использовал проженный Runtime.exec() из Java, но, в то же время, для подобных дел IDEA предлагает более кошерный GeneralCommandLine. По правде, этот переход у меня до сих пор в TODO и я буду очень признателен, если кто-то поделится опытом.

    Теперь нужно определить имя пакета, который будем удалять. Тут могут быть варианты. Андроид проект может содержать несколько исполняемых модулей с разными именами пакетов. Еще могут быть Library Projects, но о них речь не идет. Первое что пришло в голову — брать имя пакета из AndroidManifest.xml активного в данный момент модуля. Но это оказалось не очень удобно, работать вы можете не в том модуле, который хотите удалить, поэтому приходилось предварительно выделять мышкой нужный модуль. В итоге я остановился на куда более логичном решении — извлекать имя пакета из манифеста того модуля, который указан в текущей конфигурации запуска. Поясню. Как известно, чтобы установить приложение, нужна как минимум одна конфигурация запуска (Run Configuration). При создании этой самой Run Configuration, во вкладке General, вы указываете Module. Из манифеста указанного модуля и будем брать имя пакета. Если же конфигураций несколько — будем брать ту, что активна в данный момент.
    Получаем активную конфигурацию:
    RunManager runManager = RunManager.getInstance(event.getProject());
    ModuleBasedConfiguration selectedConfiguration = (ModuleBasedConfiguration) runManager.getSelectedConfiguration().getConfiguration();
    

    Следующим шагом будет нахождение точного пути до папки с AndroidManifest.xml. В случае с проектами со старой структурой это будет просто корень модуля. Для gradle-based проектов же, это как правило currentModulePath/src/main, что необходимо учитывать. Сам корень модуля получаем следующим образом:
    Module module = selectedConfiguration.getConfigurationModule().getModule();
    currentModuleFilePath = module.getModuleFilePath();
    String currentModulePath = currentModuleFilePath.substring(0, currentModuleFilePath.lastIndexOf(File.separator));
    

    Распарсить найденный AndroidManifest.xml тем же StAX парсером, на предмет атрибута package, уже тривиальная задача.

    Фактически, полученных данных (deviceId и package) достаточно для того, чтобы непосредственно приступить к выполнению adb uninstall. Но перед этим мы сделаем еще одну, по большей части косметическую, вещь. Различать устройства по deviceId не очень удобно, поэтому попробуем предоставить пользователю больше human-readable («удобочитаемой человеком»?) информации. Для этой цели, выполним для каждого устройства следующую команду:
    adb -s <deviceId> shell cat /system/build.prop;
    

    Результатом которой будет длинный список (фактически это ассоциативный массив) различных характеристик устройства. Выбираем только нужные нам, а именно:
    ro.product.manufacturer
    ro.product.model
    ro.build.version.release
    ro.build.version.sdk
    

    В итоге, вместо абстрактного заклинания R32CC02G02Z пользователь увидит гордое: «Samsung Nexus 10 Android 4.3 (API 18)».

    Дело за малым, показываем пользователю модальное окно со всем этим добром, получаем список выбранных устройств и, в порядке живой очереди, запускаем вожделенный adb uninstall:
    Process pr = Runtime.getRuntime().exec(toolPath + File.separator + "adb -s " + device.getSerialNumber() + " uninstall " + packageName);
    

    Это все.

    Исходный код проекта на GitHub: github.com/Ghedeon/ADB-Uninstall.

    Дополнение:

    • Для логов и различных сообщений, адресованных пользователю, используется Notifications.Bus.notify(<notification>). В зависимости от пользовательских настроек, сообщение появится в одном или сразу нескольких местах пользовательского интерфейса: Event Log, Balloon, Sticky Balloon, Tool window balloon. Либо не появится вообще.
    • Для взаимодействия с пользователем, чтобы ваши надстройки смотрелись органично, лучше все-таки использовать не голый Swing, a компоненты, которые предлагает IntelliJ IDEA. В частности, я использовал Dialog Wrapper из этого набора.
    • Важным нереализованным пунктом для меня остается сбор статистики, хотя бы по сбоям в работе плагина. На данный момент складывается какая-то курьезная ситуация, когда в ответ на крэш плагина IDEA предлагает отправить баг-репорт в Гугл. Буду благодарен за наводку, если кому-то известны интсрументы вроде Crashlytics, но только для Swing приложений.

    Хотелось бы отметить пользователей Lucyfer и yole. Благодарю ребят за дельные советы.
    • +43
    • 18,7k
    • 6
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 6

      +23
      Самое внезапное начало статьи 2013.
        0
        Функционал прост, как паренный початок маиса
        Но почему не пареная репа? И почему маис а не кукуруза?
          +8
          Это сложно объяснить. Топикастера зовут «Злой Щавель», что многое раскрывает :)
        0
        В eclipse в одной из последних версий ADT добавили фичу, что при установке приложения, в случая конфликта подписей, выдается сообщение и нажатием кнопки yes, приложение удаляется и тут же заливается ваше.
          0
          А есть какой-нибудь способ интегрироваться с Android плагином? Что-бы не парсить манифест самому и не вытягивать названия девайса.

          А вообще, в принципе, можно обменятся Debug ключами с коллегами.
            0
            IDEA предоставляет концепт расширений (<extensions>) и точек расширения (<extensionPoint>), которые позволяют взаимодействовать с другими плагинами и ядром IDEA. Т.е. интегрироваться можно, если в андроид плагин заложена для этого основа в виде точек расширения. Известные мне точки расширения андроид плагина:
            <extensionPoints>
                <extensionPoint name="mavenProvider" interface="org.jetbrains.android.maven.AndroidMavenProvider"/>
                <extensionPoint name="lightBuildProvider" interface="org.jetbrains.android.compiler.AndroidLightBuildProvider"/>
                <extensionPoint name="refactoringContextProvider" interface="org.jetbrains.android.refactoring.AndroidRefactoringContextProvider"/>
            </extensionPoints>
            

            Практически как и по всему, что касается разработки плагинов, по ним полностью отсутствует какая бы то ни была документация.
            Беглый анализ показал, что ничего сверхестественного там не происходит, нет никакого магического hidden API на котором общаются андроид плагин и SDK. Просто из Android SDK дергаются доступные всем утилиты (adb, aapt, dexdump...) с нужными ключами. Где-то прописанный package был бы очень кстати, но вот название девайса я бы точно нигде не искал. Уж больно непостоянная информация, не вижу причин, по которым они бы ее где-то хранили.

            Кстати, если бы не первое предложение, я бы решил, что пост вы не читали). Вариант с обменом ключей в статье указан первым в параграфе «Решения». Даже в этом случае плагин — хорошее подспорье, т.к., кроме прочего, позволяет удалять одновременно.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое