3 An example of background Twisted server running on Android
Veselin Penev edited this page 2019-09-30 10:21:26 +02:00

Usually if you need to integrate Twisted inside Kivy App on Android you have to do something like that in your main.py:

from kivy.support import install_twisted_reactor
install_twisted_reactor()

But if you do not need Kivy UI in your "background" Twisted server you can just directly run ractor.run() and be happy.

First I tried to use sample code from that wiki to do this: https://github.com/kivy/kivy/wiki/Background-Service-using-P4A-android.service

But there is another issue. Seems like the latest changes in Android requires Android services to also populate some notification to user. You need to create "Notification Channel" and then Android will accept your code.

This is how you can run "foreground" service that will run "Hello, world!" Twisted server on your Android device non-stop:

import sys

from twisted.internet import reactor
from twisted.internet import protocol
from twisted.web import server, resource

import logging
logging.basicConfig(level=logging.DEBUG)

from twisted.internet.defer import setDebugging
setDebugging(True)

from twisted.python.log import startLogging
startLogging(sys.stdout)


class Simple(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        logging.info('Hello, world!')
        return b"<html>Hello, world!</html>"


def set_auto_restart_service(restart=True):
    logging.info('set_auto_restart_service restart=%r', restart)
    from jnius import autoclass
    Service = autoclass('org.kivy.android.PythonService').mService
    Service.setAutoRestartService(restart)


def set_foreground():
    logging.info('set_foreground')
    from jnius import autoclass
    channel_id = 'org.twisted_sample.twistedsample.Twistedsample'
    Context = autoclass(u'android.content.Context')
    Intent = autoclass(u'android.content.Intent')
    PendingIntent = autoclass(u'android.app.PendingIntent')
    AndroidString = autoclass(u'java.lang.String')
    NotificationBuilder = autoclass(u'android.app.Notification$Builder')
    NotificationManager = autoclass(u'android.app.NotificationManager')
    NotificationChannel = autoclass(u'android.app.NotificationChannel')
    notification_channel = NotificationChannel(
        channel_id, AndroidString('TwistedSample Channel'.encode('utf-8')), NotificationManager.IMPORTANCE_HIGH)
    Notification = autoclass(u'android.app.Notification')
    service = autoclass('org.kivy.android.PythonService').mService
    PythonActivity = autoclass(u'org.kivy.android.PythonActivity')
    notification_service = service.getSystemService(
        Context.NOTIFICATION_SERVICE)
    notification_service.createNotificationChannel(notification_channel)
    app_context = service.getApplication().getApplicationContext()
    notification_builder = NotificationBuilder(app_context, channel_id)
    title = AndroidString("TwistedSample".encode('utf-8'))
    message = AndroidString("Application is running in background".encode('utf-8'))
    app_class = service.getApplication().getClass()
    notification_intent = Intent(app_context, PythonActivity)
    notification_intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
        Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
    notification_intent.setAction(Intent.ACTION_MAIN)
    notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
    intent = PendingIntent.getActivity(service, 0, notification_intent, 0)
    notification_builder.setContentTitle(title)
    notification_builder.setContentText(message)
    notification_builder.setContentIntent(intent)
    Drawable = autoclass(u"{}.R$drawable".format(service.getPackageName()))
    icon = getattr(Drawable, 'icon')
    notification_builder.setSmallIcon(icon)
    notification_builder.setAutoCancel(True)
    new_notification = notification_builder.getNotification()
    service.startForeground(1, new_notification)
    logging.info('set_foreground DONE : %r' % service)


def main():
    argument = os.environ.get('PYTHON_SERVICE_ARGUMENT', 'null')
    argument = json.loads(argument) if argument else None
    argument = {} if argument is None else argument
    logging.info('argument=%r', argument)
    if argument.get('stop_service'):
        logging.info('service to be stopped')
        return
    try:
        set_foreground()
        set_auto_restart_service()
        site = server.Site(Simple())
        srv = reactor.listenTCP(18000, site)
        logging.info('starting reactor with %r at %r' % (site, srv.getHost(), ))
        reactor.run()
        logging.info('twisted reactor stopped')
        set_auto_restart_service(False)
    except Exception:
        logging.exception('Exception in main()')
        # avoid auto-restart loop
        set_auto_restart_service(False)


if __name__ == '__main__':
    main()
    logging.info('EXIT')

You can find buildozer project ready to use in this repo: https://github.com/vesellov/android-twisted-service