Работа с подключаемым модулем Flutter, Android_Alarm_Manager.

Знаю это! Этот плагин работает только на платформе Android! Я лично не знаю ни одного эквивалента для iOS. Кроме того, через месяц после публикации этой статьи я наткнулся на плагин, который предоставляет уведомления как на платформах Android, так и на iOS. Я, конечно, написал статью и эту тоже. См. ниже.

Знайте, что если вы хотите использовать описанный здесь плагин, вы должны явно следовать его файлу readme, чтобы все настроить правильно. Ваш AndroidManfest.xml должен выглядеть примерно так:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="NAME OF YOUR APPLICATION STARTING WITH COM.">
    <!-- The INTERNET permission access.-->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- android_alarm_manager -->
    <!-- Start an Alarm When the Device Boots if past due -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <!-- application needs to have the device stay on -->
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="code_samples"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- android_alarm_manager -->
        <service
                android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
                android:permission="android.permission.BIND_JOB_SERVICE"
                android:exported="false"/>
        <receiver
                android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
                android:exported="false"/>
        <receiver
                android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
                android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

И, конечно же, в вашем файле pubspec.yaml вам понадобится плагин Flutter:

# https://pub.dev/packages/android_alarm_manager
android_alarm_manager: ^0.4.0

Если вы следили за моими статьями, то знаете, что многие из них посвящены библиотечному файлу Dart, содержащему некий "служебный класс-оболочку", который я написал. Чаще всего я писал такой класс для работы с плагином Flutter, который мне был нужен или который был мне полезен. Что ж, эта статья не исключение.

В данном случае это для включения будильника в вашем приложении! В моем случае я обнаружил, что плагин Flutter, android_alarm_manager, отвечал требованиям недавнего приложения, над которым я работал, и поэтому… Я сделал рутину, чтобы легко с ним работать. Если хотите, сделайте копию, сделайте ее своей и поделитесь своими улучшениями. Прохладный?

Теперь, если у вас есть время, продолжайте читать то, что я обнаружил, что мне пришлось сделать, чтобы этот плагин работал почти «безупречно», чтобы легко и быстро установить будильник или любую другую операцию, которая должна выполняться «в фоновом режиме» в какой-то момент. вовремя в будущем, пока приложение работает. Это делает жизнь немного проще, и это хорошо. Верно?

Только скриншоты. Щелкните для Gists.

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

Давай начнем.

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

Сохраняйте статичность

В этом очень простом примере вы должны нажать отображаемую кнопку, и через 5 секунд эти два нуля, как показано на скриншоте ниже, превратятся в единицы. Уууу! Что действительно интересно, конечно, так это то, что скрывается в коде.

Обратите внимание: я изменил исходный код, чтобы учесть асинхронные операции, которые должны быть выполнены до того, как приложение действительно сможет начать работу. Для этого я использовал виджет FutureBuilder. При этом я определил новую функцию под названием initSettings () для инициализации процедуры «Общие настройки», используемой для «запоминания» общего количества сработавших сигналов тревоги, как показано на скриншоте выше.

Вы также можете увидеть в функции initSettings (), показанной ниже, она устанавливает «общий счет» равным нулю, если приложение запускается впервые. Однако именно в самом начале библиотечная процедура AlarmManager инициализируется для настройки конкретной «службы будильника», необходимой для отправки уведомлений на телефоне Android.

Давайте рассмотрим пример кода и посмотрим, что составляет эту маленькую кнопку. Обратите внимание, что в библиотечной подпрограмме используются те же имена для параметров и функций, которые составляют подчеркивающий подключаемый модуль Flutter, Android_Alarm_Manager. Лучше согласиться с такими вещами. Кроме того, как и собственная функция плагина oneShot (), версия этой библиотеки после вызова будет «ждать» в течение указанного времени перед запуском указанной процедуры обратного вызова. В случае этого измененного примера процедура обратного вызова представляет собой анонимную функцию, которая запускается через 5 секунд. На скриншоте ниже приложение было фактически запущено снова, кнопка была нажата еще раз, показывая, благодаря общим настройкам, что кнопка была нажата дважды с момента ее первого запуска. Уии.

Присмотревшись к этой анонимной функции ниже, мы увидим, что ей передается единственный параметр типа integer. Вы можете догадаться, что это то же самое значение id, которое передается второму параметру в качестве случайного числа с помощью функций Dart, Random (). NextInt (pow (2,31)). Зачем теперь передавать это значение, если оно уже передано в параметр рядом с ним? Мы к этому еще вернемся.

