简体   繁体   English

关闭 python kivy 上 android 中的应用程序后如何使服务继续工作

[英]how to make the service continue working after closing the app in python kivy on android

I want to my service continue working after closing the app, but I can't do it.我希望我的服务在关闭应用程序后继续工作,但我做不到。 I heard I should use startForeground() but how to do it in python?我听说我应该使用startForeground()但如何在 python 中使用它? Code of app:应用程序代码:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from jnius import autoclass
from kivy.uix.label import Label
class MyApp(App):
    def build(self):
        fl = FloatLayout()
        try:
            service = autoclass('org.test.myapp.ServiceMyservice')
            mActivity = autoclass('org.kivy.android.PythonActivity').mActivity                                                                        
            service.start(mActivity, "")
        except Exception as error:
            fl.add_widget(Label(text=str(error), font_size=(40)))
        return fl
if __name__ == '__main__':
    MyApp().run()

Code of my service/main.py :我的service/main.py的代码:

import pickle, socket, jnius

for x in range(5):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    host = 'example-78945.portmap.host'
    port = 78945
    s.connect((host,port))
    s.send(('hello world').encode('utf-8'))

Code of ServiceMyservice.java :服务代码ServiceMyservice.java

package org.test.myapp.ServiceMyservice;

import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonService;
import android.app.Notification;
import android.app.Service;

public class ServiceMyservice extends PythonService {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }
    protected int getServiceId() {
        return 1;
    }

    static public void start(Context ctx, String pythonServiceArgument) {
        Intent intent = new Intent(ctx, ServiceMyservice.class);
        String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
        intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
        intent.putExtra("androidArgument", argument);
        intent.putExtra("serviceTitle", "My Application");
        intent.putExtra("serviceDescription", "Myservice");                                                                     
        intent.putExtra("serviceEntrypoint", "./service/main.py");
        intent.putExtra("pythonName", "myservice");
        intent.putExtra("serviceStartAsForeground", true);
        intent.putExtra("pythonHome", argument);
        intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
        intent.putExtra("pythonServiceArgument", pythonServiceArgument);
        ctx.startService(intent);
    }

    static public void stop(Context ctx) {
        Intent intent = new Intent(ctx, ServiceMyservice.class);
        ctx.stopService(intent);
    }
}

Service starts and works, but after closing the app, service closes too.服务启动并工作,但关闭应用程序后,服务也关闭。 How to fix it????怎么解决????

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向服务模板文件中的 start() 方法添加字符串参数

mine was restart argument.我的是重启论点。 This will be an extra for the activity intent to pass to onStartCommand() method that triggered by ctx.startService() method.这将是一个额外的活动意图传递给由 ctx.startService() 方法触发的 onStartCommand() 方法。 Then put 'autoRestartService' with that restart argument value.然后将“autoRestartService”与该重新启动参数值放在一起。

My.buildozer/android/platform/build-<your arch>/dists/<your app>/templates/Service.tmpl.java: My.buildozer/android/platform/build-<你的拱门>/dists/<你的应用程序>/templates/Service.tmpl.java:

package {{ args.package }};

import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonService;


public class Service{{ name|capitalize }} extends PythonService {

    {% if sticky %}
    @Override
    public int startType() {
        return START_STICKY;
    }
    {% endif %}

    @Override
    protected int getServiceId() {
        return {{ service_id }};
    }
                                  /*add 'restart' String argument to the start() method*/
    static public void start(Context ctx, String pythonServiceArgument, String restart) {
        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
        String argument = 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);
    }

    static public void stop(Context ctx) {
        Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
        ctx.stopService(intent);
    }
}

Set the autoRestartService value within PythonService's onStartCommand()在 PythonService 的 onStartCommand() 中设置autoRestartService

Take a look at PythonService's onDestroy() method below.看看下面 PythonService 的 onDestroy() 方法。 onDestroy() method will be triggered if the service is killed (caused by closing the app or swiping from recent app).如果服务被杀死(由关闭应用程序或从最近的应用程序滑动引起),将触发 onDestroy() 方法。 There is an option wether restart the service or not depends on autoRestartService value.有一个选项是否重启服务取决于 autoRestartService 值。 So set it within the onStartCommand() method by getting it from intent extras.因此,通过从 Intent Extras 中获取它来将其设置在 onStartCommand() 方法中。

My.buildozer/android/platform/build-<your arch>/dists/<your app>/src/main/org/kivy/android/PythonService.java: My.buildozer/android/platform/build-<你的架构>/dists/<你的应用>/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 definition
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.graphics.Color;

