@@ -35,41 +35,85 @@ import kotlin.concurrent.schedule
3535import kotlin.coroutines.resume
3636import kotlin.coroutines.suspendCoroutine
3737import kotlinx.coroutines.guava.future
38+ import java.util.TimerTask
39+ import java.util.concurrent.atomic.AtomicInteger
3840
3941class DartWorker (context : Context , workerParams : WorkerParameters ): ListenableWorker(context, workerParams) {
4042
4143 companion object {
4244 var workerEngine: FlutterEngine ? = null
4345 var engineReady = Mutex ()
44- }
4546
46- override fun startWork (): ListenableFuture <Result > {
47- val method = inputData.getString(" method" )!!
48- var data = inputData.getString(" data" )!!
49- val gson = GsonBuilder ()
50- .setObjectToNumberStrategy(ToNumberPolicy .LONG_OR_DOUBLE )
51- .create()
52- if (method == " SMSMsg" ) {
53- val json: HashMap <String , Any > = gson.fromJson(data, TypeToken .getParameterized(HashMap ::class .java, String ::class .java, Any ::class .java).type)
54- val pointer: Int = (json[" id" ] as Long ).toInt()
55- if (MethodCallHandler .queuedMessages.contains(pointer)) {
56- data = MethodCallHandler .queuedMessages.remove(pointer)!!
57- } else {
58- // bail
59- return Futures .immediateFuture(Result .success())
47+
48+
49+ // / Code idea taken from https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
50+ private suspend fun initNewEngine (applicationContext : Context ) {
51+ Log .d(Constants .logTag, " Ensuring Flutter is initialized before creating engine" )
52+ // We use the deprecated class here anyways, the new one doesn't work correctly using the same code
53+ FlutterMain .startInitialization(applicationContext)
54+ FlutterMain .ensureInitializationComplete(applicationContext, null )
55+
56+ Log .d(Constants .logTag, " Loading callback info" )
57+ val info = ApplicationInfoLoader .load(applicationContext)
58+ workerEngine = FlutterEngine (applicationContext)
59+
60+ currentJobs.set(0 )
61+
62+ workerEngine!! .addEngineLifecycleListener ( object : FlutterEngine .EngineLifecycleListener {
63+ override fun onPreEngineRestart () {
64+ Log .d(Constants .logTag, " Engine is restarting" )
65+ }
66+
67+ override fun onEngineWillDestroy () {
68+ Log .d(Constants .logTag, " Engine is being destroyed" )
69+ }
70+ })
71+ suspendCoroutine { cont ->
72+ // set up the method channel to receive events from Dart
73+ MethodChannel (workerEngine!! .dartExecutor.binaryMessenger, Constants .methodChannel).setMethodCallHandler {
74+ call, result -> run {
75+ if (call.method == " ready" ) {
76+ Log .d(Constants .logTag, " Dart engine is ready!" )
77+ cont.resume(Unit )
78+ } else {
79+ MethodCallHandler ().methodCallHandler(call, result, applicationContext)
80+ }
81+ }
82+ }
83+ val callbackInfo = FlutterCallbackInformation .lookupCallbackInformation(applicationContext.getSharedPreferences(" FlutterSharedPreferences" , 0 ).getLong(" flutter.backgroundCallbackHandle" , - 1 ))
84+ val callback = DartExecutor .DartCallback (applicationContext.assets, info.flutterAssetsDir, callbackInfo)
85+
86+ Log .d(Constants .logTag, " Executing Dart callback" )
87+ workerEngine!! .dartExecutor.executeDartCallback(callback)
6088 }
6189 }
6290
63- if (engine != null ) {
64- Log .d(Constants .logTag, " Using MainActivity engine to send to Dart" )
65- } else {
66- Log .d(Constants .logTag, " Using DartWorker engine to send to Dart" )
91+ var currentCancelTask: TimerTask ? = null
92+ private fun closeEngineIfNeeded (applicationContext : Context ) {
93+ currentJobs.getAndDecrement()
94+ // Delay 30 seconds so Dart has a chance to complete everything and in case new work comes in shortly after
95+ currentCancelTask?.cancel()
96+ currentCancelTask = Timer ().schedule(30000 ) {
97+ currentCancelTask = null
98+ Log .d(Constants .logTag, " $currentJobs worker(s) still queued" )
99+ if (currentJobs.get() == 0 && workerEngine != null ) {
100+ Log .d(Constants .logTag, " Closing ${Constants .dartWorkerTag} engine" )
101+ // This must be run on main thread
102+ CoroutineScope (Dispatchers .Main ).launch {
103+ workerEngine?.destroy()
104+ workerEngine = null
105+ }
106+ }
107+ }
67108 }
68- return CoroutineScope (Dispatchers .Main ).future {
109+
110+ var currentJobs = AtomicInteger (0 )
111+
112+ suspend fun callMethod (applicationContext : Context , method : String , arguments : Map <String , Any >) {
69113 engineReady.withLock {
70114 if (engine == null && workerEngine == null ) {
71115 Log .d(Constants .logTag, " Initializing engine for worker with method $method " )
72- initNewEngine()
116+ initNewEngine(applicationContext )
73117 }
74118 }
75119 Log .d(Constants .logTag, " Sending event, '$method ' to Dart" )
@@ -78,95 +122,73 @@ class DartWorker(context: Context, workerParams: WorkerParameters): ListenableWo
78122 var engineToUse: FlutterEngine ? = engine ? : workerEngine
79123 if (engineToUse == null ) {
80124 Log .d(Constants .logTag, " Engine is null, cannot send method $method to Dart" )
81- return @future Result .failure( )
125+ throw Exception ( " No engine " )
82126 }
83127
84128 Log .d(Constants .logTag, " Registering engine lifecycle listener" )
85129
86- engineToUse!! .addEngineLifecycleListener ( object : FlutterEngine .EngineLifecycleListener {
87- override fun onPreEngineRestart () {
88- Log .d(Constants .logTag, " Engine is restarting" )
89- }
90-
91- override fun onEngineWillDestroy () {
92- Log .d(Constants .logTag, " Engine is being destroyed" )
93- }
94- })
95-
96130 Log .d(Constants .logTag, " Invoking method channel..." )
97131 suspendCoroutine { cont ->
98- MethodChannel (engineToUse!! .dartExecutor.binaryMessenger, Constants .methodChannel).invokeMethod(method, gson.fromJson(data, TypeToken .getParameterized(HashMap ::class .java, String ::class .java, Any ::class .java).type), object : MethodChannel .Result {
132+ currentJobs.getAndIncrement()
133+ MethodChannel (engineToUse!! .dartExecutor.binaryMessenger, Constants .methodChannel).invokeMethod(method, arguments, object : MethodChannel .Result {
99134 override fun success (result : Any? ) {
100135 Log .d(Constants .logTag, " Worker with method $method completed successfully" )
101136 cont.resume(Result .success())
102- closeEngineIfNeeded()
137+ closeEngineIfNeeded(applicationContext )
103138 }
104-
139+
105140 override fun error (errorCode : String , errorMessage : String? , errorDetails : Any? ) {
106141 Log .e(Constants .logTag, " Worker with method $method failed!" )
107142 cont.resume(Result .failure())
108- closeEngineIfNeeded()
143+ closeEngineIfNeeded(applicationContext )
109144 }
110-
145+
111146 override fun notImplemented () {
112147 Log .e(Constants .logTag, " Worker with method $method not implemented on Dart side" )
113148 cont.resume(Result .failure())
114- closeEngineIfNeeded()
149+ closeEngineIfNeeded(applicationContext )
115150 }
116151 })
117152 }
118153
119154 Log .d(Constants .logTag, " Worker with method $method completed successfully" )
120- return @future Result .success()
121155 } catch (e: Exception ) {
122156 Log .d(Constants .logTag, " Error sending method $method to Dart: ${e.message} " )
123- return @future Result .failure()
157+ throw e
124158 }
125159 }
126160 }
127161
128- // / Code idea taken from https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
129- private suspend fun initNewEngine () {
130- Log .d(Constants .logTag, " Ensuring Flutter is initialized before creating engine" )
131- // We use the deprecated class here anyways, the new one doesn't work correctly using the same code
132- FlutterMain .startInitialization(applicationContext)
133- FlutterMain .ensureInitializationComplete(applicationContext, null )
134-
135- Log .d(Constants .logTag, " Loading callback info" )
136- val info = ApplicationInfoLoader .load(applicationContext)
137- workerEngine = FlutterEngine (applicationContext)
138- suspendCoroutine { cont ->
139- // set up the method channel to receive events from Dart
140- MethodChannel (workerEngine!! .dartExecutor.binaryMessenger, Constants .methodChannel).setMethodCallHandler {
141- call, result -> run {
142- if (call.method == " ready" ) {
143- Log .d(Constants .logTag, " Dart engine is ready!" )
144- cont.resume(Unit )
145- } else {
146- MethodCallHandler ().methodCallHandler(call, result, applicationContext)
147- }
148- }
162+ override fun startWork (): ListenableFuture <Result > {
163+ val method = inputData.getString(" method" )!!
164+ var data = inputData.getString(" data" )!!
165+ val gson = GsonBuilder ()
166+ .setObjectToNumberStrategy(ToNumberPolicy .LONG_OR_DOUBLE )
167+ .create()
168+ if (method == " SMSMsg" ) {
169+ val json: HashMap <String , Any > = gson.fromJson(data, TypeToken .getParameterized(HashMap ::class .java, String ::class .java, Any ::class .java).type)
170+ val pointer: Int = (json[" id" ] as Long ).toInt()
171+ if (MethodCallHandler .queuedMessages.contains(pointer)) {
172+ data = MethodCallHandler .queuedMessages.remove(pointer)!!
173+ } else {
174+ // bail
175+ return Futures .immediateFuture(Result .success())
149176 }
150- val callbackInfo = FlutterCallbackInformation .lookupCallbackInformation(applicationContext.getSharedPreferences(" FlutterSharedPreferences" , 0 ).getLong(" flutter.backgroundCallbackHandle" , - 1 ))
151- val callback = DartExecutor .DartCallback (applicationContext.assets, info.flutterAssetsDir, callbackInfo)
152-
153- Log .d(Constants .logTag, " Executing Dart callback" )
154- workerEngine!! .dartExecutor.executeDartCallback(callback)
155177 }
156- }
157178
158- private fun closeEngineIfNeeded () {
159- // Delay 5 seconds so Dart has a chance to complete everything and in case new work comes in shortly after
160- Timer ().schedule(5000 ) {
161- val currentWork = WorkManager .getInstance(applicationContext).getWorkInfosByTag(Constants .dartWorkerTag).get().filter { element -> ! element.state.isFinished }
162- Log .d(Constants .logTag, " ${currentWork.size} worker(s) still queued" )
163- if (currentWork.isEmpty() && workerEngine != null ) {
164- Log .d(Constants .logTag, " Closing ${Constants .dartWorkerTag} engine" )
165- // This must be run on main thread
166- CoroutineScope (Dispatchers .Main ).launch {
167- workerEngine?.destroy()
168- workerEngine = null
169- }
179+ if (engine != null ) {
180+ Log .d(Constants .logTag, " Using MainActivity engine to send to Dart" )
181+ } else {
182+ Log .d(Constants .logTag, " Using DartWorker engine to send to Dart" )
183+ }
184+ return CoroutineScope (Dispatchers .Main ).future {
185+ val arguments: HashMap <String , Any > = gson.fromJson(data, TypeToken .getParameterized(HashMap ::class .java, String ::class .java, Any ::class .java).type)
186+ try {
187+ callMethod(applicationContext, method, arguments)
188+ Result .success()
189+ } catch (e: Exception ) {
190+ Log .d(Constants .logTag, " Error sending method $method to Dart: ${e.message} " )
191+ Result .failure()
170192 }
171193 }
172194 }
0 commit comments