Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit e351a09

Browse files
authored
Updated android_alarm_manager to work after engine refactor. (#642)
* Updated `android_alarm_manager` to work after engine refactor. Fixes issue #17566: alarm_manager plugin stopped working
1 parent 956ae7f commit e351a09

File tree

4 files changed

+196
-135
lines changed

4 files changed

+196
-135
lines changed

packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java

Lines changed: 88 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,81 @@
44

55
package io.flutter.plugins.androidalarmmanager;
66

7-
import android.app.Activity;
87
import android.app.AlarmManager;
9-
import android.app.Application;
108
import android.app.PendingIntent;
119
import android.app.Service;
1210
import android.content.Context;
1311
import android.content.Intent;
1412
import android.os.IBinder;
1513
import android.util.Log;
16-
import io.flutter.app.FlutterActivity;
17-
import io.flutter.app.FlutterApplication;
14+
import io.flutter.plugin.common.MethodChannel;
1815
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
16+
import io.flutter.view.FlutterCallbackInformation;
1917
import io.flutter.view.FlutterMain;
2018
import io.flutter.view.FlutterNativeView;
19+
import io.flutter.view.FlutterRunArguments;
20+
import java.util.concurrent.atomic.AtomicBoolean;
2121

2222
public class AlarmService extends Service {
2323
public static final String TAG = "AlarmService";
24-
private static FlutterNativeView sSharedFlutterView;
24+
private static AtomicBoolean sStarted = new AtomicBoolean(false);
25+
private static FlutterNativeView sBackgroundFlutterView;
26+
private static MethodChannel sBackgroundChannel;
2527
private static PluginRegistrantCallback sPluginRegistrantCallback;
2628

27-
private FlutterNativeView mFlutterView;
28-
private String appBundlePath;
29+
private String mAppBundlePath;
30+
31+
public static void onInitialized() {
32+
sStarted.set(true);
33+
}
34+
35+
// Here we start the AlarmService. This method does a few things:
36+
// - Retrieves the callback information for the handle associated with the
37+
// callback dispatcher in the Dart portion of the plugin.
38+
// - Builds the arguments object for running in a new FlutterNativeView.
39+
// - Enters the isolate owned by the FlutterNativeView at the callback
40+
// represented by `callbackHandle` and initializes the callback
41+
// dispatcher.
42+
// - Registers the FlutterNativeView's PluginRegistry to receive
43+
// MethodChannel messages.
44+
public static void startAlarmService(Context context, long callbackHandle) {
45+
FlutterMain.ensureInitializationComplete(context, null);
46+
String mAppBundlePath = FlutterMain.findAppBundlePath(context);
47+
FlutterCallbackInformation cb =
48+
FlutterCallbackInformation.lookupCallbackInformation(callbackHandle);
49+
if (cb == null) {
50+
Log.e(TAG, "Fatal: failed to find callback");
51+
return;
52+
}
53+
54+
// Note that we're passing `true` as the second argument to our
55+
// FlutterNativeView constructor. This specifies the FlutterNativeView
56+
// as a background view and does not create a drawing surface.
57+
sBackgroundFlutterView = new FlutterNativeView(context, true);
58+
if (mAppBundlePath != null && !sStarted.get()) {
59+
Log.i(TAG, "Starting AlarmService...");
60+
FlutterRunArguments args = new FlutterRunArguments();
61+
args.bundlePath = mAppBundlePath;
62+
args.entrypoint = cb.callbackName;
63+
args.libraryPath = cb.callbackLibraryPath;
64+
sBackgroundFlutterView.runFromBundle(args);
65+
sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry());
66+
}
67+
}
68+
69+
public static void setBackgroundChannel(MethodChannel channel) {
70+
sBackgroundChannel = channel;
71+
}
2972

3073
public static void setOneShot(
3174
Context context,
3275
int requestCode,
3376
boolean exact,
3477
boolean wakeup,
3578
long startMillis,
36-
String entrypoint) {
79+
long callbackHandle) {
3780
final boolean repeating = false;
38-
scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, 0, entrypoint);
81+
scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, 0, callbackHandle);
3982
}
4083