public class PythonService extends Service implements Runnable {

    // Thread for Python code
    private Thread pythonThread = null;

    // Python environment variables
    private 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;


    public static PythonService mService = null;
    private Intent startIntent = null;

    private boolean autoRestartService = false;

    public void setAutoRestartService(boolean restart) {
        autoRestartService = restart;
    }

    public int startType() {
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @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;
        }

        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");
        boolean serviceStartAsForeground = (
            extras.getString("serviceStartAsForeground").equals("true")
        );
        pythonServiceArgument = extras.getString("pythonServiceArgument");
        autoRestartService = (
            extras.getString("autoRestartService").equals("true") //this will return boolean for autoRestartservice
        );
        pythonThread = new Thread(this);
        pythonThread.start();

        if (serviceStartAsForeground) {
            doStartForeground(extras);
        }

        return startType();
    }

    protected int getServiceId() {
        return 1;
    }

    protected void doStartForeground(Bundle extras) {
        String serviceTitle = extras.getString("serviceTitle");
        String serviceDescription = extras.getString("serviceDescription");
        Notification notification;
        Context context = getApplicationContext();
        Intent contextIntent = new Intent(context, PythonActivity.class);
        PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            notification = new Notification(
                context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
            try {
                // prevent using NotificationCompat, this saves 100kb on apk
                Method func = 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-1
            String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a";    //TODO: make this configurable
            String channelName = "Background Service";                //TODO: make this configurable
            NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, 
                NotificationManager.IMPORTANCE_NONE);
            
            chan.setLightColor(Color.BLUE);
            chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.createNotificationChannel(chan);

            Notification.Builder builder = new Notification.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);
    }

    @Override
    public void onDestroy() {
        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.
     */
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        stopSelf();
    }

    @Override
    public void run(){
        String app_root =  getFilesDir().getAbsolutePath() + "/app";
        File app_root_file = new File(app_root);
        PythonUtil.loadLibraries(app_root_file,
            new File(getApplicationInfo().nativeLibraryDir));
        this.mService = this;
        nativeStart(
            androidPrivate, androidArgument,
            serviceEntrypoint, pythonName,
            pythonHome, pythonPath,
            pythonServiceArgument);
        stopSelf();
    }

    // Native part
    public static native void nativeStart(
            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.那里有 setAutoRestartService() 方法,但我们不能调用它,因为它是非静态方法。

Last thing, buildozer.spec最后一件事,buildozer.spec

Add FOREGROUND_SERVICE permission and service to buildozer.spec.将 FOREGROUND_SERVICE 权限和服务添加到 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.现在通过将 'true' 字符串作为第三个位置参数来启动服务。

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.注意:我不是很了解Java,也许有人可以让它更简单。

Reference:参考:

@omdo: Thanks for your answer. @omdo:感谢您的回答。

your approach of adding permission works for me.您添加权限的方法对我有用。 Though I know the solution is add permission of FOREGROUND_SERVICE, I tried to add:虽然我知道解决方案是添加 FOREGROUND_SERVICE 的权限,但我尝试添加:

from android.permissions import request_permissions, Permission

request_permissions([Permission.FOREGROUND_SERVICE])

but doesn't work, until I revised buildozer.spec with your suggestion.但不起作用,直到我根据您的建议修改了 buildozer.spec。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 关闭应用程序后如何停止意图服务 - How to stop intent service after closing app 关机后android服务继续 - android service continue after shutdown android 关闭应用后是否可以无限运行服务? - Is it possible to run service endlessly in android even after closing the app? 关闭应用程序后服务未运行 - Service not running after closing app python kivy android 应用程序在移动设备上运行 apk 后崩溃 - python kivy android app crashes after running apk on mobile device 如何使用 python/kivy 在 Android 上获取音频(麦克风)输入 - How to get audio (Mic) input working on Android with python/kivy 即使应用程序被强制停止,也要重新启动服务,即使关闭应用程序,也要在后台继续运行服务如何? - Restart the service even if app is force-stopped and Keep running service in background even after closing the app How? 即使应用程序被强制停止,也要重新启动服务;关闭应用程序后,仍要在后台运行服务。 - Restart the service even if app is force-stopped and Keep running service in background even after closing the app How? 关闭应用程序并且服务仍在运行后的NullPointerException - NullPointerException after Closing App with Service still running 关闭应用程序后服务停止运行 - Service stops running after closing the app
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM