Android application startup

I have ported an iOS app to Android using Vlad Gorlov's Swift Everywhere Toolchain.

The non-UI code is shared with the iOS version (about 20k lines of Swift code). The UI is written in Kotlin with a JNI bridge using a combination of my own Swift wrapper and swift-jni.

Aside from the initial grief of fixing all of the NS* bridging issues to get the code to build, the code mostly worked.

One fix I had to make was the main bundle was pointing to a system directory (/system/bin/app_process32), so the localized strings were all missing. I created a pull request for this fix.

The next issue was my API requests weren't working. I setup cacert.pem as described in another post and turned on CURL debugging to see the request and response, but the completion handler never got called.

After a bit of debugging, I realized this was due to the lack of a run loop for Android. For Apple apps, UIApplicationMain sets this all up. But Android is a little different, since the Java/Kotlin code also runs on the main thread.

My current initialization in Kotlin ended up like this...

  • Configure the environmental variables CFFIXED_USER_HOME and URLSessionCertificateAuthorityInfoFile.
  • Copy some files from Android's asset manager to the files directory, such as Info.plist and the localized strings.
  • Load my application's library (the Swift code).
  • Call my application's initialization function.
  • Start a run loop dispatcher.

This is what I came up with for the dispatcher. Here is the Kotlin code...

private fun startDispatch() {
  val handler = Handler(Looper.getMainLooper())
  handler.post(object: Runnable {
    override fun run() {
      MyAppRun()
      handler.postDelayed(this, 100)
    }
  })
}

And this Swift code...

@_cdecl("Java_com_myapp_MyApp_MyAppRun")
public func MyAppRun(env: UnsafeMutablePointer<JNIEnv>, this: JavaObject) {
  let _ = RunLoop.main.run(mode: .default, before: Date().addingTimeInterval(0.01))
}

I tried just using Date(), but then run would lock up even though the documentation claims it should return immediately if nothing is ready to run.

I'm curious if anyone else has had some experience with this and has come up with a different solution.

4 Likes

I find this very interesting, and I wish someone would make a Hello World cross-platform example project as a starting point for poking around.

2 Likes

Vlad does have some basic examples that should get you started.

I've also considered cloning my app and stripping it down to a basic example to share. However, it would be less of a "this is how you should do it" but rather a "this is how I got it to work".

1 Like

I figured out the problem with RunLoop.main.run in polling mode and created a pull request for the fix.

I verified that using Date().addingTimeInterval(0.01) added 10ms to every poll cycle, so it wasn't optimal. It also would hang occasionally.

With the fix, I can now use Date() instead.

I'm going to look into using limitDate instead and possibly schedule the poll based on what is returned.