【问题标题】:how to make the service continue working after closing the app in python kivy on android如何在android上的python kivy中关闭应用程序后使服务继续工作
【发布时间】:2020-07-29 09:30:45
【问题描述】:

我希望我的服务在关闭应用程序后继续工作,但我做不到。我听说我应该使用startForeground() 但是如何在 python 中做到这一点?应用代码:

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的代码:

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

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

【问题讨论】:

    标签: java python android service pyjnius


    【解决方案1】:

    @omdo: 谢谢你的回答。

    您添加权限的方法对我有用。 虽然我知道解决方案是添加 FOREGROUND_SERVICE 的权限, 我尝试添加:

    from android.permissions import request_permissions, Permission
    
    request_permissions([Permission.FOREGROUND_SERVICE])
    

    但不起作用,直到我根据您的建议修改了 buildozer.spec。

    【讨论】:

      【解决方案2】:

      这种解决方法基本上是使服务自动重启。这意味着您的服务将从头开始。是的,这是硬编码。

      在Service模板文件的start()方法中添加字符串参数

      我的是 restart 论点。这将是一个额外的活动意图传递给由 ctx.startService() 方法触发的 onStartCommand() 方法。然后将 'autoRestartService' 与该 restart 参数值放在一起。

      我的 .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() 方法中。

      我的 .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,也许有人可以让它更简单。

      参考:

      【讨论】:

      • 也许解释您提供的代码会更有帮助。 +1 用于链接参考
      • @abhivemp 忘了说,这个答案已经更新了。
      • 谢谢,它对我有用,但我遇到了这个问题,可能你已经解决了。它与服务通知点击有关。如果应用程序仍在运行,它会将焦点返回给应用程序。另一种情况是,如果我通过从最近的应用程序列表中刷出应用程序来关闭应用程序,则服务会重新启动,当我单击其通知时,只会显示黑屏而不是应用程序打开。在 doStartForeground() 方法中,我尝试了contextIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);,但没有帮助。怎样才能做到这一点?
      猜你喜欢
      • 1970-01-01
      • 2022-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多