На данный момент вы можете видеть ниже, что функция обратного вызова, в свою очередь, вызывает функцию _incrementCounter (). Здесь текущее «общее количество» нажатий кнопок извлекается из процедуры «Общие настройки». Затем он обновляется на единицу с учетом текущего нажатия кнопки и сохраняется в общих настройках. Затем экран приложения обновляется переменной с приращением, _counter, с помощью функции , setState (). Вы до сих пор за всем этим следили?

Сохранение статичности

В отличие от исходного примера (см. Ниже), в этом примере разрешено использовать анонимную функцию и не требуется специально использовать статическую функцию. Конечно, вам также разрешено использовать статическую функцию в этом примере - при условии, что она все еще принимает это единственное целочисленное значение. Однако в исходном примере это должна быть статическая функция или высокоуровневая функция, определенная вне любого класса в этом библиотечном файле Dart - любой из них, как вы видите, является обязательным при работе напрямую с плагином Flutter, Android_Alarm_Manager.

Однако, если вы читали мои статьи, то знаете, что мне нравятся варианты. Все дело в том, чтобы у меня были варианты. Я написал этот служебный класс, чтобы он был более гибким - например, для анонимных функций. Таким образом, при таком расположении передача этого целочисленного значения id в качестве параметра действительно позволяет функции быть статической функцией, функцией высокого уровня или анонимной функцией. Параметры! Опять же, скриншоты исходного примера следуют ниже:

Обратимся к самому служебному классу AlarmManager. Опять же, он предназначен для работы с плагином. И снова, многие параметры являются одними и теми же параметрами, используемыми плагином, и поэтому передаются в этот плагин, но не перед обширным тестированием параметров на предмет допустимых значений. Еще одна необходимая черта таких служебных классов. Он делает всю работу, поэтому вам не нужно. Верно?

На скриншоте ниже показана первая часть этого служебного класса. В статической функции init () мы видим, что плагин действительно инициализирован. Обратите внимание: любые досадные ошибки, которые могут возникнуть при попытке инициализации, будут перехвачены оператором try-catch. Это тоже необходимо для служебных классов. Затем в функции init () есть вспомогательный класс _Callback, который вызывается для инициализации необходимых средств для взаимодействия приложения с отдельным Isolate используется службой сигнализации в фоновом режиме.

Наконец, ниже вы можете видеть, что любые и все значения параметров, отличные от NULL, назначаются конкретным, а также статическим свойствам, составляющим служебный класс AlarmManager. Здесь нет большого количества статики.

Вы обнаружите, что многие свойства и функции, составляющие этот класс, являются статическими. Однако выбор использования статических членов в классе должен быть умеренным. Например, поскольку функция init () является статической, это означает, что ее можно вызывать где угодно, когда угодно и любое количество раз. Этот факт требует дополнительного рассмотрения. В этом случае самым первым однострочным оператором на скриншоте выше является оператор if: 'i f (_init) return _init;'. Это было необходимо при написании эту функцию init (). Теперь вы можете вызывать эту функцию столько раз, сколько захотите. Тем не менее, необходимые службы и плагины инициализируются только при первом вызове. Таким образом, в группе разработчиков, например, если вызов функции init () выполняется по ошибке более одного раза, вреда не будет. Еще одна желательная характеристика полезного класса. Видишь, что я здесь делаю? Делает это своего рода "надежным". Верно?

Инициируйте свои настройки

Кстати, если эти параметры передаются этим статическим переменным, это означает, что в нашем примере у вас есть больше возможностей. Когда вызывается функция init (), настройки можно было указать сразу же. Это позволит любым и всем последующим вызовам функций oneShot (), oneShotAt () и period () использовать эти настройки, если они не указаны явно. Я продемонстрировал это ниже. Вы можете увидеть различия, сделанные в примере кода, если бы вместо этого использовались дополнительные параметры в функции init (). Остается только вызов функции «oneShot» с ее продолжительностью, идентификатором и необходимой функцией обратного вызова. Делает код немного чище. Параметры!

DDНе размещайте init () в функции build ()! Похоже, собственная функция плагина Flutter init (), которая называется AndroidAlarmManager. initialize ();, склонна вызывать побочные эффекты или вопросы. В некоторых случаях он инициирует перестройку (подобно вызову функции setState ()). Вот почему в моем служебном классе есть отдельная функция init (). Желательно, чтобы его вызывали в начале вашего приложения - например, в виджете FutureBuilder с MaterialApp. Убедитесь сами, и в своем модифицированном примере попробуйте закомментировать функцию AlarmManger.init () из initSettings () и вместо этого поместите ее прямо перед ее oneShot () (см. ниже). В этом случае в вашем примере начнут обнаруживаться ошибки.

