|
| 1 | +.. _kivy_bootstrap_contract: |
| 2 | + |
| 3 | +Supporting Kivy 3 (the Kivy bootstrap contract) |
| 4 | +=============================================== |
| 5 | + |
| 6 | +Kivy 3 asks the bootstrap for the current Android ``Activity`` instead of |
| 7 | +reflecting a class name of its own. Any tool that builds Android APKs — this |
| 8 | +project, another build tool, or a bootstrap you write yourself — makes Kivy 3 |
| 9 | +work by answering that question. |
| 10 | + |
| 11 | +This page is the implementer's guide: what to ship, the rules it must obey. |
| 12 | +p4a's own implementation is |
| 13 | +``pythonforandroid/recipes/android/src/_kivy_bootstrap.py``. |
| 14 | + |
| 15 | +Kivy 2.3.1 does not use this contract and is unaffected; see |
| 16 | +`Supporting Kivy 2.3.1 as well`_ if you need both. |
| 17 | + |
| 18 | +What you ship |
| 19 | +------------- |
| 20 | + |
| 21 | +One pure-Python module named ``_kivy_bootstrap``, importable at the **top |
| 22 | +level** of the running app's ``sys.path``. Nothing else: no registration call, |
| 23 | +no import order to arrange, no Kivy dependency. |
| 24 | + |
| 25 | +The name is Kivy's, not any bootstrap's. That is the point of the arrangement — |
| 26 | +Kivy depends on the name, so it depends on no particular bootstrap, and an |
| 27 | +unmodified Kivy runs on an APK built by any of them. |
| 28 | + |
| 29 | +Kivy imports the module the first time it needs the Activity, so it must be on |
| 30 | +``sys.path`` before the application's ``main.py`` runs. In p4a it is a |
| 31 | +``py_modules`` entry of the ``android`` recipe, so it lands beside the app's |
| 32 | +other top-level modules in ``site-packages``. |
| 33 | + |
| 34 | +``get_activity()`` (required) |
| 35 | +----------------------------- |
| 36 | + |
| 37 | +Return the current ``android.app.Activity``, or ``None`` where there is none. |
| 38 | + |
| 39 | +.. code-block:: python |
| 40 | +
|
| 41 | + def get_activity(): |
| 42 | + return SomeActivityClass.mActivity |
| 43 | +
|
| 44 | +Kivy calls this **live on every access** and never caches the result, so your |
| 45 | +implementation must not cache it either (see rule 2 below). ``None`` is a |
| 46 | +legitimate answer, not a failure: a background service runs with no Activity, |
| 47 | +and Kivy treats that as the ordinary case it is. |
| 48 | + |
| 49 | +``get_context()`` (optional) |
| 50 | +---------------------------- |
| 51 | + |
| 52 | +Return an ``android.content.Context``, or ``None``. |
| 53 | + |
| 54 | +Implement this only if your bootstrap runs processes that never have an |
| 55 | +Activity and still need a Context. When it is absent — or returns ``None`` — |
| 56 | +Kivy derives the Application context from the current Activity, which is |
| 57 | +equivalent for everything Kivy uses a Context for. p4a does not implement it. |
| 58 | + |
| 59 | +``remove_presplash()`` (optional) |
| 60 | +--------------------------------- |
| 61 | + |
| 62 | +Dismiss your boot splash. Kivy calls this once it has drawn its first frame, |
| 63 | +which is the one thing about a splash screen that only Kivy knows. |
| 64 | + |
| 65 | +*How* a splash is dismissed is entirely yours, and the mechanisms differ |
| 66 | +fundamentally: p4a overlays a ``View`` and removes it, while a bootstrap using |
| 67 | +the Android 12 system splash releases a keep-on-screen condition instead — not |
| 68 | +a method on the Activity at all. That is why this is an optional function on |
| 69 | +your module rather than a method Kivy calls on the Activity. |
| 70 | + |
| 71 | +If you have no splash to dismiss, omit the function. Kivy treats its absence as |
| 72 | +the no-op it is, with no warning. |
| 73 | + |
| 74 | +Rules |
| 75 | +----- |
| 76 | + |
| 77 | +1. **Never import Kivy from this module.** Kivy reads its ``KIVY_*`` |
| 78 | + environment and builds its configuration at import time, so importing it |
| 79 | + before the application runs would freeze that configuration before the app's |
| 80 | + ``main.py`` could set it. Kivy pulls from you precisely so that you never |
| 81 | + have to import it. |
| 82 | + |
| 83 | +2. **Never cache the Activity.** Android destroys and recreates the Activity on |
| 84 | + configuration changes (rotation, dark mode, locale, multi-window) and after |
| 85 | + process death. A stored instance goes stale, and holding one in a Python |
| 86 | + global pins a JNI reference to a dead Activity. Read it fresh on every call |
| 87 | + and staleness stops being your problem. |
| 88 | + |
| 89 | +3. **Resolve reflection inside the call, not at import.** Resolve the Java |
| 90 | + class on first call and cache *the class* if you like, but do not do it at |
| 91 | + module import: a reflection failure would then surface from Kivy's discovery |
| 92 | + import, where it looks like a missing module rather than the real fault. |
| 93 | + |
| 94 | +4. **Fail at import only with an ImportError, and only if you mean it.** Kivy |
| 95 | + treats ``ImportError`` as "this bootstrap does not implement the contract" |
| 96 | + and moves on to raise a diagnostic. Any *other* exception escaping your |
| 97 | + module's import is treated as a fault in your module and propagates |
| 98 | + unchanged, so it is not mistaken for an absent bootstrap. |
| 99 | + |
| 100 | +5. **A presplash hook must not raise, and must tolerate repeat calls.** |
| 101 | + Kivy does not guard the call and makes no promise about calling it exactly |
| 102 | + once. Doing nothing is a correct outcome; raising is not. |
| 103 | + |
| 104 | +6. **Hold no state beyond the resolved class.** Kivy may call these from more |
| 105 | + than one thread. A stateless module is thread-safe without a lock, and the |
| 106 | + contract is designed so that statelessness costs nothing. |
| 107 | + |
| 108 | +What Kivy does with it |
| 109 | +---------------------- |
| 110 | + |
| 111 | +Useful when debugging your implementation: |
| 112 | + |
| 113 | +* The module is imported lazily, on first use, and the outcome is cached — |
| 114 | + including failure, so a build with no such module does not pay for a failed |
| 115 | + import on every geometry read. |
| 116 | +* If the module is missing (or exposes no callable ``get_activity``) **while an |
| 117 | + Android runtime is present**, Kivy raises ``ActivityProviderMissing``. This |
| 118 | + is deliberately not caught by Kivy's display-geometry getters, which would |
| 119 | + otherwise mask a broken build as plausible-looking defaults. |
| 120 | +* ``kivy.mobile.get_app_context()`` prefers your ``get_context()`` and |
| 121 | + otherwise calls ``getApplicationContext()`` on the current Activity. |
| 122 | +* ``kivy.mobile.get_activity()`` returns exactly what your function returned, |
| 123 | + ``None`` included. |
| 124 | + |
| 125 | +Supporting Kivy 2.3.1 as well |
| 126 | +----------------------------- |
| 127 | + |
| 128 | +Kivy 2.3.1 predates this contract. It reaches p4a's Python ``android`` module |
| 129 | +directly — ``android.remove_presplash()`` and friends — and hardcodes |
| 130 | +``org.kivy.android.PythonActivity``. A bootstrap that wants to run both Kivy |
| 131 | +2.3.1 and Kivy 3 therefore has to satisfy both: ship ``_kivy_bootstrap`` for |
| 132 | +Kivy 3, and provide the ``android`` module (and an activity of that class name) |
| 133 | +for 2.3.1. A bootstrap targeting Kivy 3 alone needs only this contract. |
0 commit comments