I'm trying to run FlutterEngine
in Android WallpaperService
, every thing works so far except some VIVO phone crashed and throw the java.lang.RuntimeException
with message in title.
WallpaperService#onCreateEngine
getting called, and WallpaperService.Engine#onCreate
getting called.WallpaperService
in a thread named "WallpaperService".Expected results:
Pass the @UiThread
checking in Android Service
Actual results:
Crash when calling FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:36)
package com.LondonX.live_wallpaper_flt.service
import android.content.res.Configuration
import android.service.wallpaper.WallpaperService
import android.text.format.DateFormat
import android.view.SurfaceHolder
import android.view.ViewConfiguration
import com.LondonX.live_wallpaper_flt.entity.Config
import com.google.gson.Gson
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterJNI
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.renderer.FlutterRenderer
import io.flutter.embedding.engine.systemchannels.SettingsChannel
import kotlinx.coroutines.*
import java.io.File
private const val TAG = "[live_wallpaper_flt]"
class LiveWallpaperFltService : WallpaperService() {
override fun onCreateEngine(): Engine {
return object : Engine() {
private val config by lazy {
val json = File(filesDir, "live_wallpaper_config.json").readText()
Gson().fromJson(json, Config::class.java)
?: throw Exception("${TAG}invalid config file, make sure you called LiveWallpaperFlt.instance.applyConfig first.")
}
private lateinit var flutterEngine: FlutterEngine
private val scope = CoroutineScope(Dispatchers.Main)
private var refreshJob: Job? = null
private var width: Int? = null
private var height: Int? = null
private var isDark = false
override fun onCreate(surfaceHolder: SurfaceHolder?) {
super.onCreate(surfaceHolder)
flutterEngine = FlutterEngine(this@LiveWallpaperFltService)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
config.entryFunction,
),
listOf("is_started_from_wallpaper_service"),
)
isDark = isInDarkMode()
applyPlatformDarkMode()
}
private fun engineScope(f: suspend FlutterEngine.() -> Unit) {
scope.launch {
f.invoke(flutterEngine)
}
}
private fun FlutterEngine.isAttached(): Boolean {
surfaceHolder?.surface ?: return false
val flutterJNI = FlutterRenderer::class.java.getDeclaredField("flutterJNI")
.apply { isAccessible = true }.get(renderer) as? FlutterJNI
return flutterJNI?.isAttached == true
}
override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
if (visible) {
refreshJob = scope.launch {
while (true) {
delay(24)
if (flutterEngine.isAttached()) {
applyViewportMetrics()
if (isDark != isInDarkMode()) {
isDark = isInDarkMode()
applyPlatformDarkMode()
}
}
}
}
engineScope {
lifecycleChannel.appIsResumed()
if (flutterEngine.isAttached()) {
renderer.startRenderingToSurface(surfaceHolder.surface!!, true)
}
}
} else {
refreshJob?.cancel()
engineScope {
lifecycleChannel.appIsPaused()
if (flutterEngine.isAttached()) {
renderer.stopRenderingToSurface()
}
lifecycleChannel.appIsInactive()
}
}
}
override fun onDestroy() {
refreshJob?.cancel()
engineScope {
lifecycleChannel.appIsDetached()
destroy()
}
super.onDestroy()
}
override fun onSurfaceChanged(
holder: SurfaceHolder?, format: Int, width: Int, height: Int
) {
super.onSurfaceChanged(holder, format, width, height)
this.width = width
this.height = height
if (!flutterEngine.renderer.isDisplayingFlutterUi) return
applyViewportMetrics()
}
private fun applyViewportMetrics() {
val width = this.width ?: resources.displayMetrics.widthPixels
val height = this.height ?: resources.displayMetrics.heightPixels
val viewportMetrics = FlutterRenderer.ViewportMetrics()
viewportMetrics.devicePixelRatio = resources.displayMetrics.density
viewportMetrics.height = height
viewportMetrics.width = width
viewportMetrics.physicalTouchSlop =
ViewConfiguration.get(this@LiveWallpaperFltService).scaledTouchSlop
engineScope {
if (flutterEngine.isAttached()) {
renderer.setViewportMetrics(viewportMetrics)
renderer.surfaceChanged(width, height)
}
}
}
private fun isInDarkMode(): Boolean {
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
private fun applyPlatformDarkMode() {
engineScope {
settingsChannel.startMessage()
.setTextScaleFactor(resources.configuration.fontScale)
.setUse24HourFormat(DateFormat.is24HourFormat(this@LiveWallpaperFltService))
.setPlatformBrightness(
if (isInDarkMode()) {
SettingsChannel.PlatformBrightness.dark
} else {
SettingsChannel.PlatformBrightness.light
}
).send()
}
}
}
}
}
Still crash even I tried using engineScope
to wrap codes running on CoroutineScope(Dispatchers.Main)
.
Fatal Exception: java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: WallpaperService
at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:36)
at io.flutter.embedding.engine.FlutterJNI.setPlatformMessageHandler(FlutterJNI.java)
at io.flutter.embedding.engine.dart.DartExecutor.onAttachedToJNI(DartExecutor.java:11)
at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:58)
at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:8)
at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:11)
at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:6)
at io.flutter.embedding.engine.FlutterEngine.<init>(FlutterEngine.java:1)
at com.LondonX.live_wallpaper_flt.service.LiveWallpaperFltService$onCreateEngine$1.<init>(LiveWallpaperFltService.java:18)
at com.LondonX.live_wallpaper_flt.service.LiveWallpaperFltService.onCreateEngine(LiveWallpaperFltService.java:2)
at android.service.wallpaper.WallpaperService$IWallpaperEngineWrapper.executeMessage(WallpaperService.java:2192)
at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:44)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:233)
at android.os.Looper.loop(Looper.java:334)
at android.os.HandlerThread.run(HandlerThread.java:67)
flutter analyze
Analyzing wallpaper_gpt...
No issues found! (ran in 7.0s)
flutter doctor -v
[✓] Flutter (Channel stable, 3.7.7, on macOS 13.2.1 22D68 darwin-x64, locale zh-Hans-CN)
• Flutter version 3.7.7 on channel stable at /usr/local/Caskroom/flutter/1.22.6/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 2ad6cd72c0 (9 days ago), 2023-03-08 09:41:59 -0800
• Engine revision 1837b5be5f
• Dart version 2.19.4
• DevTools version 2.20.1
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc2)
• Android SDK at /Users/london/Library/Android/sdk/
• Platform android-33, build-tools 33.0.0-rc2
• ANDROID_HOME = /Users/london/Library/Android/sdk/
• ANDROID_SDK_ROOT = /Users/london/Library/Android/sdk/
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 14C18
• CocoaPods version 1.11.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2022.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
[✓] IntelliJ IDEA Community Edition (version 2022.1)
• IntelliJ at /Applications/IntelliJ IDEA CE.app
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.76.0)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.60.0
[✓] Proxy Configuration
• HTTP_PROXY is set
• NO_PROXY is localhost,127.0.0.1,::1
• NO_PROXY contains localhost
• NO_PROXY contains 127.0.0.1
• NO_PROXY contains ::1
[✓] Connected device (2 available)
• London’s iPhone (mobile) • 00008101-001E39DE3A63001E • ios • iOS 16.3.1 20D67
• Chrome (web) • chrome • web-javascript • Google Chrome 111.0.5563.64
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!
Owner Name | flutter |
Repo Name | flutter |
Full Name | flutter/flutter |
Language | Dart |
Created Date | 2015-03-06 |
Updated Date | 2023-03-30 |
Star Count | 151602 |
Watcher Count | 3555 |
Fork Count | 25000 |
Issue Count | 11498 |
Issue Title | Created Date | Updated Date |
---|