Skip to content

Commit

Permalink
Add documentation section on workers
Browse files Browse the repository at this point in the history
  • Loading branch information
dbnicholson committed Aug 3, 2022
1 parent ebf6f43 commit 67ed764
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
6 changes: 6 additions & 0 deletions doc/source/buildoptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ options (this list may not be exhaustive):
included in AndroidManifest.xml.
- ``--service``: A service name and the Python script it should
run. See :ref:`arbitrary_scripts_services`.
- ``--worker``: A worker name and the Python script it should run. See
:ref:`workers` for details.
- ``--add-source``: Add a source directory to the app's Java code.
- ``--no-compile-pyo``: Do not optimise .py files to .pyo.
- ``--enable-androidx``: Enable AndroidX support library.
Expand Down Expand Up @@ -149,6 +151,8 @@ ready.
included in AndroidManifest.xml.
- ``--service``: A service name and the Python script it should
run. See :ref:`arbitrary_scripts_services`.
- ``--worker``: A worker name and the Python script it should run. See
:ref:`workers` for details.
- ``add-source``: Add a source directory to the app's Java code.
- ``--port``: The port on localhost that the WebView will
access. Defaults to 5000.
Expand All @@ -170,6 +174,8 @@ systems and frameworks.
- ``--version``: The version number.
- ``--service``: A service name and the Python script it should
run. See :ref:`arbitrary_scripts_services`.
- ``--worker``: A worker name and the Python script it should run. See
:ref:`workers` for details.
- ``--blacklist``: The path to a file containing blacklisted patterns
that will be excluded from the final AAR. Defaults to ``./blacklist.txt``.
- ``--whitelist``: The path to a file containing whitelisted patterns
Expand Down
1 change: 1 addition & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Contents
recipes
bootstraps
services
workers
troubleshooting
docker
contribute
Expand Down
129 changes: 129 additions & 0 deletions doc/source/workers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
Workers
=======

python-for-android supports worker tasks using `WorkManager
<https://developer.android.com/topic/libraries/architecture/workmanager>`_.
``WorkManager`` tasks are the recommended way to perform both one-time
and recurring work with current Android. Starting with Android 12,
worker tasks will also be required to replace foreground services in
some cases.

Each worker runs tasks in a service declared in the
``AndroidManifest.xml`` file. This is managed by python-for-android with
a service generated from the application package. The worker service is
specified to run in a separate process since python-for-android does not
support running multiple Python interpreters in the same process.

Worker creation
---------------

To create the worker, create a python script with your worker code and
add a ``--worker=myworker:PATH_TO_WORKER_PY`` argument when calling
python-for-android.

The ``myworker`` name before the colon is in the names of the worker and
worker service classes, via which you will interact with it later.

The ``PATH_TO_WORKER_PY`` is the relative path to the worker entry point
(like ``workers/myworker.py``)

You can add multiple ``--worker`` arguments to include multiple workers,
all of which you will later be able to stop and start from your app.

Running workers
---------------

To run the workers (i.e. starting them from within your main app code),
you must use PyJNIus to interact with the Java class python-for-android
creates for each one. First, you need to create a work request using the
``buildInputData`` helper function which configures the work to run in
the appropriate service class::

from jnius import autoclass

worker = autoclass('your.package.domain.package.name.MyworkerWorker')
OneTimeWorkRequestBuilder = autoclass('androidx.work.OneTimeWorkRequest$Builder')
argument = ''
data = worker.buildInputData(argument)
request = OneTimeWorkRequestBuilder(worker._class).setInputData(data).build()

Here, ``your.package.domain.package.name`` refers to the package
identifier of your APK. The identifier is set by the ``--package``
argument to python-for-android. The name of the worker is
``MyworkerWorker``, where ``Myworker`` is the identifier that was
previously passed to the ``--worker`` argument, but with the first
letter upper case. You must also pass the ``argument`` parameter even if
(as here) it is an empty string or `None`. If you do pass it, the
service can make use of this argument.

The argument is made available to your worker via the
'PYTHON_SERVICE_ARGUMENT' environment variable. It is exposed as a
simple string, so if you want to pass in multiple values, we would
recommend using the json module to encode and decode more complex data.
::

from os import environ
argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')

Now the work request needs to be enqueued in the application's
`WorkManager
<https://developer.android.com/reference/androidx/work/WorkManager>`_
instance::

mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
WorkManager = autoclass('androidx.work.WorkManager')
work_manager = WorkManager.getInstance(mActivity)
work_manager.enqueue(request)

Enqueuing a work request is asynchronous and returns an `Operation
<https://developer.android.com/reference/androidx/work/Operation>`_. To
block until the request has been queued, wait for the state to resolve::

operation = work_manager.enqueue(request)
operation.getResult().get()

Once the work request has been queued, information about the request
such as its current state can be requested from ``WorkManager``::

request_id = request.getId()
work_info = work_manager.getWorkInfoById(request_id).get()
state = work_info.getState()
print('Work request state:', state.toString())
if state.isFinished():
print('Work request has completed')

.. note::

The app root directory for Python imports will be in the app root
folder even if the worker file is in a subfolder. If the worker is
in the ``worker/`` folder, it must be imported with ``import
worker.module`` rather than ``import module``.

Worker progress
~~~~~~~~~~~~~~~

A worker can send intermediate progress data for the work request that
can be retrieved in the activity. From the worker script, use the
``setProgressAsync`` method from the worker class instance::

from jnius import autoclass

mWorker = autoclass('your.package.domain.package.name.MyworkerWorker').mWorker
DataBuilder = autoclass('androidx.work.Data$Builder')

data = DataBuilder().putInt('PROGRESS', 50).build()
mWorker.setProgressAsync(data)

The progress can be retrieved in the activity from the work request
information::

request_id = request.getId()
work_info = work_manager.getWorkInfoById(request_id).get()
progress = work_info.getProgress().getInt('PROGRESS', 0)
print('Work request {}% complete'.format(progress))

.. note::

At present, there is no method to return output data for the work
request. The work is either succeeded or failed based on the exit
status of the worker script.

0 comments on commit 67ed764

Please sign in to comment.