How To Make The Service Continue Working After Closing The App In Python Kivy On Android
Solution 1:
This workaround is basically making the service got an auto restart. That means your service will start from beginning. And yes, this is hardcoding.
Add a string argument to start() method in Service template file
mine was restart argument. This will be an extra for the activity intent to pass to onStartCommand() method that triggered by ctx.startService() method. Then put 'autoRestartService' with that restart argument value.
My .buildozer/android/platform/build-<your arch>/dists/<your app>/templates/Service.tmpl.java:
package {{ args.package }};
import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonService;
publicclassService{{ name|capitalize }} extendsPythonService {
{% if sticky %}
@OverridepublicintstartType() {
return START_STICKY;
}
{% endif %}
@OverrideprotectedintgetServiceId() {
return {{ service_id }};
}
/*add 'restart' String argument to the start() method*/staticpublicvoidstart(Context ctx, String pythonServiceArgument, String restart) {
Intentintent=newIntent(ctx, Service{{ name|capitalize }}.class);
Stringargument= ctx.getFilesDir().getAbsolutePath() + "/app";
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
intent.putExtra("androidArgument", argument);
intent.putExtra("serviceTitle", "{{ args.name }}");
intent.putExtra("serviceDescription", "{{ name|capitalize }}");
intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
intent.putExtra("pythonName", "{{ name }}");
intent.putExtra("serviceStartAsForeground", "{{ foreground|lower }}");
intent.putExtra("pythonHome", argument);
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
intent.putExtra("autoRestartService", restart); /*<-- add this line*/
ctx.startService(intent);
}
staticpublicvoidstop(Context ctx) {
Intentintent=newIntent(ctx, Service{{ name|capitalize }}.class);
ctx.stopService(intent);
}
}
Set the autoRestartService value within PythonService's onStartCommand()
Take a look at PythonService's onDestroy() method below. onDestroy() method will be triggered if the service is killed (caused by closing the app or swiping from recent app). There is an option wether restart the service or not depends on autoRestartService value. So set it within the onStartCommand() method by getting it from intent extras.
My .buildozer/android/platform/build-<your arch>/dists/<your app>/src/main/org/kivy/android/PythonService.java:
package org.kivy.android;
import android.os.Build;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import android.app.Service;
import android.os.IBinder;
import android.os.Bundle;
import android.content.Intent;
import android.content.Context;
import android.util.Log;
import android.app.Notification;
import android.app.PendingIntent;
import android.os.Process;
import java.io.File;
//imports for channel definitionimport android.app.NotificationManager;
import android.app.NotificationChannel;
import android.graphics.Color;
publicclassPythonServiceextendsServiceimplementsRunnable {
// Thread for Python codeprivateThreadpythonThread=null;
// Python environment variablesprivate String androidPrivate;
private String androidArgument;
private String pythonName;
private String pythonHome;
private String pythonPath;
private String serviceEntrypoint;
// Argument to pass to Python code,private String pythonServiceArgument;
publicstaticPythonServicemService=null;
privateIntentstartIntent=null;
privatebooleanautoRestartService=false;
publicvoidsetAutoRestartService(boolean restart) {
autoRestartService = restart;
}
publicintstartType() {
return START_NOT_STICKY;
}
@Overridepublic IBinder onBind(Intent arg0) {
returnnull;
}
@OverridepublicvoidonCreate() {
super.onCreate();
}
@OverridepublicintonStartCommand(Intent intent, int flags, int startId) {
if (pythonThread != null) {
Log.v("python service", "service exists, do not start again");
return START_NOT_STICKY;
}
startIntent = intent;
Bundleextras= 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");
booleanserviceStartAsForeground= (
extras.getString("serviceStartAsForeground").equals("true")
);
pythonServiceArgument = extras.getString("pythonServiceArgument");
autoRestartService = (
extras.getString("autoRestartService").equals("true") //this will return boolean for autoRestartservice
);
pythonThread = newThread(this);
pythonThread.start();
if (serviceStartAsForeground) {
doStartForeground(extras);
}
return startType();
}
protectedintgetServiceId() {
return1;
}
protectedvoiddoStartForeground(Bundle extras) {
StringserviceTitle= extras.getString("serviceTitle");
StringserviceDescription= extras.getString("serviceDescription");
Notification notification;
Contextcontext= getApplicationContext();
IntentcontextIntent=newIntent(context, PythonActivity.class);
PendingIntentpIntent= PendingIntent.getActivity(context, 0, contextIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notification = newNotification(
context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
try {
// prevent using NotificationCompat, this saves 100kb on apkMethodfunc= notification.getClass().getMethod(
"setLatestEventInfo", Context.class, CharSequence.class,
CharSequence.class, PendingIntent.class);
func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
} catch (NoSuchMethodException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e) {
}
} else {
// for android 8+ we need to create our own channel// https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1StringNOTIFICATION_CHANNEL_ID="org.kivy.p4a"; //TODO: make this configurableStringchannelName="Background Service"; //TODO: make this configurableNotificationChannelchan=newNotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,
NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManagermanager= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(chan);
Notification.Builderbuilder=newNotification.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle(serviceTitle);
builder.setContentText(serviceDescription);
builder.setContentIntent(pIntent);
builder.setSmallIcon(context.getApplicationInfo().icon);
notification = builder.build();
}
startForeground(getServiceId(), notification);
}
@OverridepublicvoidonDestroy() {
super.onDestroy();
pythonThread = null;
if (autoRestartService && startIntent != null) {
Log.v("python service", "service restart requested");
startService(startIntent);
}
Process.killProcess(Process.myPid());
}
/**
* Stops the task gracefully when killed.
* Calling stopSelf() will trigger a onDestroy() call from the system.
*/@OverridepublicvoidonTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
stopSelf();
}
@Overridepublicvoidrun(){
Stringapp_root= getFilesDir().getAbsolutePath() + "/app";
Fileapp_root_file=newFile(app_root);
PythonUtil.loadLibraries(app_root_file,
newFile(getApplicationInfo().nativeLibraryDir));
this.mService = this;
nativeStart(
androidPrivate, androidArgument,
serviceEntrypoint, pythonName,
pythonHome, pythonPath,
pythonServiceArgument);
stopSelf();
}
// Native partpublicstaticnativevoidnativeStart(
String androidPrivate, String androidArgument,
String serviceEntrypoint, String pythonName,
String pythonHome, String pythonPath,
String pythonServiceArgument);
}
there is setAutoRestartService() method there, but we cant call it because it is non-static method.
Last thing, buildozer.spec
Add FOREGROUND_SERVICE permission and service to buildozer.spec.
android.permissions = FOREGROUND_SERVICE
...
services = myservice:./path/to/your-service.py:foreground
Now start the service by giving 'true' string as third positional argument.
activity = autoclass('org.kivy.android.PythonActivity').mActivity
service = autoclass('com.omdo.example.ServiceMyservice')
service.start(activity, '', 'true')
Note: I don't really understands Java, maybe someone can make it simpler.
Reference:
Solution 2:
@omdo: Thanks for your answer.
your approach of adding permission works for me. Though I know the solution is add permission of FOREGROUND_SERVICE, I tried to add:
from android.permissions import request_permissions, Permission
request_permissions([Permission.FOREGROUND_SERVICE])
but doesn't work, until I revised buildozer.spec with your suggestion.
Post a Comment for "How To Make The Service Continue Working After Closing The App In Python Kivy On Android"