13
13
import android .util .Log ;
14
14
import androidx .core .app .AlarmManagerCompat ;
15
15
import androidx .core .app .JobIntentService ;
16
- import io .flutter .plugin .common .MethodChannel ;
17
16
import 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 ;
22
17
import java .util .Collections ;
23
18
import java .util .HashMap ;
24
19
import java .util .HashSet ;
27
22
import java .util .List ;
28
23
import java .util .Set ;
29
24
import java .util .concurrent .CountDownLatch ;
30
- import java .util .concurrent .atomic .AtomicBoolean ;
31
25
import org .json .JSONException ;
32
26
import org .json .JSONObject ;
33
27
34
28
public 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" ;
38
30
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" ;
40
32
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 ();
42
34
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 >());
48
37
49
38
/** 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 ;
59
40
60
- // Schedule the alarm to be handled by the AlarmService.
41
+ /** Schedule the alarm to be handled by the {@link AlarmService}. */
61
42
public static void enqueueAlarmProcessing (Context context , Intent alarmContext ) {
62
43
enqueueWork (context , AlarmService .class , JOB_ID , alarmContext );
63
44
}
64
45
65
46
/**
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}.
75
48
*
76
49
* <p>Preconditions:
77
50
*
78
51
* <ul>
79
52
* <li>The given {@code callbackHandle} must correspond to a registered Dart callback. If the
80
53
* 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
82
55
* PluginRegistrantException} will be thrown.
83
56
* </ul>
84
57
*/
85
58
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..." );
95
61
return ;
96
62
}
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 );
114
65
}
115
66
116
67
/**
117
- * Called once the Dart isolate ({@code sBackgroundFlutterView }) has finished initializing.
68
+ * Called once the Dart isolate ({@code flutterBackgroundExecutor }) has finished initializing.
118
69
*
119
70
* <p>Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code
120
71
* AlarmService.initialized} message. Processes all alarm events that came in while the isolate
121
72
* was starting.
122
73
*/
123
- // TODO(mattcarroll): consider making this method package private
124
- public static void onInitialized () {
74
+ /* package */ static void onInitialized () {
125
75
Log .i (TAG , "AlarmService started!" );
126
- sIsIsolateRunning .set (true );
127
- synchronized (sAlarmQueue ) {
76
+ synchronized (alarmQueue ) {
128
77
// Handle all the alarm events received before the Dart isolate was
129
78
// initialized, then clear the queue.
130
- Iterator <Intent > i = sAlarmQueue .iterator ();
79
+ Iterator <Intent > i = alarmQueue .iterator ();
131
80
while (i .hasNext ()) {
132
- executeDartCallbackInBackgroundIsolate (i .next (), null );
81
+ flutterBackgroundExecutor . executeDartCallbackInBackgroundIsolate (i .next (), null );
133
82
}
134
- sAlarmQueue .clear ();
83
+ alarmQueue .clear ();
135
84
}
136
85
}
137
86
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
-
146
87
/**
147
88
* Sets the Dart callback handle for the Dart method that is responsible for initializing the
148
89
* background Dart isolate, preparing it to receive Dart callback tasks requests.
149
90
*/
150
91
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 );
166
93
}
167
94
168
95
/**
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.
170
98
*
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.
173
103
*/
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 );
216
107
}
217
108
218
109
private static void scheduleAlarm (
@@ -285,6 +176,7 @@ private static void scheduleAlarm(
285
176
}
286
177
}
287
178
179
+ /** Schedules a one-shot alarm to be executed once in the future. */
288
180
public static void setOneShot (Context context , AndroidAlarmManagerPlugin .OneShotRequest request ) {
289
181
final boolean repeating = false ;
290
182
scheduleAlarm (
@@ -301,6 +193,7 @@ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShot
301
193
request .callbackHandle );
302
194
}
303
195
196
+ /** Schedules a periodic alarm to be executed repeatedly in the future. */
304
197
public static void setPeriodic (
305
198
Context context , AndroidAlarmManagerPlugin .PeriodicRequest request ) {
306
199
final boolean repeating = true ;
@@ -320,6 +213,7 @@ public static void setPeriodic(
320
213
request .callbackHandle );
321
214
}
322
215
216
+ /** Cancels an alarm with ID {@code requestCode}. */
323
217
public static void cancel (Context context , int requestCode ) {
324
218
// Clear the alarm if it was set to be rescheduled after reboots.
325
219
clearPersistentAlarm (context , requestCode );
@@ -364,7 +258,7 @@ private static void addPersistentAlarm(
364
258
String key = getPersistentAlarmKey (requestCode );
365
259
SharedPreferences prefs = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
366
260
367
- synchronized (sPersistentAlarmsLock ) {
261
+ synchronized (persistentAlarmsLock ) {
368
262
Set <String > persistentAlarms = prefs .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
369
263
if (persistentAlarms == null ) {
370
264
persistentAlarms = new HashSet <>();
@@ -383,7 +277,7 @@ private static void addPersistentAlarm(
383
277
384
278
private static void clearPersistentAlarm (Context context , int requestCode ) {
385
279
SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
386
- synchronized (sPersistentAlarmsLock ) {
280
+ synchronized (persistentAlarmsLock ) {
387
281
Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
388
282
if ((persistentAlarms == null ) || !persistentAlarms .contains (requestCode )) {
389
283
return ;
@@ -399,7 +293,7 @@ private static void clearPersistentAlarm(Context context, int requestCode) {
399
293
}
400
294
401
295
public static void reschedulePersistentAlarms (Context context ) {
402
- synchronized (sPersistentAlarmsLock ) {
296
+ synchronized (persistentAlarmsLock ) {
403
297
SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
404
298
Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
405
299
// No alarms to reschedule.
@@ -449,15 +343,11 @@ public static void reschedulePersistentAlarms(Context context) {
449
343
@ Override
450
344
public void onCreate () {
451
345
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 ();
460
348
}
349
+ Context context = getApplicationContext ();
350
+ flutterBackgroundExecutor .startBackgroundIsolate (context );
461
351
}
462
352
463
353
/**
@@ -470,17 +360,17 @@ public void onCreate() {
470
360
* intent}, then the desired Dart callback is invoked immediately.
471
361
*
472
362
* <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
474
364
* callbacks have been executed.
475
365
*/
476
366
@ Override
477
367
protected void onHandleWork (final Intent intent ) {
478
368
// If we're in the middle of processing queued alarms, add the incoming
479
369
// intent to the queue and return.
480
- synchronized (sAlarmQueue ) {
481
- if (!sIsIsolateRunning . get ()) {
370
+ synchronized (alarmQueue ) {
371
+ if (!flutterBackgroundExecutor . isRunning ()) {
482
372
Log .i (TAG , "AlarmService has not yet started." );
483
- sAlarmQueue .add (intent );
373
+ alarmQueue .add (intent );
484
374
return ;
485
375
}
486
376
}
@@ -493,7 +383,7 @@ protected void onHandleWork(final Intent intent) {
493
383
new Runnable () {
494
384
@ Override
495
385
public void run () {
496
- executeDartCallbackInBackgroundIsolate (intent , latch );
386
+ flutterBackgroundExecutor . executeDartCallbackInBackgroundIsolate (intent , latch );
497
387
}
498
388
});
499
389
0 commit comments