Возьми свой oneShot

хорошо, вернемся к служебному классу. В функции oneShot () первые три «обязательных» значения параметра проверяются на допустимость и передаются в собственную функцию oneShot () подключаемого модуля. Все, кроме параметра «Функция», обратного вызова. Вместо этого он добавляется к статическому объекту Map, однозначно идентифицируемому предоставленным целочисленным идентификатором, с помощью следующей команды _Callback.oneShots [id] = callback. Мы скоро к этому вернемся. Наконец, вы заметили, что в этом вспомогательном классе _Callback есть вызов статической функции oneShot (). Это необходимая «статическая функция», которая будет использоваться плагином. Остальные значения параметров остаются в том числе статических переменных, использующих оператор объединения с нулем, ??. Оператор используется, поэтому, когда явный параметр не передается, вместо него используются значения в этих статических переменных. Возьми? Между прочим, все эти статические переменные инициализируются значениями по умолчанию, поэтому в сам плагин в конечном итоге не передаются нулевые значения. Отлично.

Не рискуй

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

Кроме того, как и любой хороший служебный класс, у этого класса есть средства, позволяющие разработчику «проверить», была ли операция успешной или нет. Если нет, любое исключение, которое могло произойти, записывается, чтобы разработчик мог его обработать. Это демонстрируется ниже в модифицированном примере с недавно вставленным оператором if.

Более статичный

Далее в служебном классе AlarmManager. Мы видим функцию oneShotAt (). Опять же, поскольку все эти функции являются статическими, в код необходимо включить некоторые меры предосторожности. Например, при неблагоприятных обстоятельствах плагин может не инициализироваться сначала при вызове этой функции onShotAt (). Другими словами, это функция init () не вызывалась первой. Это могло произойти при использовании широкой публикой. На скриншоте ниже видно, что такая ситуация проверяется с помощью функции assert (). Это в надежде, что разработчик поймает такую ​​ошибку в процессе разработки. В производственной среде он перехватывается оператором if, который следует за функцией assert ().

Обратите внимание, что эта функция oneShotAt () имеет собственный объект Map для хранения переданной в функции обратного вызова, а также собственную статическую функцию _Callback.onShatAt (), для передачи в собственную функцию плагина oneShotAt (). Все это, кстати, подразумевает, что вы можете вызывать эти функции любое количество раз в своем приложении, планируя любое количество операций, которые должны произойти в будущем. Конечно, каждому должно быть присвоено собственное уникальное значение идентификатора, помните. В противном случае любая уже запланированная операция будет перезаписана новой, если будет использоваться то же значение id. Вот в чем суть использования уникальных идентификаторов. Верно?

Однако все это также подразумевает, что вы можете использовать один и тот же идентификатор, но между тремя разными функциями: oneShot (), oneShotAt () и периодическим. () раздельно. Помните, что у них есть свои собственные отдельные объекты карты и статические функции. Этот факт хорошо послужил мне в моем недавнем проекте, где в качестве идентификатора использовались те самые значения, которые находятся в основных полях резидентной базы данных. Варианты, детка! Любить это!

Обзор плагина

Теперь бегло взгляните на собственные функции oneShot () и oneShotAt () плагина Flutter, и вы увидите, что его функция oneShot (), фактически просто передает свой параметр своему аналогу oneShotAt (). Обратите внимание, что объект CallbackHandle, который вы видите на снимке экрана ниже, происходит от функции _getCallbackHandle (), которая, в свою очередь, вызвала функцию фреймворка Flutter, PluginUtilities.getCallbackHandle (callback ). Эта операция «отрывает» копию функции обратного вызова, чтобы иметь к ней доступ и вызывать такую ​​функцию в изолированном фоновом режиме. Я тоже к этому вернусь.

Операция обратного вызова

А пока продолжим и рассмотрим «вспомогательный класс» _Callback в файле библиотеки . Ниже вы можете увидеть, что объекты Map, которые добавляются в функцию обратного вызова, определены в этом классе как статические свойства. В этом классе также есть функция init (), которая вызывается в собственной функции init () AlarmManger. Именно в этой функции init () регистрируется «порт связи» с тремя конкретными идентификаторами имени. Порт используется фоновой изоляцией для связи с изоляцией переднего плана, передавая ему сообщения. Угадайте, каковы значения этих идентификаторов имен? Они показаны на снимке экрана ниже и хранятся в переменных _oneShot, _oneShotAt, и _периодический.

Как следует из названия, изоляты - это отдельные сегменты памяти,
которые по замыслу ... изолированы. Нет разделения памяти. Между изолятами происходит только передача сообщений. Содержимое такого сообщения может быть примитивным значением (null, num, bool, double, String), экземпляром объекта SendPort, объектом List или объектом Map с любым из этих примитивных значений, упомянутых первым.

