I'm starting a service from my Kivy app:
service = autoclass('net.saband.myapp.ServiceMyservice')
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
service.start(mActivity, '')
It works. And if I close my app using the back button my service still works. But if I close my app by swiping it out from the recent apps list the service dies.
I've found this issue and tried to use startForeground
guided by this article. It works but the notification is not removable so this solution doesn't suit me.
I've read this question and it looks like I could be helped using START_STICKY... but it's kivy service so how can I implement it? I've tried to edit Service.tmpl.java in my python-for-android templates and change this:
public class Service{{ name|capitalize }} extends PythonService {
{% if sticky %}
@Override
public int startType() {
return START_STICKY;
}
{% endif %}
...
to this:
public class Service{{ name|capitalize }} extends PythonService {
@Override
public int startType() {
return START_STICKY;
}
(Yes, I understand that {% if sticky %}
means that I can set it somewhere but in the official docs there is not a word about it.)
But nothing changed, the service still dies. According the logs the restart is scheduled:
11-17 22:52:07.140 1496 1511 I ActivityManager: Killing 29431:net.saband.myapp/u0a122 (adj 9): remove task
11-17 22:52:07.219 1496 3404 I WindowState: WIN DEATH: Window{3c605b3 u0 net.saband.myapp/org.kivy.android.PythonActivity}
11-17 22:52:07.220 1496 3404 W WindowManager: Force-removing child win Window{5ed4ff u0 SurfaceView} from container Window{3c605b3 u0 net.saband.myapp/org.kivy.android.PythonActivity}
11-17 22:52:07.225 1496 2871 W WindowManager: Failed looking up window
11-17 22:52:07.225 1496 2871 W WindowManager: java.lang.IllegalArgumentException: Requested window android.os.BinderProxy@c7f2770 does not exist
11-17 22:52:07.225 1496 2871 W WindowManager: at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:8821)
11-17 22:52:07.225 1496 2871 W WindowManager: at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:8812)
11-17 22:52:07.225 1496 2871 W WindowManager: at com.android.server.wm.WindowState$DeathRecipient.binderDied(WindowState.java:1212)
11-17 22:52:07.225 1496 2871 W WindowManager: at android.os.BinderProxy.sendDeathNotice(Binder.java:558)
11-17 22:52:07.225 1496 2871 I WindowState: WIN DEATH: null
11-17 22:52:07.247 1496 3311 D ActivityManager: cleanUpApplicationRecord -- 29431
11-17 22:52:07.250 1496 3538 I ActivityManager: Killing 29366:net.saband.myapp:service_myservice/u0a122 (adj 8): remove task
11-17 22:52:07.304 1496 3557 D ActivityManager: cleanUpApplicationRecord -- 29366
11-17 22:52:07.305 1496 3557 W ActivityManager: Scheduling restart of crashed service net.saband.myapp/.ServiceMyservice in 1000ms
But nothing happens.
I need the service to continue to work even when the app is closed by swiping it out from the recent apps list. And I need removable notifications. That's all. Many apps can do it. But is there a way to do it with Kivy and python-for-android?
Thanks.
I did it. But this required changing the java code and the solution is hardcoded. It's strange and unpleasantly that python-for-android developers didn't foresee this.
Well, the solution.
Open file .buildozer/android/platform/build/dists/myapp/src/org/kivy/android/PythonService.java
. In function startType()
change START_NOT_STICKY
to START_STICKY
:
public int startType() {
return START_STICKY;
}
Now the service will be restarted. But this is not enough because after restart in function onStartCommand(Intent intent, int flags, int startId)
intent will be null so we will get an error:
E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Bundle android.content.Intent.getExtras()' on a null object reference
So we need to add the if statement in this function:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (pythonThread != null) {
Log.v("python service", "service exists, do not start again");
return START_NOT_STICKY;
}
if (intent != null) {
startIntent = intent;
Bundle extras = intent.getExtras();
androidPrivate = extras.getString("androidPrivate");
androidArgument = extras.getString("androidArgument");
serviceEntrypoint = extras.getString("serviceEntrypoint");
pythonName = extras.getString("pythonName");
pythonHome = extras.getString("pythonHome");
pythonPath = extras.getString("pythonPath");
pythonServiceArgument = extras.getString("pythonServiceArgument");
pythonThread = new Thread(this);
pythonThread.start();
if (canDisplayNotification()) {
doStartForeground(extras);
}
} else {
pythonThread = new Thread(this);
pythonThread.start();
}
return startType();
}
But this is steel not enough because now we have another error in nativeStart
function call because there are no extras:
F DEBUG : Abort message: 'art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: GetStringUTFChars received NULL jstring'
So I've added the null check and some default values (2 of them is hardcoded) to run()
function:
@Override
public void run(){
String package_root = getFilesDir().getAbsolutePath();
String app_root = package_root + "/app";
File app_root_file = new File(app_root);
PythonUtil.loadLibraries(app_root_file);
this.mService = this;
if (androidPrivate == null) {
androidPrivate = package_root;
}
if (androidArgument == null) {
androidArgument = app_root;
}
if (serviceEntrypoint == null) {
serviceEntrypoint = "./service/main.py"; // hardcoded
}
if (pythonName == null) {
pythonName = "myservice"; // hardcoded
}
if (pythonHome == null) {
pythonHome = app_root;
}
if (pythonPath == null) {
pythonPath = package_root;
}
if (pythonServiceArgument == null) {
pythonServiceArgument = app_root+":"+app_root+"/lib";
}
nativeStart(
androidPrivate, androidArgument,
serviceEntrypoint, pythonName,
pythonHome, pythonPath,
pythonServiceArgument);
stopSelf();
}
Now it works.