Improving Swift support and interoperability experience for Android

I’m curious about this: what does it mean to be „equivalent“? In our Swift Android code we literally just use @MainActor, but it requires a few lines of setup (driven by the Android choreographer, and calling into a private Dispatch API). Is the equivalent approach preferable do you think, or more a POC?

At some point I tried to do what I assume you’re doing in your repo but I couldn’t figure out the ALooper API enough to get a POC working. I wonder if there’s a way to set up the choreographer callback loop directly from native code.

Good question, I suppose it depends on the level of abstraction. This plugs into the structured concurrency runtime rather than libdispatch and uses eventfd(2) to signal to the Android Looper when there are jobs ready to be scheduled. It doesn't use any private API and it doesn't require polling (are you draining the dispatch queue on frame callbacks?)

Your approach is more portable to existing code that uses @MainActor, but it is likely less efficient.

At some point I tried to do what I assume you’re doing in your repo but I couldn’t figure out the ALooper API enough to get a POC working. I wonder if there’s a way to set up the choreographer callback loop directly from native code.

You don't need the choreographer, you can just use an event FD.

It's quite elegant. The only catch is you need to call AndroidLooper_initialize() in your JNI_OnLoad() as there is unfortunately no non-ambient way to get the main looper (i.e. you can only get the current thread's looper). It is almost possible:

But I wasn't really comfortable using that much private C++ API, it's very fragile.

1 Like

Yes, at some point we used to loop through the main queue if there was time left before the next frame but we found the version I linked to to be sufficient and preferable for our needs (processing max. one job per frame).

I’m curious if we can improve this and/or if there’s a way to decouple this from the frame callback/ to perform vsync via the main queue with something like Task.sleep. To be honest the eventfd API is a bit over my head conceptually at this point.

I did have a look at the code in your repo. At face value it looks clean and elegant. At the same time, because I don’t understand eventfd conceptually, it might as well be Chinese to me :sweat_smile:.

Just reading it I have no idea what it‘s actually doing and what part(s) of it would do what we need. Did you base this on an official guide or reference?

I’m curious if we can improve this and/or if there’s a way to decouple this from the frame callback/ to perform vsync via the main queue with something like Task.sleep. To be honest the eventfd API is a bit over my head conceptually at this point.

eventfd(2) just a way of creating a file descriptor that can be used for signalling events. Have a read of the AndroidLooper code, it's less than 500 LOC, and about a third of that is LockedState from Apple.

1 Like

Just reading it I have no idea what it‘s actually doing and what part(s) of it would do what we need. Did you base this on an official guide or reference?

My references were:

:slight_smile:

4 Likes

Ok I think I got it. So you’re using Android’s main looper (or optionally any arbitrary one) to implicitly poll the eventfd behind the scenes, which will in turn call your AExecutor_thunk, which then drains the associated AExecutor’s queue. Each AExecutor is associated 1:1 with an ALooper. The job itself does not need to be written to the fd- writing a single 64bit value to the fd is enough to inform ALooper to run the „thunk” callback. The actual jobs are stored on the AExecutor.

Essentially you’re adding a callback to Android’s main looper that (indirectly) ensures jobs will run on the main thread. But to do so you’re bypassing the builtin MainActor, so jobs enqueued there will not be processed.

It’s a good approach I think, it’s just a shame about the last point. I wonder what is stopping us (the Swift Android community) from implementing the real MainActor in the Swift Android SDK like you have here. I don’t have enough context about the existing solution (which ties into Dispatch and CoreFoundation) to know the answer to that unfortunately.

2 Likes

Excellent summary – I should put that in the README! :slight_smile:

Agreed, it would be good to support @MainActor. It's on my list to think about, unless we can replace the @MainActor executor then I think it would probably require changes to libdispatch to be able to use the Android Looper as an alternative to kqueue(2) or epoll(2).

3 Likes

In our current implementation our project cannot yet do without libdispatch anyway, but ultimately it’d be beneficial (re: code size, complexity, dependencies) to remove it and rely on the Swift Concurrency runtime alone if possible. Implementing an entirely dispatch-free MainActor would be very helpful for that.

I remember reading proposals on the forums here about the possibility of overriding the main global executor. Unfortunately I lost track of them and I’m unsure if they ever made it into the language.

1 Like

Yeah, I'm not an expert on the internals but I'm sure someone else following this thread is :slight_smile:

More cool spam! Managed to get a FlutterSwift example working with swift-java and the Android SDK. Now, I imagine the number of people that want to write their UI in Dart and business logic in Swift across multiple platforms may not be large, but it has shaken out a bunch of issues in the Java VM integration.

Tooling is still very limited (long shell script) but we'll get there... this test also builds directly on the Linux frame buffer with DRM as well as Apple platforms.

Edit: merged to main now.

11 Likes

Nice! You should consider putting together a Flutter SDK bundle, to make it easy for people to build with. Maybe you and @marcprux can provide it with the Skip toolchain as a paid alternative to their SwiftUI transpiler, for new apps at least.

I'd rather code my GUI in Swift, but I'll take a look at Dart and Flutter because of your work. :smiley:

5 Likes

Congrats!

Doug

2 Likes

New WIP: AndroidLogging: SwiftLog backend for Android logging API.

9 Likes

This is totally worth a PR to GitHub - apple/swift-log: A Logging API for Swift to add it to the list of backends in the README.md :slight_smile: Would you mind doing that and I'm happy to approve that? :+1:

But please make it use a released version and not branch: "main" and we're good :slight_smile:

2 Likes

The released versions don’t support Android - Request for release · Issue #335 · apple/swift-log · GitHub

Sorry that issue was a bit unclear, it seems you'd just need the current main to be released :slight_smile: We can totally just do that right now.

2 Likes

@ktoso I would love to see @lukeh's AndroidLogging backend featured in swift-log!

I am not very familiar with either project (yet) but I would be very very happy to finally see an official logging solution for Android after very close to 10 years of using Swift on the platform.

We did cut a swift-log release so the blocker from android compatibility should be removed afaics @lukeh :-)

We should be clear though that there’s nothing really “official” about any backend, it’s all just open source projects. Swift-log features a list of them but that doesn’t make them any more official than any other backend. It’s great if there can be a backend that folks collaborate on and becomes more reused but I wanted to clarify the word “official” isn’t really the term to use here, it could perhaps be misleading as to expectations on maintenance etc.

1 Like