gotev/android-upload-service

Question on crash message when closing the app: Unable to create service net.gotev.uploadservice.UploadService

derwaldgeist opened this issue ยท 16 comments

I have implemented the background service successfully, it's working as a charm. Thanks a lot for this library!

However, I noticed some behaviour: When I kill the application by swiping it off, I get this error message in logcat:

Unable to create service net.gotev.uploadservice.UploadService: java.lang.IllegalArgumentException: You have to set namespace to your app package name (context.packageName) in your Application subclass

and Android reports an app crash.

I have read in #510 that this might be related to android:name not being set in AndroidManifest.xml, as also stated in the docs. However, I am developing a plugin for Unity, and Unity does not use a class that derives from Application.

ChatGPT recommended me to set the UnityPluginActivity instead because that is the main entry point for a Unity project. This did not work, because it does not inherit from Application. When I hinted it to this, it recommended to create a blank Application instead.

My questions here:

  • Is this a safe and valid approach?
  • Can it this be done in an Android library that will be imported into the actual application?
  • Which package name should I use? That of my library or that of the main Unity application?
  • Is there any other way to ensure that the library works as expected?

The error occurs only happens if I kill the app. Otherwise, everything works fine, even without the android:name prop. I have done all the initialization stuff in the Activity, using application as the context for UploadServiceConfig.initialize. This worked, but I do not know what class name this application keyword is actually referring to.

Thanks in advance for your support.

I now learned by introspection that Unity's Application class is named "Application" at runtime and has the same package name as set in the Unity configuration. However, if I try to set this application class for android:name, the app crashes on start because the class cannot be found. So I guess I still have to find another way to tell the library which namespace it should use.

gotev commented

Hi, first of all, it's always good to read such a well thought and structured reasoning!

In native Apps, the Application subclass is where you usually perform all the initialization of services and initial configurations, but if Unity has a different lifecycle or "main", you can set the namespace from there. If you are developing an SDK, your users are responsible for calling your SDK init where appropriate.

The important thing is to perform upload service init before you try the first upload, setting the app's package name as the namespace. This setting is so vital, because all the internal event messaging happens using Android intents, which needs a namespace, and without it, it won't work.

Hi @gotev, Thanks for the fast response. Setting the namespace worked in the UnityPlayerActivity worked, and I also checked that the init code will be called first. That was all just fine.

However, the problem occurred when I killed the app. In this case, I got the errror message above.

In the meantime, I was able to add a "real" Application class to the Unity app (I've asked folks at the Unity forum if my approach is valid, still waiting for a response there). As far as I can tell for now, this actually worked. This allowed me to add android:name to the manifest, and since then, the app won't crash with the message any more.

There is one thing I still noticed, though. If I kill the app while an upload is still running, I can see that the onCreate() method of the Application class is called once again, as if my app is being started once more. I can also see an upload notification in the notification center. This upload does not seem to make much progress, though.

Could it be that the service tries to restart the app in this case?

And one more question: What is the reason that the android:name property is needed in the manifest? The service worked just fine without it. At least until I killed the app... Only for this scenario, it was needed somehow.

(And thanks again for the update even on a Sunday.)

gotev commented

Speaking about native apps, setting android:name value == yourApplicationSubclass in the manifest is necessary to register your application subclass and get it run by android on specific app lifecycle moments, one of which is onCreate, when the app gets instantiated and before the first activity is created. This is standard android practice.

As you have described, unity wrappers may differ, and you are experiencing the crash, so follow their guidance in this case.

From a library standpoint, problems you are facing are not due to the lib. Just init it in the best place advised by Unity folks, before starting the first upload. My docs are meant to guide integration on purely native apps.

Got that. I was just wondering why the error only occurs as soon as I kill the app. In the Unity world, there is not much information about the inner workings of the frameork, unfortunately. So much of it is "guesswork". Since my approach works now, I am keeping it and will do some testing in the field to confirm it does not have any consequences. What could happen, though, is that on the long run Unity changes something an side-effects will show up at a time when I almost forgot about this stuff...

One question in addition: I noticed that uploads will continue even if I kill the app, as soon as I restart it. Is this guaranteed by your library? On iOS, the system sometimes would resume the uploads, while sometimes it didn't, in a rather unpredictable manner. This led me to implement a solution where I myself keep track of all uploads, and if iOS would not assume them after a while (i.e. after a timeout fires), I resumed them myself. Is this approach necessary here as well, or does your library do this internally?

gotev commented

One question in addition: I noticed that uploads will continue even if I kill the app, as soon as I restart it. Is this guaranteed by your library?

The library launches an android service attached to the app's process and as long as that process is running, the service will run. I noticed that "killing" the app (swiping it out from the app pager) means different things on different android versions and vendors. Some of them keep the process running and only destroy the activity, others freeze the entire process to resume it later and others kill the app process as well.

Bear in mind that uploads are heavily dependent on device network condition and server socket timeouts, so I strongly advise to have a logic in place to check and retry uploads and not rely on defaults.

For that reason I also added the permanent uploads solution, to give lib users an additional way to implement such custom logics, which require more effort than one could estimate in advance.

Thanks for sharing these additional insights. I dug a bit deeper into the matter and saw that on my Google Pixel 8 Pro, the app seems to get automatically started again after I have swiped it out of the app pager. However, it won't be started regularly (i.e. the Unity initialization routines don't kick in). I saw that, for instance, the Facebook plugin got initialized once again and complained that it didn't have the required configuration data like it normally would. This seems to be also the reason why the upload library crashed if the android:name-prop wasn't set. That got me to the assumption that the service might re-open the app, but only call the Application constructor, but not the rest. Since I added the custom Application class and the android:name prop, everything works fine.

