当前位置: 首页 > 知识库问答 >
问题:

如何让服务在Android系统的python kivy中关闭应用程序后继续工作

勾俊
2023-03-14
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()
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'))
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);
    }
}

服务启动和工作,但关闭应用程序后,服务也关闭。怎么修????

共有1个答案

王昊
2023-03-14

这个变通方法基本上是让服务自动重启。这意味着你的服务将从头开始。是的,这是硬编码。

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

我的是重新开始的争论。对于传递给由ctx.startService()方法触发的onStartCommand()方法的活动意图来说,这将是额外的。然后将'auto restartservice'与restart参数值一起放入。

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

在PythonService的onStartCommand()中设置autoRestartService值

下面看一下PythonService的onDestroy()方法。如果服务被终止(由关闭应用程序或从最近的应用程序中滑动引起),将触发onDestroy()方法。是否重新启动服务取决于autoRestartService值。因此,通过从intent extras获取它,在onStartCommand()方法中设置它。

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

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

最后一件事,buildozer.spec

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

android.permissions = FOREGROUND_SERVICE
...

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

现在通过给出'true'字符串作为第三个位置参数来启动服务。

activity = autoclass('org.kivy.android.PythonActivity').mActivity
service = autoclass('com.omdo.example.ServiceMyservice')
service.start(activity, '', 'true')

注意:我不太懂Java,也许有人能让它更简单。

参考资料:

  • 静态与非静态
  • 前景与背景
  • 意图和额外内容
  • 创建python-for-android服务
 类似资料:
  • 好吧,我是Android Studio的新手,我在玩一个愚蠢的屁噪音应用程序。我的第一次尝试是一个按钮发出噪音。现在我有三个按钮,但应用程序无法在模拟器中打开。它只说应用程序一直在关闭。我试着用谷歌搜索log cat中的每一个错误,但到目前为止没有任何效果。我尝试了两个不同的模拟器,但最初它工作得很好,所以不确定要改变什么。声音文件是。我不确定这是否重要。 这是我的原木猫 04-10 11:41:

  • 假设我计划在整个应用程序中使用一个executorservice,向其发送新的runnable或callable以执行/提交,然后我命令立即关闭。我只想把我的“任务”交给executorservice,让他处理它们,并在提供资源的情况下执行它们(他有多少线程可用,如果需要,他可以创建多少线程,然后相应地将这些任务排队)。 根据您在Android应用程序中使用ExecutorService的经验,并

  • 我有一个程序正在显示结果的条形图。我想等到用户关闭条形图,继续下一行代码,询问他们是否要为该图输入新信息。 正在发生的是,柱状图的场景将打开,但什么也不会显示,并且JOptionPane立即弹出问题。如果我点击“否”,则显示图表,但程序结束。如果我点击“是”,程序将返回到前面的对话框窗口,但在其他对话框结束之前不会显示图表。 我想等待提示用户,直到他们关闭条形图。在这一点上,我是如此的沮丧,以至于

  • 当你第一次启动应用程序时,如何做图片这样的事情?多谢帮忙。

  • 我正在Java做一个应用程序,我需要它在后台工作,当用户关闭应用程序时,他仍然保持隐藏状态