|
| 1 | +Workers |
| 2 | +======= |
| 3 | + |
| 4 | +python-for-android supports worker tasks using `WorkManager |
| 5 | +<https://developer.android.com/topic/libraries/architecture/workmanager>`_. |
| 6 | +``WorkManager`` tasks are the recommended way to perform both one-time |
| 7 | +and recurring work with current Android. Starting with Android 12, |
| 8 | +worker tasks will also be required to replace foreground services in |
| 9 | +some cases. |
| 10 | + |
| 11 | +Each worker runs tasks in a service declared in the |
| 12 | +``AndroidManifest.xml`` file. This is managed by python-for-android with |
| 13 | +a service generated from the application package. The worker service is |
| 14 | +specified to run in a separate process since python-for-android does not |
| 15 | +support running multiple Python interpreters in the same process. |
| 16 | + |
| 17 | +Worker creation |
| 18 | +--------------- |
| 19 | + |
| 20 | +To create the worker, create a python script with your worker code and |
| 21 | +add a ``--worker=myworker:PATH_TO_WORKER_PY`` argument when calling |
| 22 | +python-for-android. |
| 23 | + |
| 24 | +The ``myworker`` name before the colon is in the names of the worker and |
| 25 | +worker service classes, via which you will interact with it later. |
| 26 | + |
| 27 | +The ``PATH_TO_WORKER_PY`` is the relative path to the worker entry point |
| 28 | +(like ``workers/myworker.py``) |
| 29 | + |
| 30 | +You can add multiple ``--worker`` arguments to include multiple workers, |
| 31 | +all of which you will later be able to stop and start from your app. |
| 32 | + |
| 33 | +Running workers |
| 34 | +--------------- |
| 35 | + |
| 36 | +To run the workers (i.e. starting them from within your main app code), |
| 37 | +you must use PyJNIus to interact with the Java class python-for-android |
| 38 | +creates for each one. First, you need to create a work request using the |
| 39 | +``buildInputData`` helper function which configures the work to run in |
| 40 | +the appropriate service class:: |
| 41 | + |
| 42 | + from jnius import autoclass |
| 43 | + |
| 44 | + worker = autoclass('your.package.domain.package.name.MyworkerWorker') |
| 45 | + OneTimeWorkRequestBuilder = autoclass('androidx.work.OneTimeWorkRequest$Builder') |
| 46 | + argument = '' |
| 47 | + data = worker.buildInputData(argument) |
| 48 | + request = OneTimeWorkRequestBuilder(worker._class).setInputData(data).build() |
| 49 | + |
| 50 | +Here, ``your.package.domain.package.name`` refers to the package |
| 51 | +identifier of your APK. The identifier is set by the ``--package`` |
| 52 | +argument to python-for-android. The name of the worker is |
| 53 | +``MyworkerWorker``, where ``Myworker`` is the identifier that was |
| 54 | +previously passed to the ``--worker`` argument, but with the first |
| 55 | +letter upper case. You must also pass the ``argument`` parameter even if |
| 56 | +(as here) it is an empty string or `None`. If you do pass it, the |
| 57 | +service can make use of this argument. |
| 58 | + |
| 59 | +The argument is made available to your worker via the |
| 60 | +'PYTHON_SERVICE_ARGUMENT' environment variable. It is exposed as a |
| 61 | +simple string, so if you want to pass in multiple values, we would |
| 62 | +recommend using the json module to encode and decode more complex data. |
| 63 | +:: |
| 64 | + |
| 65 | + from os import environ |
| 66 | + argument = environ.get('PYTHON_SERVICE_ARGUMENT', '') |
| 67 | + |
| 68 | +Now the work request needs to be enqueued in the application's |
| 69 | +`WorkManager |
| 70 | +<https://developer.android.com/reference/androidx/work/WorkManager>`_ |
| 71 | +instance:: |
| 72 | + |
| 73 | + mActivity = autoclass('org.kivy.android.PythonActivity').mActivity |
| 74 | + WorkManager = autoclass('androidx.work.WorkManager') |
| 75 | + work_manager = WorkManager.getInstance(mActivity) |
| 76 | + work_manager.enqueue(request) |
| 77 | + |
| 78 | +Enqueuing a work request is asynchronous and returns an `Operation |
| 79 | +<https://developer.android.com/reference/androidx/work/Operation>`_. To |
| 80 | +block until the request has been queued, wait for the state to resolve:: |
| 81 | + |
| 82 | + operation = work_manager.enqueue(request) |
| 83 | + operation.getResult().get() |
| 84 | + |
| 85 | +Once the work request has been queued, information about the request |
| 86 | +such as its current state can be requested from ``WorkManager``:: |
| 87 | + |
| 88 | + request_id = request.getId() |
| 89 | + work_info = work_manager.getWorkInfoById(request_id).get() |
| 90 | + state = work_info.getState() |
| 91 | + print('Work request state:', state.toString()) |
| 92 | + if state.isFinished(): |
| 93 | + print('Work request has completed') |
| 94 | + |
| 95 | +.. note:: |
| 96 | + |
| 97 | + The app root directory for Python imports will be in the app root |
| 98 | + folder even if the worker file is in a subfolder. If the worker is |
| 99 | + in the ``worker/`` folder, it must be imported with ``import |
| 100 | + worker.module`` rather than ``import module``. |
| 101 | + |
| 102 | +Worker progress |
| 103 | +~~~~~~~~~~~~~~~ |
| 104 | + |
| 105 | +A worker can send intermediate progress data for the work request that |
| 106 | +can be retrieved in the activity. From the worker script, use the |
| 107 | +``setProgressAsync`` method from the worker class instance:: |
| 108 | + |
| 109 | + from jnius import autoclass |
| 110 | + |
| 111 | + mWorker = autoclass('your.package.domain.package.name.MyworkerWorker').mWorker |
| 112 | + DataBuilder = autoclass('androidx.work.Data$Builder') |
| 113 | + |
| 114 | + data = DataBuilder().putInt('PROGRESS', 50).build() |
| 115 | + mWorker.setProgressAsync(data) |
| 116 | + |
| 117 | +The progress can be retrieved in the activity from the work request |
| 118 | +information:: |
| 119 | + |
| 120 | + request_id = request.getId() |
| 121 | + work_info = work_manager.getWorkInfoById(request_id).get() |
| 122 | + progress = work_info.getProgress().getInt('PROGRESS', 0) |
| 123 | + print('Work request {}% complete'.format(progress)) |
| 124 | + |
| 125 | +.. note:: |
| 126 | + |
| 127 | + At present, there is no method to return output data for the work |
| 128 | + request. The work is either succeeded or failed based on the exit |
| 129 | + status of the worker script. |
0 commit comments