Слушайте в фоновом режиме

Порт дополнительно назначается «слушателем», чтобы реагировать, если и когда сообщение получено, в данном случае фоновым изолятором, выполняющим службу сигнализации. Слушатель представляет собой анонимную функцию, принимающую в качестве параметра объект Map. Вы можете видеть ниже, что объект Map содержит целочисленное значение (которое оказывается идентификатором) и строку, в которой хранится один из этих «идентификаторов имени». Оператор case определяет затем, какой «тип» функции должен запускаться. Видишь, как это работает? Конечно, поскольку это служебный класс, все это заключено в оператор try-catch. Например, мы не знаем, что произойдет, когда выбранная функция будет запущена. Мы хотим перехватывать любые исключения, которые могут возникнуть. Верно?

Отправить сообщение

Итак, как это сообщение отправляется в приложение, работающее на переднем плане? Итак, как только эти идентификаторы имен будут зарегистрированы выше, тогда (см. Ниже) любая из трех функций служебного класса, AlarmManager. oneShot (), AlarmManager . oneShotAt () , и AlarmManager. period () передают три соответствующие статические функции, _Callback. onShot (), _Callback. onShotAt () и _Callback. периодический (), непосредственно в плагин Flutter. Это позволит фоновой Isolate затем передать сообщение обратно в приложение, работающее на переднем плане Isolate. Все три типа вызовов перечислены ниже.

Как видите, это три соответствующие статические функции: _Callback. onShot (), _Callback. onShotAt () и _Callback. period (), которые являются «мостом» между изоляцией фона и изоляцией переднего плана. Например, когда пришло время включить будильник, служба будильника вызовет одну из этих трех статических функций. Обратите внимание, что это не фактическая функция, определенная в изолированном переднем плане, а ее «оторванная копия». Из-за этого вы увидите некоторое противоречивое поведение. Например, любая статическая переменная в этой функции, обычно определяемая в изолированном переднем плане, будет иметь нулевое значение в фоновом изолировании. Вы можете сами проверить это явление.

Например, в нашем модифицированном примере, если я нажал кнопку в третий раз, мы знаем, что объект Map, oneShots, имеет в себе один объект Function, который запускается и обновляет экран через пять секунд, и он делает именно это на снимке экрана процедуры «Слушатель» ниже. Однако в процессе этот объект Map становится пустым, если к нему обращаются в фоновом режиме Isolate ?! Как такое возможно? Это возможно, потому что это копия объекта Map, а не тот, который находится на переднем плане Isolate. Опять же, Isolates могут передавать друг другу только «сообщения». Они не разделяют память.

Опять же, эти три статические функции во вспомогательном классе _Callback перечислены одна за другой. Они показаны на скриншоте ниже. В каждом из них вы можете видеть, что на Isolate переднего плана ссылаются с помощью «идентификаторов имени» и передается объект Map из фонового Isolate. Обратите внимание, что оператор условного доступа к члену ?. используется в случае, если операция поиска возвращает значение null. Так будет, если, например, имени не существует. Это вряд ли когда-либо произойдет, поскольку это весь внутренний код, но, будучи служебным классом, мы не рискуем. Верно?

Все это находится в конце файла библиотеки, и именно здесь мы наконец видим значение этих «идентификаторов имени» в переменных _oneShot, _oneShotAt, и _periodic. Каждый из них назван в честь соответствующего типа функции. Не очень образно, но имеет смысл. Мы также видим, что это переменные высокого уровня, определенные вне класса или функции высокого уровня. Фактически, это постоянные переменные с ключевым словом const. Как и переменные final, переменные const инициализируются только один раз и не могут быть изменены. В отличие от переменных final, они определяются при компиляции приложения. Следовательно, для наших нужд они доступны даже для фоновых изоляторов. Отлично.

Если вы не разбираетесь во всех этих вещах, связанных с Isolate. Не беспокойся об этом прямо сейчас. Вот для чего нужен служебный класс. В отличие от исходного кода примера плагина, вам не нужно беспокоиться о настройке «портов связи» или о том, когда использовать статическую или высокоуровневую функцию или переменную. Именно поэтому я написал этот класс в первую очередь - так что мне тоже не нужно беспокоиться обо всем этом. Вот почему такие служебные классы вообще пишутся - чтобы их можно было использовать снова и снова в наших многочисленных приложениях, которые мы все будем писать в будущем. Верно?

Ваше здоровье.

→ Другие рассказы Грега Перри