From 13f73a86e9c1cc0c5fc46a757992e24219d03715 Mon Sep 17 00:00:00 2001 From: Eka Setiawan Saputra Date: Wed, 14 Jun 2023 11:25:28 +0700 Subject: [PATCH] refactor: improve android performances --- .../example/android/app/build.gradle | 2 +- .../example/android/build.gradle | 2 +- .../example/lib/main.dart | 14 +- .../example/pubspec.yaml | 10 +- .../IBackgroundService.aidl | 9 -- .../IBackgroundServiceBinder.aidl | 11 -- .../BackgroundService.java | 76 +++------- .../FlutterBackgroundServicePlugin.java | 133 ++++++++---------- .../flutter_background_service/Pipe.java | 40 ++++++ .../flutter_background_service_android.dart | 33 +++-- 10 files changed, 156 insertions(+), 174 deletions(-) delete mode 100644 packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundService.aidl delete mode 100644 packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundServiceBinder.aidl create mode 100644 packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/Pipe.java diff --git a/packages/flutter_background_service/example/android/app/build.gradle b/packages/flutter_background_service/example/android/app/build.gradle index 775f625f..5ae9db09 100644 --- a/packages/flutter_background_service/example/android/app/build.gradle +++ b/packages/flutter_background_service/example/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "id.flutter.example" - minSdkVersion flutter.minSdkVersion + minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/packages/flutter_background_service/example/android/build.gradle b/packages/flutter_background_service/example/android/build.gradle index 83ae2200..3cdaac95 100644 --- a/packages/flutter_background_service/example/android/build.gradle +++ b/packages/flutter_background_service/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/flutter_background_service/example/lib/main.dart b/packages/flutter_background_service/example/lib/main.dart index 303560e1..02c087bb 100644 --- a/packages/flutter_background_service/example/lib/main.dart +++ b/packages/flutter_background_service/example/lib/main.dart @@ -30,14 +30,16 @@ Future initializeService() async { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - if (Platform.isIOS) { + if (Platform.isIOS || Platform.isAndroid) { await flutterLocalNotificationsPlugin.initialize( const InitializationSettings( - iOS: IOSInitializationSettings(), + iOS: DarwinInitializationSettings(), + android: AndroidInitializationSettings('ic_bg_service_small'), ), ); } + await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() @@ -139,10 +141,10 @@ void onStart(ServiceInstance service) async { ); // if you don't using custom notification, uncomment this - // service.setForegroundNotificationInfo( - // title: "My App Service", - // content: "Updated at ${DateTime.now()}", - // ); + service.setForegroundNotificationInfo( + title: "My App Service", + content: "Updated at ${DateTime.now()}", + ); } } diff --git a/packages/flutter_background_service/example/pubspec.yaml b/packages/flutter_background_service/example/pubspec.yaml index b330ede3..7a7ec414 100644 --- a/packages/flutter_background_service/example/pubspec.yaml +++ b/packages/flutter_background_service/example/pubspec.yaml @@ -42,12 +42,12 @@ dependencies: flutter_background_service_android: ^4.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - device_info_plus: ^3.2.2 - shared_preferences: ^2.0.15 + cupertino_icons: ^1.0.5 + device_info_plus: ^9.0.2 + shared_preferences: ^2.1.2 # optional if you wish to use a custom foreground service notification - flutter_local_notifications: ^9.9.1 + flutter_local_notifications: ^14.1.1 dev_dependencies: flutter_test: @@ -58,7 +58,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^1.0.0 + flutter_lints: ^2.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundService.aidl b/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundService.aidl deleted file mode 100644 index 60f1e99c..00000000 --- a/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundService.aidl +++ /dev/null @@ -1,9 +0,0 @@ -// IBackgroundService.aidl -package id.flutter.flutter_background_service; - -// Declare any non-default types here with import statements - -interface IBackgroundService { - void invoke(String data); - void stop(); -} \ No newline at end of file diff --git a/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundServiceBinder.aidl b/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundServiceBinder.aidl deleted file mode 100644 index 80750088..00000000 --- a/packages/flutter_background_service_android/android/src/main/aidl/id/flutter/flutter_background_service/IBackgroundServiceBinder.aidl +++ /dev/null @@ -1,11 +0,0 @@ -// IBackgroundServiceBinder.aidl -package id.flutter.flutter_background_service; - -import id.flutter.flutter_background_service.IBackgroundService; -// Declare any non-default types here with import statements - -interface IBackgroundServiceBinder { - void bind(int id, IBackgroundService service); - void unbind(int id); - void invoke(String data); -} \ No newline at end of file diff --git a/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BackgroundService.java b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BackgroundService.java index 1d816c87..3a8f8ecc 100644 --- a/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BackgroundService.java +++ b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/BackgroundService.java @@ -8,6 +8,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -38,13 +39,13 @@ import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; public class BackgroundService extends Service implements MethodChannel.MethodCallHandler { private static final String TAG = "BackgroundService"; private static final String LOCK_NAME = BackgroundService.class.getName() + ".Lock"; public static volatile WakeLock lockStatic = null; // notice static - final Map listeners = new HashMap<>(); AtomicBoolean isRunning = new AtomicBoolean(false); private FlutterEngine backgroundEngine; private MethodChannel methodChannel; @@ -56,32 +57,6 @@ public class BackgroundService extends Service implements MethodChannel.MethodCa private String notificationChannelId; private int notificationId; private Handler mainHandler; - private final IBackgroundServiceBinder.Stub binder = new IBackgroundServiceBinder.Stub() { - - @Override - public void bind(int id, IBackgroundService service) { - synchronized (listeners) { - listeners.put(id, service); - } - } - - @Override - public void unbind(int id) { - synchronized (listeners) { - listeners.remove(id); - } - } - - @Override - public void invoke(String data) { - try { - JSONObject call = new JSONObject(data); - receiveData(call); - } catch (Exception e) { - e.printStackTrace(); - } - } - }; synchronized public static PowerManager.WakeLock getLock(Context context) { if (lockStatic == null) { @@ -97,18 +72,11 @@ synchronized public static PowerManager.WakeLock getLock(Context context) { @Override public IBinder onBind(Intent intent) { - return binder; + return null; } @Override public boolean onUnbind(Intent intent) { - final int binderId = intent.getIntExtra("binder_id", 0); - if (binderId != 0) { - synchronized (listeners) { - listeners.remove(binderId); - } - } - return super.onUnbind(intent); } @@ -116,6 +84,8 @@ public boolean onUnbind(Intent intent) { public void onCreate() { super.onCreate(); + FlutterBackgroundServicePlugin.servicePipe.addListener(listener); + config = new Config(this); mainHandler = new Handler(Looper.getMainLooper()); @@ -151,9 +121,17 @@ public void onDestroy() { methodChannel = null; dartEntrypoint = null; + FlutterBackgroundServicePlugin.servicePipe.removeListener(listener); super.onDestroy(); } + private final Pipe.PipeListener listener = new Pipe.PipeListener() { + @Override + public void onReceived(JSONObject object) { + receiveData(object); + } + }; + private void createNotificationChannel() { if (SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "Background Service"; @@ -223,6 +201,10 @@ private void runService() { isRunning.set(true); backgroundEngine = new FlutterEngine(this); + + // remove FlutterBackgroundServicePlugin (because its only for UI) + backgroundEngine.getPlugins().remove(FlutterBackgroundServicePlugin.class); + backgroundEngine.getServiceControlSurface().attachToService(BackgroundService.this, null, config.isForeground()); methodChannel = new MethodChannel(backgroundEngine.getDartExecutor().getBinaryMessenger(), "id.flutter/background_service_android_bg", JSONMethodCodec.INSTANCE); @@ -234,6 +216,7 @@ private void runService() { long backgroundHandle = config.getBackgroundHandle(); args.add(String.valueOf(backgroundHandle)); + backgroundEngine.getDartExecutor().executeDartEntrypoint(dartEntrypoint, args); } catch (UnsatisfiedLinkError e) { @@ -316,20 +299,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result if (method.equalsIgnoreCase("stopService")) { isManuallyStopped = true; WatchdogReceiver.remove(this); - - try { - synchronized (listeners) { - for (Integer key : listeners.keySet()) { - IBackgroundService listener = listeners.get(key); - if (listener != null) { - listener.stop(); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - stopSelf(); result.success(true); return; @@ -337,13 +306,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result if (method.equalsIgnoreCase("sendData")) { try { - synchronized (listeners) { - for (Integer key : listeners.keySet()) { - IBackgroundService listener = listeners.get(key); - if (listener != null) { - listener.invoke(call.arguments.toString()); - } - } + if (FlutterBackgroundServicePlugin.mainPipe.hasListener()){ + FlutterBackgroundServicePlugin.mainPipe.invoke((JSONObject) call.arguments); } result.success(true); diff --git a/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/FlutterBackgroundServicePlugin.java b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/FlutterBackgroundServicePlugin.java index 10de9d91..19b67702 100644 --- a/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/FlutterBackgroundServicePlugin.java +++ b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/FlutterBackgroundServicePlugin.java @@ -14,9 +14,14 @@ import org.json.JSONObject; +import java.util.HashMap; +import java.util.Map; + +import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.service.ServiceAware; import io.flutter.embedding.engine.plugins.service.ServicePluginBinding; +import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -27,93 +32,57 @@ /** * FlutterBackgroundServicePlugin */ -public class FlutterBackgroundServicePlugin implements FlutterPlugin, MethodCallHandler, ServiceAware { +public class FlutterBackgroundServicePlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler { private static final String TAG = "BackgroundServicePlugin"; - private final int binderId = (int) (System.currentTimeMillis() / 1000); private Handler mainHandler; private Config config; private MethodChannel channel; - private Context context; - private IBackgroundServiceBinder serviceBinder; - private boolean mShouldUnbind = false; - - @SuppressWarnings("deprecation") - public static void registerWith(Registrar registrar) { - final FlutterBackgroundServicePlugin plugin = new FlutterBackgroundServicePlugin(); - final MethodChannel channel = new MethodChannel(registrar.messenger(), "id.flutter/background_service_android", JSONMethodCodec.INSTANCE); - channel.setMethodCallHandler(plugin); - plugin.channel = channel; - } + private EventChannel eventChannel; + private final Map eventSinks = new HashMap<>(); - private final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - serviceBinder = IBackgroundServiceBinder.Stub.asInterface(service); + private Context context; - try { - IBackgroundService listener = new IBackgroundService.Stub() { - @Override - public void invoke(String data) { - try { - JSONObject call = new JSONObject(data); - receiveData(call); - } catch (Exception e) { - e.printStackTrace(); - } - } + public static final Pipe servicePipe = new Pipe(); + public static final Pipe mainPipe = new Pipe(); - @Override - public void stop() { - if (context != null && serviceBinder != null) { - mShouldUnbind = false; - context.unbindService(serviceConnection); - } - } - }; - - serviceBinder.bind(binderId, listener); - } catch (Exception e) { - e.printStackTrace(); - } - } + public static void registerWith(FlutterEngine engine){ + Log.d(TAG, "registering with FlutterEngine"); + } + private final Pipe.PipeListener listener = new Pipe.PipeListener() { @Override - public void onServiceDisconnected(ComponentName name) { - try { - mShouldUnbind = false; - serviceBinder.unbind(binderId); - serviceBinder = null; - } catch (Exception e) { - e.printStackTrace(); - } + public void onReceived(JSONObject object) { + receiveData(object); } }; + @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { this.context = flutterPluginBinding.getApplicationContext(); this.config = new Config(this.context); - mShouldUnbind = false; mainHandler = new Handler(context.getMainLooper()); - channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "id.flutter/background_service_android", JSONMethodCodec.INSTANCE); + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "id.flutter/background_service/android/method", JSONMethodCodec.INSTANCE); channel.setMethodCallHandler(this); + + eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "id.flutter/background_service/android/event", JSONMethodCodec.INSTANCE); + eventChannel.setStreamHandler(this); + + mainPipe.addListener(listener); } private void start() { WatchdogReceiver.enqueue(context); boolean isForeground = config.isForeground(); Intent intent = new Intent(context, BackgroundService.class); - intent.putExtra("binder_id", binderId); if (isForeground) { ContextCompat.startForegroundService(context, intent); } else { context.startService(intent); } - - mShouldUnbind = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override @@ -155,13 +124,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { } if (method.equalsIgnoreCase("sendData")) { - if (serviceBinder != null) { - serviceBinder.invoke(call.arguments.toString()); - result.success(true); - return; - } + synchronized (servicePipe){ + if (servicePipe.hasListener()){ + servicePipe.invoke((JSONObject) call.arguments); + result.success(true); + return; + } - result.success(false); + result.success(false); + } return; } @@ -188,36 +159,44 @@ private boolean isServiceRunning() { @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + mainPipe.removeListener(listener); + channel.setMethodCallHandler(null); channel = null; - if (mShouldUnbind && serviceBinder != null) { - binding.getApplicationContext().unbindService(serviceConnection); - mShouldUnbind = false; + synchronized (eventSinks){ + eventSinks.clear(); } + eventChannel.setStreamHandler(null); + eventChannel = null; } private void receiveData(JSONObject data) { final JSONObject arg = data; - mainHandler.post(new Runnable() { - @Override - public void run() { - if (channel != null) { - channel.invokeMethod("onReceiveData", arg); - } + synchronized (this){ + for (EventChannel.EventSink sink : + eventSinks.values()) { + mainHandler.post(new Runnable() { + @Override + public void run() { + sink.success(arg); + } + }); } - }); + } } @Override - public void onAttachedToService(@NonNull ServicePluginBinding binding) { - Log.d(TAG, "onAttachedToService"); + public void onListen(Object arguments, EventChannel.EventSink events) { + synchronized (this){ + eventSinks.put(arguments, events); + } } @Override - public void onDetachedFromService() { - Log.d(TAG, "onDetachedFromService"); + public void onCancel(Object arguments) { + synchronized (this){ + eventSinks.remove(arguments); + } } - - } diff --git a/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/Pipe.java b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/Pipe.java new file mode 100644 index 00000000..551526e3 --- /dev/null +++ b/packages/flutter_background_service_android/android/src/main/java/id/flutter/flutter_background_service/Pipe.java @@ -0,0 +1,40 @@ +package id.flutter.flutter_background_service; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class Pipe { + public interface PipeListener { + public void onReceived(JSONObject object); + } + + private final List listeners = new ArrayList<>(); + public boolean hasListener() { + return !listeners.isEmpty(); + } + + public void addListener(PipeListener listener){ + synchronized (this){ + this.listeners.add(listener); + } + } + + public void removeListener(PipeListener listener){ + synchronized (this){ + this.listeners.remove(listener); + } + } + + public void invoke(JSONObject object){ + synchronized (this) { + if (!listeners.isEmpty()) { + for (PipeListener listener : + this.listeners) { + listener.onReceived(object); + } + } + } + } +} diff --git a/packages/flutter_background_service_android/lib/flutter_background_service_android.dart b/packages/flutter_background_service_android/lib/flutter_background_service_android.dart index 69541511..7c8e8658 100644 --- a/packages/flutter_background_service_android/lib/flutter_background_service_android.dart +++ b/packages/flutter_background_service_android/lib/flutter_background_service_android.dart @@ -26,20 +26,20 @@ class FlutterBackgroundServiceAndroid extends FlutterBackgroundServicePlatform { FlutterBackgroundServiceAndroid(); } - static const MethodChannel _channel = const MethodChannel( - 'id.flutter/background_service_android', - JSONMethodCodec(), - ); + FlutterBackgroundServiceAndroid._(); + static final FlutterBackgroundServiceAndroid _instance = + FlutterBackgroundServiceAndroid._(); + factory FlutterBackgroundServiceAndroid() => _instance; + + Future _handleMethodCall(MethodCall call) async { + debugPrint(call.method); - Future _handle(MethodCall call) async { switch (call.method) { case "onReceiveData": _controller.sink.add(call.arguments); break; default: } - - return true; } Future start() async { @@ -47,11 +47,28 @@ class FlutterBackgroundServiceAndroid extends FlutterBackgroundServicePlatform { return result ?? false; } + final MethodChannel _channel = MethodChannel( + 'id.flutter/background_service/android/method', + JSONMethodCodec(), + ); + + final EventChannel _eventChannel = EventChannel( + 'id.flutter/background_service/android/event', + JSONMethodCodec(), + ); + + StreamSubscription? _eventChannelListener; Future configure({ required IosConfiguration iosConfiguration, required AndroidConfiguration androidConfiguration, }) async { - _channel.setMethodCallHandler(_handle); + _channel.setMethodCallHandler(_handleMethodCall); + + _eventChannelListener?.cancel(); + _eventChannelListener = + _eventChannel.receiveBroadcastStream().listen((event) { + _controller.sink.add(event); + }); final CallbackHandle? handle = PluginUtilities.getCallbackHandle(androidConfiguration.onStart);