1313import android .util .Log ;
1414import androidx .core .app .AlarmManagerCompat ;
1515import androidx .core .app .JobIntentService ;
16- import io .flutter .plugin .common .MethodChannel ;
1716import io .flutter .plugin .common .PluginRegistry .PluginRegistrantCallback ;
18- import io .flutter .view .FlutterCallbackInformation ;
19- import io .flutter .view .FlutterMain ;
20- import io .flutter .view .FlutterNativeView ;
21- import io .flutter .view .FlutterRunArguments ;
2217import java .util .Collections ;
2318import java .util .HashMap ;
2419import java .util .HashSet ;
2722import java .util .List ;
2823import java .util .Set ;
2924import java .util .concurrent .CountDownLatch ;
30- import java .util .concurrent .atomic .AtomicBoolean ;
3125import org .json .JSONException ;
3226import org .json .JSONObject ;
3327
3428public class AlarmService extends JobIntentService {
35- // TODO(mattcarroll): tags should be private. Make private if no public usage.
36- public static final String TAG = "AlarmService" ;
37- private static final String CALLBACK_HANDLE_KEY = "callback_handle" ;
29+ private static final String TAG = "AlarmService" ;
3830 private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids" ;
39- private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin" ;
31+ protected static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin" ;
4032 private static final int JOB_ID = 1984 ; // Random job ID.
41- private static final Object sPersistentAlarmsLock = new Object ();
33+ private static final Object persistentAlarmsLock = new Object ();
4234
43- // TODO(mattcarroll): make sIsIsolateRunning per-instance, not static.
44- private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean (false );
45-
46- // TODO(mattcarroll): make sAlarmQueue per-instance, not static.
47- private static List <Intent > sAlarmQueue = Collections .synchronizedList (new LinkedList <Intent >());
35+ // TODO(mattcarroll): make alarmQueue per-instance, not static.
36+ private static List <Intent > alarmQueue = Collections .synchronizedList (new LinkedList <Intent >());
4837
4938 /** Background Dart execution context. */
50- private static FlutterNativeView sBackgroundFlutterView ;
51-
52- /**
53- * The {@link MethodChannel} that connects the Android side of this plugin with the background
54- * Dart isolate that was created by this plugin.
55- */
56- private static MethodChannel sBackgroundChannel ;
57-
58- private static PluginRegistrantCallback sPluginRegistrantCallback ;
39+ private static FlutterBackgroundExecutor flutterBackgroundExecutor ;
5940
60- // Schedule the alarm to be handled by the AlarmService.
41+ /** Schedule the alarm to be handled by the {@link AlarmService}. */
6142 public static void enqueueAlarmProcessing (Context context , Intent alarmContext ) {
6243 enqueueWork (context , AlarmService .class , JOB_ID , alarmContext );
6344 }
6445
6546 /**
66- * Starts running a background Dart isolate within a new {@link FlutterNativeView}.
67- *
68- * <p>The isolate is configured as follows:
69- *
70- * <ul>
71- * <li>Bundle Path: {@code FlutterMain.findAppBundlePath(context)}.
72- * <li>Entrypoint: The Dart method represented by {@code callbackHandle}.
73- * <li>Run args: none.
74- * </ul>
47+ * Starts the background isolate for the {@link AlarmService}.
7548 *
7649 * <p>Preconditions:
7750 *
7851 * <ul>
7952 * <li>The given {@code callbackHandle} must correspond to a registered Dart callback. If the
8053 * handle does not resolve to a Dart callback then this method does nothing.
81- * <li>A static {@link #sPluginRegistrantCallback } must exist, otherwise a {@link
54+ * <li>A static {@link #pluginRegistrantCallback } must exist, otherwise a {@link
8255 * PluginRegistrantException} will be thrown.
8356 * </ul>
8457 */
8558 public static void startBackgroundIsolate (Context context , long callbackHandle ) {
86- // TODO(mattcarroll): re-arrange order of operations. The order is strange - there are 3
87- // conditions that must be met for this method to do anything but they're split up for no
88- // apparent reason. Do the qualification checks first, then execute the method's logic.
89- FlutterMain .ensureInitializationComplete (context , null );
90- String mAppBundlePath = FlutterMain .findAppBundlePath (context );
91- FlutterCallbackInformation flutterCallback =
92- FlutterCallbackInformation .lookupCallbackInformation (callbackHandle );
93- if (flutterCallback == null ) {
94- Log .e (TAG , "Fatal: failed to find callback" );
59+ if (flutterBackgroundExecutor != null ) {
60+ Log .w (TAG , "Attempted to start a duplicate background isolate. Returning..." );
9561 return ;
9662 }
97-
98- // Note that we're passing `true` as the second argument to our
99- // FlutterNativeView constructor. This specifies the FlutterNativeView
100- // as a background view and does not create a drawing surface.
101- sBackgroundFlutterView = new FlutterNativeView (context , true );
102- if (mAppBundlePath != null && !sIsIsolateRunning .get ()) {
103- if (sPluginRegistrantCallback == null ) {
104- throw new PluginRegistrantException ();
105- }
106- Log .i (TAG , "Starting AlarmService..." );
107- FlutterRunArguments args = new FlutterRunArguments ();
108- args .bundlePath = mAppBundlePath ;
109- args .entrypoint = flutterCallback .callbackName ;
110- args .libraryPath = flutterCallback .callbackLibraryPath ;
111- sBackgroundFlutterView .runFromBundle (args );
112- sPluginRegistrantCallback .registerWith (sBackgroundFlutterView .getPluginRegistry ());
113- }
63+ flutterBackgroundExecutor = new FlutterBackgroundExecutor ();
64+ flutterBackgroundExecutor .startBackgroundIsolate (context , callbackHandle );
11465 }
11566
11667 /**
117- * Called once the Dart isolate ({@code sBackgroundFlutterView }) has finished initializing.
68+ * Called once the Dart isolate ({@code flutterBackgroundExecutor }) has finished initializing.
11869 *
11970 * <p>Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code
12071 * AlarmService.initialized} message. Processes all alarm events that came in while the isolate
12172 * was starting.
12273 */
123- // TODO(mattcarroll): consider making this method package private
124- public static void onInitialized () {
74+ /* package */ static void onInitialized () {
12575 Log .i (TAG , "AlarmService started!" );
126- sIsIsolateRunning .set (true );
127- synchronized (sAlarmQueue ) {
76+ synchronized (alarmQueue ) {
12877 // Handle all the alarm events received before the Dart isolate was
12978 // initialized, then clear the queue.
130- Iterator <Intent > i = sAlarmQueue .iterator ();
79+ Iterator <Intent > i = alarmQueue .iterator ();
13180 while (i .hasNext ()) {
132- executeDartCallbackInBackgroundIsolate (i .next (), null );
81+ flutterBackgroundExecutor . executeDartCallbackInBackgroundIsolate (i .next (), null );
13382 }
134- sAlarmQueue .clear ();
83+ alarmQueue .clear ();
13584 }
13685 }
13786
138- /**
139- * Sets the {@link MethodChannel} that is used to communicate with Dart callbacks that are invoked
140- * in the background by the android_alarm_manager plugin.
141- */
142- public static void setBackgroundChannel (MethodChannel channel ) {
143- sBackgroundChannel = channel ;
144- }
145-
14687 /**
14788 * Sets the Dart callback handle for the Dart method that is responsible for initializing the
14889 * background Dart isolate, preparing it to receive Dart callback tasks requests.
14990 */
15091 public static void setCallbackDispatcher (Context context , long callbackHandle ) {
151- SharedPreferences prefs = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
152- prefs .edit ().putLong (CALLBACK_HANDLE_KEY , callbackHandle ).apply ();
153- }
154-
155- public static boolean setBackgroundFlutterView (FlutterNativeView view ) {
156- if (sBackgroundFlutterView != null && sBackgroundFlutterView != view ) {
157- Log .i (TAG , "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView" );
158- return false ;
159- }
160- sBackgroundFlutterView = view ;
161- return true ;
162- }
163-
164- public static void setPluginRegistrant (PluginRegistrantCallback callback ) {
165- sPluginRegistrantCallback = callback ;
92+ FlutterBackgroundExecutor .setCallbackDispatcher (context , callbackHandle );
16693 }
16794
16895 /**
169- * Executes the desired Dart callback in a background Dart isolate.
96+ * Sets the {@link PluginRegistrantCallback} used to register the plugins used by an application
97+ * with the newly spawned background isolate.
17098 *
171- * <p>The given {@code intent} should contain a {@code long} extra called "callbackHandle", which
172- * corresponds to a callback registered with the Dart VM.
99+ * <p>This should be invoked in {@link Application.onCreate} with {@link
100+ * GeneratedPluginRegistrant} in applications using the V1 embedding API in order to use other
101+ * plugins in the background isolate. For applications using the V2 embedding API, it is not
102+ * necessary to set a {@link PluginRegistrantCallback} as plugins are registered automatically.
173103 */
174- private static void executeDartCallbackInBackgroundIsolate (
175- Intent intent , final CountDownLatch latch ) {
176- // Grab the handle for the callback associated with this alarm. Pay close
177- // attention to the type of the callback handle as storing this value in a
178- // variable of the wrong size will cause the callback lookup to fail.
179- long callbackHandle = intent .getLongExtra ("callbackHandle" , 0 );
180- if (sBackgroundChannel == null ) {
181- Log .e (
182- TAG ,
183- "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out." );
184- return ;
185- }
186-
187- // If another thread is waiting, then wake that thread when the callback returns a result.
188- MethodChannel .Result result = null ;
189- if (latch != null ) {
190- result =
191- new MethodChannel .Result () {
192- @ Override
193- public void success (Object result ) {
194- latch .countDown ();
195- }
196-
197- @ Override
198- public void error (String errorCode , String errorMessage , Object errorDetails ) {
199- latch .countDown ();
200- }
201-
202- @ Override
203- public void notImplemented () {
204- latch .countDown ();
205- }
206- };
207- }
208-
209- // Handle the alarm event in Dart. Note that for this plugin, we don't
210- // care about the method name as we simply lookup and invoke the callback
211- // provided.
212- // TODO(mattcarroll): consider giving a method name anyway for the purpose of developer discoverability
213- // when reading the source code. Especially on the Dart side.
214- sBackgroundChannel .invokeMethod (
215- "" , new Object [] {callbackHandle , intent .getIntExtra ("id" , -1 )}, result );
104+ public static void setPluginRegistrant (PluginRegistrantCallback callback ) {
105+ // Indirectly set in FlutterBackgroundExecutor for backwards compatibility.
106+ FlutterBackgroundExecutor .setPluginRegistrant (callback );
216107 }
217108
218109 private static void scheduleAlarm (
@@ -285,6 +176,7 @@ private static void scheduleAlarm(
285176 }
286177 }
287178
179+ /** Schedules a one-shot alarm to be executed once in the future. */
288180 public static void setOneShot (Context context , AndroidAlarmManagerPlugin .OneShotRequest request ) {
289181 final boolean repeating = false ;
290182 scheduleAlarm (
@@ -301,6 +193,7 @@ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShot
301193 request .callbackHandle );
302194 }
303195
196+ /** Schedules a periodic alarm to be executed repeatedly in the future. */
304197 public static void setPeriodic (
305198 Context context , AndroidAlarmManagerPlugin .PeriodicRequest request ) {
306199 final boolean repeating = true ;
@@ -320,6 +213,7 @@ public static void setPeriodic(
320213 request .callbackHandle );
321214 }
322215
216+ /** Cancels an alarm with ID {@code requestCode}. */
323217 public static void cancel (Context context , int requestCode ) {
324218 // Clear the alarm if it was set to be rescheduled after reboots.
325219 clearPersistentAlarm (context , requestCode );
@@ -364,7 +258,7 @@ private static void addPersistentAlarm(
364258 String key = getPersistentAlarmKey (requestCode );
365259 SharedPreferences prefs = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
366260
367- synchronized (sPersistentAlarmsLock ) {
261+ synchronized (persistentAlarmsLock ) {
368262 Set <String > persistentAlarms = prefs .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
369263 if (persistentAlarms == null ) {
370264 persistentAlarms = new HashSet <>();
@@ -383,7 +277,7 @@ private static void addPersistentAlarm(
383277
384278 private static void clearPersistentAlarm (Context context , int requestCode ) {
385279 SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
386- synchronized (sPersistentAlarmsLock ) {
280+ synchronized (persistentAlarmsLock ) {
387281 Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
388282 if ((persistentAlarms == null ) || !persistentAlarms .contains (requestCode )) {
389283 return ;
@@ -399,7 +293,7 @@ private static void clearPersistentAlarm(Context context, int requestCode) {
399293 }
400294
401295 public static void reschedulePersistentAlarms (Context context ) {
402- synchronized (sPersistentAlarmsLock ) {
296+ synchronized (persistentAlarmsLock ) {
403297 SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
404298 Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
405299 // No alarms to reschedule.
@@ -449,15 +343,11 @@ public static void reschedulePersistentAlarms(Context context) {
449343 @ Override
450344 public void onCreate () {
451345 super .onCreate ();
452-
453- Context context = getApplicationContext ();
454- FlutterMain .ensureInitializationComplete (context , null );
455-
456- if (!sIsIsolateRunning .get ()) {
457- SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
458- long callbackHandle = p .getLong (CALLBACK_HANDLE_KEY , 0 );
459- startBackgroundIsolate (context , callbackHandle );
346+ if (flutterBackgroundExecutor == null ) {
347+ flutterBackgroundExecutor = new FlutterBackgroundExecutor ();
460348 }
349+ Context context = getApplicationContext ();
350+ flutterBackgroundExecutor .startBackgroundIsolate (context );
461351 }
462352
463353 /**
@@ -470,17 +360,17 @@ public void onCreate() {
470360 * intent}, then the desired Dart callback is invoked immediately.
471361 *
472362 * <p>If there are any pre-existing callback requests that have yet to be executed, the incoming
473- * {@code intent} is added to the {@link #sAlarmQueue } to invoked later, after all pre-existing
363+ * {@code intent} is added to the {@link #alarmQueue } to invoked later, after all pre-existing
474364 * callbacks have been executed.
475365 */
476366 @ Override
477367 protected void onHandleWork (final Intent intent ) {
478368 // If we're in the middle of processing queued alarms, add the incoming
479369 // intent to the queue and return.
480- synchronized (sAlarmQueue ) {
481- if (!sIsIsolateRunning . get ()) {
370+ synchronized (alarmQueue ) {
371+ if (!flutterBackgroundExecutor . isRunning ()) {
482372 Log .i (TAG , "AlarmService has not yet started." );
483- sAlarmQueue .add (intent );
373+ alarmQueue .add (intent );
484374 return ;
485375 }
486376 }
@@ -493,7 +383,7 @@ protected void onHandleWork(final Intent intent) {
493383 new Runnable () {
494384 @ Override
495385 public void run () {
496- executeDartCallbackInBackgroundIsolate (intent , latch );
386+ flutterBackgroundExecutor . executeDartCallbackInBackgroundIsolate (intent , latch );
497387 }
498388 });
499389
0 commit comments