Aha, thanks to Mark Keen's link, I was able to create a custom Application
for my Flutter app that logs the stack track for strict mode violations. Create a MainApplication.kt
Kotlin file like this:
package com.yourdomain.flutter_app
import android.app.Application
import android.os.StrictMode
import android.os.StrictMode.OnVmViolationListener
import android.os.StrictMode.VmPolicy
import android.os.strictmode.Violation
import android.util.Log
import io.flutter.view.FlutterMain
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.Executor
class CurrentThreadExecutor : Executor {
override fun execute(r: Runnable) {
r.run()
}
}
class StacktraceLogger : OnVmViolationListener {
override fun onVmViolation(v: Violation) {
val sw = StringWriter()
val pw = PrintWriter(sw)
v.printStackTrace(pw)
Log.e("STRICTMODE", sw.toString())
}
}
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
val policy = VmPolicy.Builder()
.detectAll()
.detectNonSdkApiUsage()
.penaltyListener(CurrentThreadExecutor(), StacktraceLogger())
.build()
StrictMode.setVmPolicy(policy)
FlutterMain.startInitialization(applicationContext)
}
}
And then point your AndroidManifest.xml
to it:
<application
android:name="com.yourdomain.flutter_app.MainApplication"
Then when you run it you should see a stack trace. In this case the AccessibilityNodeInfo
stuff does appear to be a Flutter issue:
2021-02-20 12:43:11.986 3291-3291/uk.co.timhutt.flutter_app E/STRICTMODE: android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J
at android.os.StrictMode.lambda$static$1(StrictMode.java:416)
at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
at java.lang.Class.getDeclaredMethodInternal(Native Method)
at java.lang.Class.getPublicMethodRecursive(Class.java:2079)
at java.lang.Class.getMethod(Class.java:2066)
at java.lang.Class.getMethod(Class.java:1693)
at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:446)
at io.flutter.view.AccessibilityViewEmbedder$ReflectionAccessors.<init>(AccessibilityViewEmbedder.java:429)
at io.flutter.view.AccessibilityViewEmbedder.<init>(AccessibilityViewEmbedder.java:70)
at io.flutter.view.AccessibilityBridge.<init>(AccessibilityBridge.java:346)
at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:919)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(FlutterActivityAndFragmentDelegate.java:294)
at io.flutter.embedding.android.FlutterActivity.createFlutterView(FlutterActivity.java:520)
at io.flutter.embedding.android.FlutterActivity.onCreate(FlutterActivity.java:414)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3404)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7660)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Update: The SQLite methods
Weirdly, it still didn't print any stack traces for the SQLite errors, but I noticed that the first errors were for "reflection" whereas those were for "linking". I am linking with a Rust library that uses a bundled copy of SQLite - using this dependency:
rusqlite = { version = "0.24.2", features = ["bundled"] }
And then loading it in Dart:
final DynamicLibrary mapRenderNative = Platform.isAndroid
? DynamicLibrary.open("libmap_render.so")
: DynamicLibrary.process();
When I commented that line out and ran wgradle clean
(necessary it seems), the errors went away! Wtf? It seems like Android intercepts dlopen()
and checks if you link against any of the forbidden symbols.
But the really weird thing is that my libmap_render.so
doesn't link with SQLite
(because it's statically linked into it):
>llvm-readelf -d target\aarch64-linux-android\debug\libmap_render.so
Dynamic section at offset 0x52768 contains 23 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) NOW
0x0000000000000007 (RELA) 0x988
0x0000000000000008 (RELASZ) 9768 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffff9 (RELACOUNT) 405
0x0000000000000017 (JMPREL) 0x2fb0
0x0000000000000002 (PLTRELSZ) 888 (bytes)
0x0000000000000003 (PLTGOT) 0x54940
0x0000000000000014 (PLTREL) RELA
0x0000000000000006 (SYMTAB) 0x308
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000005 (STRTAB) 0x79c
0x000000000000000a (STRSZ) 491 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x778
0x000000000000001a (FINI_ARRAY) 0x54758
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x000000006ffffff0 (VERSYM) 0x6e0
0x000000006ffffffe (VERNEED) 0x734
0x000000006fffffff (VERNEEDNUM) 2
0x0000000000000000 (NULL) 0x0
I also couldn't find any SQLite symbols in the symbol table, relocations, etc. So I have literally no idea what Android is doing. Does it literally scan the binary?
I'll open a new question for that I think.