Will now also re-implement my custom retry logic I already built for iOS, just to be sure.

What exactly do you mean by "permanent upload solution"?

gotev commented

What exactly do you mean by "permanent upload solution"?

https://github.com/gotev/android-upload-service/wiki/4.x-Usage#persistence

Thanks! I solved it differently, by saving the pending upload job data in a JSON file, as I did on iOS.

BTW: I checked the stack trace now and saw that the additional initialization of the app (after swiping it out) is actually caused by a ZygoteInit.main. While under normal conditions (app is running) this library is in the middle of the stack trace (which starts at Unity's game loop), in this case the stack trace starts with Zygote. Seems as if this library tries to auto-start the app after the user kills it. It also calls onCreate on the Application class again. Then, pending uploads seem to start again (they shows up in the notifications), but they get stalled and do not have any noticable progress.

I also noticed that sometimes the upload progress gets super slow once I restart an upload job on app restart (only 400kb/s over WiFi). Is that some priority thing?

gotev commented

Zygote is not a lib, but the parent process from which all Android applications are forked. It's responsible for initializing the Dalvik or ART runtime environment. Seeing "ZygoteInit.main" in the stack trace is normal during app startup, but it should not be involved in restarting your app after being swiped out. If you want to dig in further in Android system details: https://elinux.org/Android_Zygote_Startup

I also noticed that sometimes the upload progress gets super slow once I restart an upload job on app restart (only 400kb/s over WiFi). Is that some priority thing?

It's a combination of the factors listed here: https://github.com/gotev/android-upload-service/wiki/Troubleshooting-Procedure#sockettimeout-or-broken-pipe-while-uploading

Thanks for the additional insights. The uploads are still pretty slow even if the app itself is in the foreground. I'm uploading to S3 from a fiber connection, so typically it should be faster than 700kb/s. I had assumed that a background upload is only throttled if the app is in the background?

gotev commented

Under same network conditions and uploading the same file, what's the difference in speed of your Android device vs iPhone? How are you measuring upload performance? However, as I said, there are a ton of variables involved in this. Finding the exact cause is like searching for a needle in a haystack.

I just found out that it must be a problem with my WiFi right now. I tried it on the iPhone and had the same slow speeds. When I switched to a mobile network, things were much faster. Sorry for bothering.

gotev commented

Great that you solved the issue! After all it was not a library bug, so it's great. This leads me to sum this thread like this:

when you choose a hybrid framework and want to do something which involves the platform, you have to write things 3 times: hybrid language, swift, kotlin and debug a whole lot more. When something doesn't work, many other variables gets added to the mix, caused by the additional layer between your code and the platform. This thread is only the last one in a very long list ๐Ÿ˜„