4184
public static void setPeriodic(
@@ -45,10 +88,17 @@ public static void setPeriodic(
4588
boolean wakeup,
4689
long startMillis,
4790
long intervalMillis,
48-
String entrypoint) {
91+
long callbackHandle) {
4992
final boolean repeating = true;
5093
scheduleAlarm(
51-
context, requestCode, repeating, exact, wakeup, startMillis, intervalMillis, entrypoint);
94+
context,
95+
requestCode,
96+
repeating,
97+
exact,
98+
wakeup,
99+
startMillis,
100+
intervalMillis,
101+
callbackHandle);
52102
}
53103

54104
public static void cancel(Context context, int requestCode) {
@@ -64,109 +114,53 @@ public static void cancel(Context context, int requestCode) {
64114
}
65115

66116
public static FlutterNativeView getSharedFlutterView() {
67-
return sSharedFlutterView;
117+
return sBackgroundFlutterView;
68118
}
69119

70-
public static boolean setSharedFlutterView(FlutterNativeView view) {
71-
if (sSharedFlutterView != null && sSharedFlutterView != view) {
72-
Log.i(TAG, "setSharedFlutterView tried to overwrite an existing FlutterNativeView");
120+
public static boolean setBackgroundFlutterView(FlutterNativeView view) {
121+
if (sBackgroundFlutterView != null && sBackgroundFlutterView != view) {
122+
Log.i(TAG, "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView");
73123
return false;
74124
}
75-
Log.i(TAG, "setSharedFlutterView set");
76-
sSharedFlutterView = view;
125+
sBackgroundFlutterView = view;
77126
return true;
78127
}
79128

80129
public static void setPluginRegistrant(PluginRegistrantCallback callback) {
81130
sPluginRegistrantCallback = callback;
82131
}
83132

84-
private void ensureFlutterView() {
85-
if (mFlutterView != null) {
86-
return;
87-
}
88-
89-
if (sSharedFlutterView != null) {
90-
mFlutterView = sSharedFlutterView;
91-
return;
92-
}
93-
94-
// mFlutterView and sSharedFlutterView are both null. That likely means that
95-
// no FlutterView has ever been created in this process before. So, we'll
96-
// make one, and assign it to both mFlutterView and sSharedFlutterView.
97-
mFlutterView = new FlutterNativeView(getApplicationContext());
98-
sSharedFlutterView = mFlutterView;
99-
100-
// If there was no FlutterNativeView before now, then we also must
101-
// initialize the PluginRegistry.
102-
sPluginRegistrantCallback.registerWith(mFlutterView.getPluginRegistry());
103-
return;
104-
}
105-
106-
// This returns the FlutterView for the main FlutterActivity if there is one.
107-
private static FlutterNativeView viewFromAppContext(Context context) {
108-
Application app = (Application) context;
109-
if (!(app instanceof FlutterApplication)) {
110-
Log.i(TAG, "viewFromAppContext app not a FlutterApplication");
111-
return null;
112-
}
113-
FlutterApplication flutterApp = (FlutterApplication) app;
114-
Activity activity = flutterApp.getCurrentActivity();
115-
if (activity == null) {
116-
Log.i(TAG, "viewFromAppContext activity is null");
117-
return null;
118-
}
119-
if (!(activity instanceof FlutterActivity)) {
120-
Log.i(TAG, "viewFromAppContext activity is not a FlutterActivity");
121-
return null;
122-
}
123-
FlutterActivity flutterActivity = (FlutterActivity) activity;
124-
return flutterActivity.getFlutterView().getFlutterNativeView();
125-
}
126-
127133
@Override
128134
public void onCreate() {
129135
super.onCreate();
130136
Context context = getApplicationContext();
131-
mFlutterView = viewFromAppContext(context);
132137
FlutterMain.ensureInitializationComplete(context, null);
133-
if (appBundlePath == null) {
134-
appBundlePath = FlutterMain.findAppBundlePath(context);
135-
}
136-
}
137-
138-
@Override
139-
public void onDestroy() {
140-
// Try to find the native view of the main activity if there is one.
141-
Context context = getApplicationContext();
142-
FlutterNativeView nativeView = viewFromAppContext(context);
143-
144-
// Don't destroy mFlutterView if it is the same as the native view for the
145-
// main activity, or the same as the shared native view.
146-
if (mFlutterView != nativeView && mFlutterView != sSharedFlutterView) {
147-
mFlutterView.destroy();
148-
}
149-
mFlutterView = null;
150-
151-
// Don't destroy the shared native view if it is the same native view as
152-
// for the main activity.
153-
if (sSharedFlutterView != nativeView) {
154-
sSharedFlutterView.destroy();
155-
}
156-
sSharedFlutterView = null;
138+
mAppBundlePath = FlutterMain.findAppBundlePath(context);
157139
}
158140

141+
// This is where we handle alarm events before sending them to our callback
142+
// dispatcher in Dart.
159143
@Override
160144
public int onStartCommand(Intent intent, int flags, int startId) {
161-
ensureFlutterView();
162-
String entrypoint = intent.getStringExtra("entrypoint");
163-
if (entrypoint == null) {
164-
Log.i(TAG, "onStartCommand got a null entrypoint. Bailing out");
145+
if (!sStarted.get()) {
146+
Log.i(TAG, "AlarmService has not yet started.");
147+
// TODO(bkonyi): queue up alarm events.
165148
return START_NOT_STICKY;
166149
}
167-
if (appBundlePath != null) {
168-
mFlutterView.runFromBundle(appBundlePath, null, entrypoint, true);
150+
// Grab the handle for the callback associated with this alarm. Pay close
151+
// attention to the type of the callback handle as storing this value in a
152+
// variable of the wrong size will cause the callback lookup to fail.
153+
long callbackHandle = intent.getLongExtra("callbackHandle", 0);
154+
if (sBackgroundChannel == null) {
155+
Log.e(
156+
TAG,
157+
"setBackgroundChannel was not called before alarms were scheduled." + " Bailing out.");
158+
return START_NOT_STICKY;
169159
}
160+
// Handle the alarm event in Dart. Note that for this plugin, we don't
161+
// care about the method name as we simply lookup and invoke the callback
162+
// provided.
163+
sBackgroundChannel.invokeMethod("", new Object[] {callbackHandle});
170164
return START_NOT_STICKY;
171165
}
172166

@@ -183,10 +177,10 @@ private static void scheduleAlarm(
183177
boolean wakeup,
184178
long startMillis,
185179
long intervalMillis,
186-
String entrypoint) {
187-
// Create an Intent for the alarm and set the desired Dart entrypoint.
180+
long callbackHandle) {
181+
// Create an Intent for the alarm and set the desired Dart callback handle.
188182
Intent alarm = new Intent(context, AlarmService.class);
189-
alarm.putExtra("entrypoint", entrypoint);
183+
alarm.putExtra("callbackHandle", callbackHandle);
190184
PendingIntent pendingIntent =
191185
PendingIntent.getService(context, requestCode, alarm, PendingIntent.FLAG_UPDATE_CURRENT);
192186

packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ public static void registerWith(Registrar registrar) {
2525
registrar.messenger(),
2626
"plugins.flutter.io/android_alarm_manager",
2727
JSONMethodCodec.INSTANCE);
28+
final MethodChannel backgroundChannel =
29+
new MethodChannel(
30+
registrar.messenger(),
31+
"plugins.flutter.io/android_alarm_manager_background",
32+
JSONMethodCodec.INSTANCE);
2833
AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(registrar.context());
2934
channel.setMethodCallHandler(plugin);
35+
backgroundChannel.setMethodCallHandler(plugin);
3036
registrar.addViewDestroyListener(plugin);
37+
AlarmService.setBackgroundChannel(backgroundChannel);
3138
}
3239

3340
private Context mContext;
@@ -41,7 +48,12 @@ public void onMethodCall(MethodCall call, Result result) {
4148
String method = call.method;
4249
Object arguments = call.arguments;
4350
try {
44-
if (method.equals("Alarm.periodic")) {
51+
if (method.equals("AlarmService.start")) {
52+
startService((JSONArray) arguments);
53+
result.success(true);
54+
} else if (method.equals("AlarmService.initialized")) {
55+
AlarmService.onInitialized();
56+
} else if (method.equals("Alarm.periodic")) {
4557
periodic((JSONArray) arguments);
4658
result.success(true);
4759
} else if (method.equals("Alarm.oneShot")) {
@@ -58,13 +70,18 @@ public void onMethodCall(MethodCall call, Result result) {
5870
}
5971
}
6072

73+
private void startService(JSONArray arguments) throws JSONException {
74+
long callbackHandle = arguments.getLong(0);
75+
AlarmService.startAlarmService(mContext, callbackHandle);
76+
}
77+
6178
private void oneShot(JSONArray arguments) throws JSONException {
6279
int requestCode = arguments.getInt(0);
6380
boolean exact = arguments.getBoolean(1);
6481
boolean wakeup = arguments.getBoolean(2);
6582
long startMillis = arguments.getLong(3);
66-
String entrypoint = arguments.getString(4);
67-
AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, entrypoint);
83+
long callbackHandle = arguments.getLong(4);
84+
AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, callbackHandle);
6885
}
6986

7087
private void periodic(JSONArray arguments) throws JSONException {
@@ -73,9 +90,9 @@ private void periodic(JSONArray arguments) throws JSONException {
7390
boolean wakeup = arguments.getBoolean(2);
7491
long startMillis = arguments.getLong(3);
7592
long intervalMillis = arguments.getLong(4);
76-
String entrypoint = arguments.getString(5);
93+
long callbackHandle = arguments.getLong(5);
7794
AlarmService.setPeriodic(
78-
mContext, requestCode, exact, wakeup, startMillis, intervalMillis, entrypoint);
95+
mContext, requestCode, exact, wakeup, startMillis, intervalMillis, callbackHandle);
7996
}
8097

8198
private void cancel(JSONArray arguments) throws JSONException {
@@ -85,6 +102,6 @@ private void cancel(JSONArray arguments) throws JSONException {
85102

86103
@Override
87104
public boolean onViewDestroy(FlutterNativeView nativeView) {
88-
return AlarmService.setSharedFlutterView(nativeView);
105+
return AlarmService.setBackgroundFlutterView(nativeView);
89106
}
90107
}

packages/android_alarm_manager/example/lib/main.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,20 @@ Future<Null> main() async {
7171
final int helloAlarmID = 0;
7272
final int goodbyeAlarmID = 1;
7373
final int oneShotID = 2;
74+
75+
// Start the AlarmManager service.
76+
await AndroidAlarmManager.initialize();
77+
7478
printHelloMessage("Hello, main()!");
7579
runApp(const Center(
7680
child: Text('Hello, world!', textDirection: TextDirection.ltr)));
7781
await AndroidAlarmManager.periodic(
78-
const Duration(minutes: 1), helloAlarmID, printHello);
79-
await AndroidAlarmManager.periodic(
80-
const Duration(minutes: 1), goodbyeAlarmID, printGoodbye);
82+
const Duration(seconds: 5), helloAlarmID, printHello,
83+
wakeup: true);
84+
await AndroidAlarmManager.oneShot(
85+
const Duration(seconds: 5), goodbyeAlarmID, printGoodbye);
8186
if (!oneShotFired) {
8287
await AndroidAlarmManager.oneShot(
83-
const Duration(minutes: 1), oneShotID, printOneShot);
88+
const Duration(seconds: 5), oneShotID, printOneShot);
8489
}
8590
}

0 commit comments

Comments
 (0)