Предоставить контроль активности интерфейса в SDK - PullRequest
1 голос
/ 07 марта 2019

В настоящее время мы создаем библиотеку Android, которая содержит Activity.Мы хотим контролировать точный поток и состояние активности, но дать пользователю, который реализует управление библиотекой, как выглядит пользовательский интерфейс.Между тем, мы хотим показать наименьшее количество внутренних классов.

Пользователь SDK может решить, где размещены представления, размеры, цвета;мы решаем, что происходит с onClicks, и предоставляем тексты TextViews.

В действии используется шаблон Model-View-Intent, поэтому мы хотим предоставить неизменяемое состояние.Без настраиваемого пользовательского интерфейса Activity и все ее классы являются внутренними.С настраиваемым пользовательским интерфейсом, гораздо больше классов должны быть обнародованы.Это увеличивает риск нарушения изменений в обновлениях и раскрывает нашу логику.

Чтобы представить пользовательский интерфейс, было предложено несколько решений:

  • Наличие статического обратного вызова для действияэто вызывает onCreate(), так что setContentView() может быть установлено, и вызывает render(state: State) при каждом изменении состояния.Наши классы защищены, как нам нравится, но использование статики для этого сомнительно.

  • Сделайте активность открытой, чтобы пользователи sdk могли создавать ее подклассы.Это означает, что каждый класс, используемый действием, должен быть изменен с внутреннего на общедоступный. Классы, которые на самом деле должны быть внутренними, будут скрыты, запутывая их с помощью ProGuard.

  • Было бы лучше всего передать функцию в Intent, которая, насколько мне известно, невозможна.

  • Передать POJOесли бы мы определили такие параметры, как цвет фона, который является наиболее ограничивающим для пользователя SDK и не рассматривается.

Какое решение является лучшим?Есть ли другой метод, кроме тех, о которых мы думали?

1 Ответ

1 голос
/ 07 марта 2019

Пользователь SDK может решить, где размещены представления, размеры, цвета;мы решаем, что происходит с onClicks, и предоставляем тексты TextViews.

Я представляю это так:

  • Ваш потребитель получает State, ViewGroupи Actions объект, который я объясню позже.
  • На основе State потребитель должен создать несколько представлений и разместить их, как он хочет, в предоставленном ViewGroup.
  • Потребитель также обязан зарегистрировать вышеприведенные представления, используя некоторые Actions.register*Button методы.
  • Вышеприведенная логика выполняется внутри одного метода обратного вызова.Как только указанный метод завершит работу, ваш SDK проверит правильность (всем необходимым действиям назначается интерактивное представление) и продолжит.

Теперь, как передать этот метод обратного вызова в кислый SDK?

1.

Было бы лучше всего передать функцию в Intent

Это на самом деле возможно с относительной легкостью (и, IMO, с некоторыми серьезными недостатками).

В вашем SDK создайте статический Map<Key, Callback> sCallbacks.Когда ваш потребитель регистрирует обратный вызов, используя ваш API, вы создадите для него ключ поиска и сохраните его на карте.Вы можете передать ключ как Intent дополнительно.После того, как ваша активность SDK открыта, он может найти обратный вызов, используя ключ из своего намерения.

Ключ может быть String или UUID или любым другим, который соответствует вашим потребностям, и может быть помещен в намерение.

Плюсы:

  • Это обманчиво легко внедрить и использовать.
  • Потребитель может использовать ваш SDK только изодин файлВесь код вызова находится в одном месте.

Минусы:

  • Вы не отвечаете за создание обратного вызова.Потребитель может создать его как анонимный класс внутри Activity, что приводит к утечке памяти.
  • Вы теряете карту обратного вызова, когда процесс умирает.Если приложение было убито во время действия SDK, вам нужно аккуратно обработать его при перезапуске приложения.
  • Переменные не используются несколькими процессами.Если ваша активность SDK и вызывающий код не совпадают, ваша активность SDK не сможет найти обратный вызов.Помните, что потребитель может изменить процесс своей деятельности и даже переопределить свой собственный процесс активности.

