在android上的python kivy中关闭应用程序后如何使服务继续工作 [英] how to make the service continue working after closing the app in python kivy on android

查看:60
本文介绍了在android上的python kivy中关闭应用程序后如何使服务继续工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望我的服务在关闭应用程序后仍能继续工作,但是我不能这样做.听说我应该使用 startForeground(),但是如何在python中执行呢?应用代码:

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? 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()

我的 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'))

ServiceMyservice.java 的代码:

Code of 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????

推荐答案

此替代方法基本上是使服务自动重启.这意味着您的服务将从头开始.是的,这是硬编码.

向服务模板文件中的start()方法添加字符串参数

我的是 restart 参数.对于将活动意图传递给由ctx.startService()方法触发的onStartCommand()方法而言,这将是额外的.然后将'autoRestartService'与该 restart 参数值一起放置.

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.

我的.buildozer/android/platform/build-< your arch>/dists/< your app>/templates/Service.tmpl.java:

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;


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

在PythonService的onStartCommand()中设置 autoRestartService

Set the autoRestartService value within PythonService's onStartCommand()

看看下面的PythonService的onDestroy()方法.如果服务被终止(由于关闭应用程序或从最近的应用程序刷卡而导致),则会触发onDestroy()方法.是否有一个选项可以重启服务,也可以不依赖于autoRestartService值.因此,请从intent extras中获取它,并在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.

我的.buildozer/android/platform/build-< your arch>/dists/< your app>/src/main/org/kivy/android/PythonService.java:

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

那里有setAutoRestartService()方法,但是我们不能调用它,因为它是非静态方法.

there is setAutoRestartService() method there, but we cant call it because it is non-static method.

最后一点,buildozer.spec

将FOREGROUND_SERVICE权限和服务添加到buildozer.spec.

Add FOREGROUND_SERVICE permission and service to buildozer.spec.

android.permissions = FOREGROUND_SERVICE
...

services = myservice:./path/to/your-service.py:foreground

现在通过提供"true"字符串作为第三个位置参数来启动服务.

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')

注意:我不太了解Java,也许有人可以简化它.

Note: I don't really understands Java, maybe someone can make it simpler.

参考:

这篇关于在android上的python kivy中关闭应用程序后如何使服务继续工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