Точка вызова будет выглядеть примерно так: startSdk(context) { state, parent, actions -> /* ... */ }.

Это далекосамый удобный способ для потребителя, но слабости начинают проявляться, как только вы покидаете область типичной настройки потребителя.

2.

Сделайте активность открытой, чтобы пользователи sdkможно разделить на подклассы.

Как вы объяснили, это невозможно без компромиссов с вашей стороны.

Плюсы: ?

Минусы:

  • Потребителю необходимо зарегистрировать свой подкласс и отменить регистрацию исходной активности SDK в AndroidManifest.xml.Я предполагаю, что слияние манифестов включено.Это является болью, так как я часто забываю посмотреть здесь.
  • Потребитель получает easy доступ к вашей деятельности и может свободно прекратить ее, когда пожелает.
  • Если ваша документация нетронута, потребителю будет сложно выяснить, что следует переопределить в вашей деятельности.

Точка вызова будет выглядеть примерно так: startSdk<MySdkActivity>(context).

Я действительно не понимаю преимущества этого варианта как для потребителя.Я теряю преимущества # 1 и ничего не получаю взамен.Как разработчик, я не могу санкционировать такое отношение «пусть потребитель справится с этим».Они сломают вещи, вы получите отчеты об ошибках, и вам в конечном итоге придется с ними справиться.

3.

Здесь я попытаюсь расширить идею, упомянутую первой в комментариях.

Обратным вызовом будет абстрактный класс, определенный вашим SDK.Это будет использоваться следующим образом:

  • Потребитель расширяет этот класс и определяет тело обратного вызова. Класс должен иметь пустой конструктор и быть статичным. Как правило, он будет определен в своем собственном файле.
  • Имя класса передается в намерении вашей активности SDK.
  • Ваш SDK рефлексивно создает экземпляр обратного вызова.

Класс обратного вызова может иметь несколько методов, один может настраивать меню, один может настраивать только иерархию просмотра, один получает всю активность в качестве параметра. Потребитель выберет тот, который им нужен. Опять же, это должно быть хорошо задокументировано, если есть несколько вариантов.

Плюсы:

  • Вы отделяете обратный вызов от сайта вызовов (откуда вызывается ваш SDK). Это хорошо, потому что обратный вызов выполняется внутри вашей активности, полностью отделенной от вызывающего кода.
  • В результате вы не можете утратить активность вызова.
  • Потребителю не нужно трогать AndroidManifest.xml. Это здорово, потому что регистрация вашего обратного вызова не имеет ничего общего с Android. Манифест в основном для вещей, с которыми система взаимодействует.
  • Обратный вызов легко воссоздается после смерти процесса. Он не имеет параметров конструктора и не имеет состояния.
  • Работает в нескольких процессах. Если, по какой-либо причине, потребитель должен общаться между процессами, он отвечает за то, как он этого добивается. Ваш SDK не делает это невозможным, как в случае № 2.
  • Вы можете продолжать занятия internal.

Минусы:

  • Вы должны связать правило proguard, чтобы пустой конструктор каждого класса расширял абстрактный класс обратного вызова. Вам также нужно сохранить их имена классов.

Я предполагаю, что вы скрываете передачу намерений как деталь реализации, чтобы точка входа могла выглядеть примерно как startSdk<MyCallback>(context).

Мне нравится этот, потому что он перекладывает все возможные обязанности с потребителя на вас, разработчика SDK. Вы мешаете потребителю неправильно использовать API. Вы защищаете потребителя от возможных ошибок.

Теперь вернемся к первому абзацу. Пока потребитель может получить контекст (ViewGroup.getContext()), он может получить доступ к деятельности и приложению (в этом процессе). Если и вызывающее действие, и ваше действие SDK находятся в одном и том же процессе, потребитель может даже получить доступ к своему подготовленному компоненту Dagger. Но они не могут переопределить ваши методы деятельности неожиданными способами.

...