An unofficial Buildozer Users' Guide
Revised 2023-01-28
- Introduction
- What is Different about Android?
- Android Storage
- Concurrency
- Android Service
- App Permissions
- Buildozer and p4a
- Debugging
- Android Hardware
- Android Packages
- Kivy Related Topics
- Creating a Recipe
- Resources
- Release Builds
- Appendix A : Using adb
- Appendix B : Using an emulator
- Appendix C : Locally modifying a recipe
- Appendix D : Debugging on WSL
- Appendix E : Copying from private storage
- Appendix F : Install Bundletool
- Appendix G : Modifying p4a
- Appendix H : Cryptic Error Messages
- No module named 'msvcrt'
- Aidl not found
- Sdkmanager is not installed
- 64-bit instead of 32-bit
- EM_X86_64 instead of EM_AARCH64
- No module named '_Socket'
- weakly-referenced object
- OpenCV requires Android SDK Tools
- No such file or directory: 'ffmpeg'
- Unsupported class file major version 62
- Permission denied: '/storage/emulated/0/...'
- ModuleNotFoundError: No module named 'PIL'
- Requested API target 27 is not available
- BUILD FAILURE: No main.py(o)
- /usr/bin/gzip: 1: ELF : not found
- SSL: CERTIFICATE_VERIFY_FAILED
- gradlew failed!
- android:exported
- null pointer dereference
- No module named 'android'
- Hunk #1 FAILED
- Kivy is too old
- C compiler cannot create executables
- undefined macro LT_SYS_SYMBOL_USCORE
- all is not a valid value for orientation
- orientation have an invalid value
- build.gradle : 79: Unexpected input: '{'
- Could not resolve all files for configuration
- presplash-lottie: No such file or directory
Python and Kivy are portable across operating systems because of POSIX, Python wheels, and pip. However Android is not POSIX compliant, wheels are not usually available for Android, and pip is not installed on Android. Clearly many apps won't 'just work' on Android. The document is about porting a Kivy app to Android.
For a well written app that only paints the screen, and does nothing else, building for Android will be 'push button'. One can for example build 'Hello World' with the default 'buildozer.spec' file. Of course this does not describe many apps.
This document is not a substitute for Reading The Fine Manual (RTFM). Your basic resources are Buildozer, Python for Android, and Android.
Python-for-Android is a truly amazing achievement, but there are some details to understand. The alternative is Java and the Android api, which has been shown to induce insanity in laboratory mice.
Lastly this document is my understanding as of the date above. I am not a developer in any of the Kivy sub projects (except for a few PRs). I have created a suite of Android orientated examples.
The document is guaranteed to be incomplete is some way, and may possibly be wrong in another way. But reading it will hopefully provide you with come context with which to read the official documentation and to experiment. Those two steps are the key to insight and understanding, which in turn is key to making your idea real.
Android is not POSIX compliant (POSIX is so deep in your assumptions about computers you probably don't know it exists).
The file system model is different, the app cannot use any directory in the file system. The app has specific private storage directories that support file operations. The app also has storage shared between apps, this is implemented as a database.
Threads are available like the desktop. Subprocess is available, but the excutable called by subprocess on the desktop almost certainly is not available.
Multi-tasking, on the desktop when an app loses focus or is minimized it continues to execute - it just doesnt see UI events. Android is not multi-tasking, when an app is removed from the UI it pauses, and then does not execute any app code. Execution without a UI requires an Android service.
Apps have a lifecycle, keep to the Kivy Lifcycle so that the app keeps to the Android Lifecycle. Android api calls made outside of the Kivy App class or its children may cause non-deterministic behavior or crashes.
Some Python packages are not written in Python (are not pure-Python), they contain code that must be compiled. Pip provides pre-compiled packages for desktop OSes, but not for Android. P4a addresses this with recipes, but not all impure packages are available. AVOID DISAPPOINTMENT, check availability first. See also.
Unlike the desktop you must provide information about your Python code, this requirement causes everybody who doesn't understand it to crash and burn. This information must be supplied in the buildozer.spec file. It includes all the pip packages your app depends on, any file types your app depends on for data, and the Android permissions your app depends on. Meta-information may not be limited to that short list, but that list is critical.
The view of the Android file system has changed a few times over the years. Modern Android devices use external storage, however the term is ambiguous as it applies to two incompatible uses cases.
Storage is either Private Storage or Shared Storage. Private Storage content is only visible to the app that created it, Shared Storage is visible to all apps. Python file operations can only be performed in Private Storage, Shared Storage is a database not a file system.
An app can perform Python file operations (read, write, shutil) on its private storage. There are three usable storage location: the app install directory, the app storage directory, and the app cache directory.
No permissions are requires to read or write an app's private storage. More on permissions below.
Do not confuse private with secure, on older Android versions it is possible for other apps to read private storage. And more generally Android Studio allows inspection.
The install directory is ./
, files from the apk or aab can be accessed. An app update will overwrte this directory so this is not a good place to save files (though you can).
Most of the time this is what you should use.
The app storage directory is persistent over the installed lifetime of the app, and is removed when the app is uninstalled. The app storage directory is accessed using:
from android.storage import app_storage_path
app_storage_directory_path = app_storage_path()
The app cache directory is temporary storage, the lifetime of files in this are is managed by Android. The app storage directory is accessed using:
from android import mActivity
context = mActivity.getApplicationContext()
result = context.getExternalCacheDir()
if result:
app_cache_directory_path = str(result.toString())
Shared storage is visible to all apps, and is persistent after an app is uninstalled. A portable app can not perform Python file operations on Shared Storage. Files are copied to and from Shared Storage.
On devices running Android 10 and later or build android.api > 28 shared storage is implemented as a database, you cannot access shared storage with Python file operations because there is no file path. The Kivy and KivyMD file choosers do not work, you must use the Android Chooser.
On devices with Android less than 10, shared storage is a file system and you can access file with from android.storage import primary_external_storage_path
. But this does not work on devices running Android 10 or greater.
On devices running Android 10 or greater shared storage is accessed via the Java MediaStore api, or the Chooser api.
The MediaStore is a database, not a file system. The MediaStore is organized based on multiple root directories. For example 'Music', 'Movies', 'Pictures', 'Documents', and 'Downloads'.
The MediaStore is accessed by copying a file to or from private storage. Because MediaStore files have no file path that Python requires to access a file.
"Are you telling me I can't ....?", I'm explaining that Android shared storage is different from our expectations, evolve or die out.
An Android version independent Python api for shared storage is implemented in the package androidstorage4kivy.
The usage model is, files can be copies to, copied from, and deleted from shared storage. For more details see the SharedStorage class documentation.
Example usage is in shared_storage_example. The example also demonstrates using the Android file Chooser, using the SharedStorage4Kivy Chooser class.
On devices running Android 10 and later no permissions are requires to read or write an app's own shared storage or private storage.
Reading another app's shared storage requires READ_EXTERNAL_STORAGE permission if android.api < 33 and READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO see if android.api >= 33. An app cannot overwrite another app's shared file.
There is one special case, which is different from typical desktop usage. An app cannot read another app's file in the Downloads directory, regardless of permissions.
On devices running Android 9 and less, WRITE_EXTERNAL_STORAGE is required for any file writes. Or READ_EXTERNAL_STORAGE if the app only wants to do shared storage reads.
Files in Shared Storage can be shared between apps. If you want to share a file first copy it to shared storage. Then send it to an Android ShareSheet. The complement operation, receiving a shared file requires copying the file to private storage so Python can access it.
The androidstorage4kivy package contains a ShareSheet class that invokes an Android ShareSheet, or sends a file directly to a specific app. The latter assumes that the specific app knows how to receive files of the type sent.
Examples of sending a file are in share_send_example, and receiving a file in share_receive_example.
Kivy executes on the 'UI thread', Android requires that this thread is always responsive to UI events. As a consequence long latency operations (e.g. network access, sleep()) or computationally expensive operations must be performed in their own Python threads.
Threads must be truly asynchronous to the UI thread, so do not use join()
in the UI thread. A non-UI thread may not write to a UI widget. See this basics example. A very thread safe way to return results to the UI thread is to use the @mainthread
decorator, for example:
from threading import Thread
from kivy.clock import mainthread
def run_some_function_in_thread(self, arg0):
# note the value of args is a tuple,
# it always contains at least one comma
Thread(target=self.some_function, args = (arg0_val,),
daemon=True).start()
def some_function(self, arg0):
try:
# the behavior goes here, creating some result
result = 'greetings earthlings'
# sync a *copy* of the result with the Kivy UI thread
self.make_thread_safe(str(result))
except:
# handle if you want to
pass
@mainthread
def make_thread_safe(self, text):
# assign to some UI widget
self.label.text = text
A daemon thread will exit when the app exits, providing all threads running at app exit are also daemon threads. Failure to always specify a daemon thread may cause an app to exit in a delayed way.
The Python subprocess depends on having an ARM executable to run, this does not exist unless you build it and then chmod 744
. The exception is system commands which of course are compiled for ARM; the executables for ls
, ps
, etc. are in Android's /system/bin
. System commands may not have the same features as a Bash shell, and when run from subprocess.Popen
have app only permission.
There is no python3
executable, Python's sys.executable
is empty. To run a Python script in a new process, we use an Android Service.
Python does not allow calling (async) coroutines from (traditional) routines. Kivy is constructed of routines, however Kivy has a method that allows it to be started as an async coroutine. Thus the Kivy and Async loops do not block one another, this does not mean any other Kivy routines become coroutines.
We normally start a Kivy app with:
ExampleApp().run()
For basic Async usage we start Kivy with (we'll show trio later):
asyncio.run(ExampleApp().async_run('asyncio'))
Alone this does not allow us to combine routines and coroutines. An app can't directly call a coroutine from say a Kivy UI event such as a Button event. But an app can do this indirectly, the details are different for the trio
and asyncio
packages.
The basic approaches are shown in the next two sections, with simple examples. Using trio
a coroutine is started from a routine. Using asyncio
a coroutine is enabled from a routine.
Using trio
, indirectly call a coroutine from a routine by initializing the app with the trio
event loop, and using this to call start_soon()
. The coroutine is started from a routine.
from kivy.app import App
from kivy.uix.label import Label
import trio
class ExampleApp(App):
def __init__(self, nursery):
super().__init__()
self.nursery = nursery
def build(self):
return Label(text = 'Greetings Earthlings')
def on_start(self):
print("Kivy app started")
self.start_async_function()
def start_async_function(self):
self.nursery.start_soon(self.my_async_function) # starts coroutine
async def my_async_function(self):
print("woohooo running async function on kivy app")
await self.another_async_function()
async def another_async_function(self):
print("another async function")
# Start kivy app as an asynchronous task
async def main():
async with trio.open_nursery() as nursery:
await ExampleApp(nursery).async_run("trio") # start Kivy
nursery.cancel_scope.cancel()
trio.run(main) # note this is a reference to main
Thanks to @Hamburguesa for this one.
Using asyncio
, indirectly enable a coroutine from a routine by having the coroutine await
on an asyncio.Event
set by a routine. The coroutine is enabled from a routine.
from kivy.app import App
from kivy.uix.label import Label
import asyncio
class ExampleApp(App):
def __init__(self):
super().__init__()
self.started = asyncio.Event()
def build(self):
return Label(text = 'Greetings Earthlings')
def on_start(self):
print("Kivy app started")
self.started.set()
async def my_async_function(self):
await self.started.wait() # enables coroutine when set()
self.started.clear() # not needed here but good to know about
print("woohooo running async function on kivy app")
await self.another_async_function()
async def another_async_function(self):
print("another async function")
# Start kivy app as an asynchronous task
async def main():
app = ExampleApp()
await asyncio.gather(app.async_run('asyncio'), # starts Kivy
app.my_async_function(), # starts coroutine
return_exceptions = True) # for debugging
asyncio.run(main()) # note this is a call of main()
An Android Service performs operations that are not related to the UI. In the context of Kivy, an Android service is a Python script.
There are two Kivy examples Kivy Service Osc, and Mutli-Service Example. OSC is a good package for message passing between app and service. However it is not designed for passing large datas, consider using the file system in this case. OSC requires INTERNET permission.
We assign the service a name, and associate this with script file name in buildozer.spec
. The service name must be a valid Java class name. The first character and no other character must be upper case. In the sample below the_service.py
is the name of the script, and Worker
is the name we give the service.
# (list) List of service to declare
services = Worker:the_service.py
We start the service from an app using the fully qualified service name, which is the app package name, with '.Service' and the service name appended:
from android import mActivity
def start_service(self, name):
context = mActivity.getApplicationContext()
service_name = str(context.getPackageName()) + '.Service' + name
service = autoclass(service_name)
service.start(mActivity,'') # starts or re-initializes a service
return service
self.start_service('Worker') # starts a service
service_name
is the name of a Java class implementing the service, which in turn executes the file the_service.py
.
Most documentation and examples show stopping a service like this (assuming the class variables have been previously set):
# This does not work with a sticky foreground service
self.service.stop(self.mActivity)
As explained in the next section this does not work with a sticky foreground service. The general way to stop a service is to re-initialize the service then immediately stop it:
# a general way to stop a service
# re-initializes then stops the service
self.start_service('Worker').stop(mActivity)
When debugging, the default Python filter excludes any print statments in the service. To see output from the service use the adb logcat -s
option, for example adb logcat -s Worker
.
The lifetime of the service is limited by the service type. Three types of service are avilabile, the difference between them is the service lifetime allowed by the OS. The service type is specified in buildozer.spec
.
There is an api for restarting a service killed by the OS setAutoRestartService()
. This is only meaningful with a background service, in this case it is probably better to use a foreground service.
A background service is generally short lived as defined by the OS, it is specified in buildozer.spec with:
# (list) List of service to declare
services = Worker:the_service.py
A foreground service's lifetime is no longer than it's app lifetime, when the app stops (not pauses) the service stops. A foreground service is specified in buildozer.spec with:
# (list) List of service to declare
services = Worker:the_service.py:foreground
# (list) Permissions
android.permissions = FOREGROUND_SERVICE
A notification icon may be created in the task bar, see next section.
A sticky foreground service lifetime is nominally unconstrained. An app may stopand start (not just pause) and resume communication with a sticky foreground service. An app is responsible for terminating a sticky foreground service, specially if the service is energy intensive. A sticky foreground service is specified in buildozer.spec with:
# (list) List of service to declare
services = Worker:the_service.py:foreground:sticky
# (list) Permissions
android.permissions = FOREGROUND_SERVICE
A notification icon may be created in the task bar, see next section.
Because an app may stop and restart while a sticky foreground service is running, the app may not contain valid dynamic information about the service. Importantly a reference to the instance of the service saved in a class variable will be None
when the app restarts. The restart-stop code above re-initializes the service to obtain a new reference to the service so that it can be stopped.
For android.api < 33 a foreground service always places a notification icon in the task bar. Depending on Android device version there may be an up to 10 second delay in the appearance of the icon, this delay is an Android 'feature' and does not indicate that the foreground service has not started.
For android.api >= 33 a foreground service only places a notification icon in the task bar if POST_NOTIFICATIONS permission is specified in buildozer.spec and as a run time permission. If you do not do this, app behavior will vary with device version. See the Android documentation and App Permissions. No notification does not mean the service is not started.
The default icon, and the notification title and text,can be changed by specifying three additional string arguments. This requires the master version of Buildozer (1.4.1.dev0).
service.start(mActivity, 'icon_resource_name', 'Title', 'Text', '')
'icon_resource_name' is the name of an Android resource. See android.add_resources
in buildozer.spec
. Remember to consider icon size.
For example adding icon resources from a local directory named 'res_icons':
android.add_resources =
res_icons/all_inclusive.xml:mipmap,
res_icons/align_vertical_top.png:drawable
And use one as:
service.start('all_inclusive', 'Important News', 'Suddenly, nothing happened.', '')
If you want to understand the implementation of services in more detail, read the code: PythonService.java, Service.tmpl.java, and build.py.
Android restricts access to many features. An app must declare the permissions it requires. There are two different declarations, manifest and user. User permissions are a subset of manifest permissions. The full list of permissions is documented by Google. In general you must research the permissions needed by your app, resist the temptation to blindly guess.
Manifest permissions are declared in the buildozer.spec file. Common examples are CAMERA, INTERNET, READ_EXTERNAL_STORAGE, RECORD_AUDIO. Apps that scan Bluetooth or scan Wifi may require multiple permissions.
For example if the app connects to a network add INTERNET permission.
WRITE_EXTERNAL_STORAGE is never required for api >= 30 https://developer.android.com/training/data-storage#permissions
READ_EXTERNAL_STORAGE is never required for api >= 33 https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
Any app manifest permission documented as having "Protection level: dangerous" additionally requires a user permission. From Python this is initiated with a request_permissions()
call. User permission requests are seen as the dialog that Android apps present to the user. Of the four manifest permissions listed above, three are "dangerous".
Many old Kivy examples show request_permissions()
at the top of main.py, on newer versions of Android this will lead to unexpected behavior. Because it violates the Kivy Lifecycle.
Currently request_permissions()
can only be called after (not from) on_start()
, or from build()
, and can only be called once per time step. Generally calling after on_start
simplifies the app control logic for handling both the 'request' case and the 'previously granted' case; of course the user may deny permission and the app must handle this case.
from android.permissions import request_permissions, check_permission, Permission
request_permissions([Permission.CAMERA, Permission.RECORD_AUDIO])
ok = check_permission(Permission.READ_EXTERNAL_STORAGE)
An implementation example is the AndroidPermissions
class which encapsulates permission behavior. You can copy this file and modify the actual permissions for your app. Then instantiate the class like this:
def on_start(self):
self.dont_gc = AndroidPermissions(self.start_app)
def start_app(self):
self.dont_gc = None
# use whatever required permission
Note that the App class variable self.dont_gc
delays garbage collection and it critically important.
Finally it is normal Android behavior that if a user denies permission, it may not be possible to grant that permission from the App. In this case the user must grant the permission from the Android Settings panel for the app.
The current release version of Buildozer is 1.5, by default always Buildozer uses the current release version of p4a.
Buildozer runs on Linux, Windows users need a Linux virtual machine such as WSL, Colab, or VirtualBox to run Buildozer. Buildozer also runs on a Mac, but the number of users is small and this is reflected in the availability of help and in software maturity.
If building on WSL, always build the project in a directory on the Linux partition (somewhere under WSL cd ~
). You will have to copy your project from the Windows partition to the Linux partition. NEVER build the project in a directory on the Windows partition (usually /mnt/c/
or /mnt/d/
), as this may give unpredicatable results.
Install documentation, RTFM, really.
Test your Buildozer install by building (buildozer android debug
) and running Hello World with the default buildozer.spec
(create this with buildozer init
). I know you just want to see your app run on Android, but this simple first step will provide you with a framework to address any future issues.
Some things you can do now to prevent surprises later:
-
Read the install instructions to re-check that all the dependencies are installed.
-
Re-check that the requirements in buildozer.spec are fully and correctly specified.
-
Re-check that each of the requirements is either pure Python or has a recipe.
-
The first time Buildozer is run type
y
each time you are asked to accept a Google License Agreement.
Buildozer's behavior can be non-deterministic in any of these cases:
-
It is run as root.
-
It is run on an NTFS partition mounted on a Linux system. WSL users, don't build your app on the Windows partition (
/mnt/c/Users....
). Because some Python packages implement OS specific behavior based on the disk root name. -
There are Python style trailing comments in the buildozer.spec file. Comment characters must be the first character on a line in the buildozer.spec file.
Note that Buildozer allows specification of build files, versions, and options; but unlike most other build tools it does not do version management. If buildozer.spec is changed the change probably won't propagate into the apk on the next build. After changing the buildozer.spec file users must do an appclean.
buildozer appclean
buildozer android debug
There may be some exceptions to this, the only one I know to be safe is one can add (but not change version of, or remove) a package in the requirements list without the appclean.
You don't need to always do a buildozer appclean
if you don't change buildozer.spec.
There is no magic universal buildozer.spec, its configuration depends on the functionality of your app.
Generally change as few options as possible; resist the temptation to overspecify, you will over constrain the implementation.
-
If the app is for you, change the
title
andpackage.name
. -
If the app is for the store, also change
package.domain
. -
If the app includes data files, add any necessary file extensions in
source.include_exts
. -
If the app uses Python packages that are normally installed with
pip3
, add these and their dependencies torequirements
. -
You want pretty? add
presplash.filename
andicon.filename
. -
Add the required Android Permissions to
android.permissions
. For example if the app connects to a network add INTERNET permission. -
If the app is for the store you will need to increase the default
android.api
. -
Want to speedup debug builds? Remove one of the elements in
android.archs
.
There are a lots of other options about tool versions, Java, and stuff; most users can and should ignore these.
RTFM, really. And see the KivyMD section.
This must contain only alpha numeric characters, do not use any other characters.
This must contain exactly one period .
surrounded by alpha numeric characters, do not use any other characters. Anthing else will cause a Gradle fail.
This must contain no more than two periods .
surrounded by numeric characters. More than two periods will cause a Gradle fail.
This is the list of pip packages (and possibly versions) that your app imports from.
In addition Buildozer needs to know the packages your packages depend on (because there is no pip3 on Android). Determining these is shown in the next section.
Do not add Python system modules, only packages you might install with pip3 on the desktop.
There are some pip3 packages that are added automatically, no need to put these in requirements: libffi, openssl, sqlite3, setuptools, six, pyjnius, android
.
If a requirement is a package that requires a recipe, version pinning may result in a build error. Because a compile recipe is created for the version specified in the recipe, and may not apply to other versions if the compile model has changed.
Your options are to use the default version, or locally modify the recipe.
To recursively find an installed package dependencies, use pipdeptree.
pip3 install pipdeptree
Use pipdeptree -p <packagename>
, for example:
pip3 install google-cloud-firestore
pipdeptree -p google-cloud-firestore
Returns a package dependency tree. It looks like this:
google-cloud-firestore==2.5.2
- google-api-core [required: >=1.31.5,<3.0.0dev,!=2.3.0,!=2.2.*,!=2.1.*,!=2.0.*, installed: 2.8.1]
- google-auth [required: >=1.25.0,<3.0dev, installed: 2.6.0]
- cachetools [required: >=2.0.0,<6.0, installed: 5.0.0]
- pyasn1-modules [required: >=0.2.1, installed: 0.2.8]
- pyasn1 [required: >=0.4.6,<0.5.0, installed: 0.4.8]
- rsa [required: >=3.1.4,<5, installed: 4.8]
- pyasn1 [required: >=0.1.3, installed: 0.4.8]
....and so on....
Each line is a dependency. Because it is a tree and we want a list many lines may be duplicates. Remove the version information (after and including the [
), remove the duplicate lines, remove the newlines, and change the -
to ,
.
We get the list of requirements dependencies for this package.
google-cloud-firestore, googxle-api-core, google-auth, cachetools, pyasn1-modules, pyasn1, rsa, googleapis-common-protos, protobuf, requests, chardet, idna, urllib3, google-cloud-core, proto-plus
However, this technique is only as good as the package information. And exhibits platform variations. For example if this is run on Linux the requests
dependencies certifi, chardet, idna, urllib3
will not be listed. These missing dependencies will be found during debugging.
Some Kivy widgets have requirement dependencies:
kivy.network.urlrequest
needs requests, urllib3, chardet, idna, certifi
kivy.uix.video
needs ffpyplayer
kivy.core.audio.SoundLoader
needs ffpyplayer, ffpyplayer_codecs
For examples see.
Using the technique described above, we get the following:
requirements = python3,kivy, pyrebase4, gcloud, googleapis-common-protos, protobuf, httplib2, pyparsing, oauth2client, pyasn1, pyasn1-modules, rsa, pycryptodome, python-jwt, jws, requests, certifi, chardet, idna, urllib3, requests-toolbelt , jwcrypto, cryptography, deprecated, wrapt
Run on Windows, the last four items were not determined automatically. Run on Linux the requests dependencies were missing. In both cases these were added during debugging.
requirements = python3,kivy, firebase-admin, cachecontrol, msgpack, requests, certifi, chardet, idna, urllib3, google-api-core, google-auth, cachetools, pyasn1-modules, pyasn1, rsa, pyasn1, googleapis-common-protos, protobuf, google-api-python-client, google-auth-httplib2, httplib2, pyparsing, uritemplate, google-cloud-firestore, google-cloud-core, proto-plus, google-cloud-storage, google-resumable-media, google-crc32c
The packages you add here must be pure Python, or have a recipe in this list. If this is not the case, the options are to:
-
Rewrite the app
-
Locally modify an existing recipe see Appendix C.
-
Import the functionality from Java.
None of these options are trivial. That is why it said AVOID DISAPPOINTMENT in the Wheels section above.
If you have a problem run the debugger.
Some combination of portrait
, landscape
, portrait-reverse
, landscape-reverse
. And landscape
is portrait
rotated 90 degrees counter clockwise.
The previous all
option is no longer available, use:
orientation = portrait, landscape, portrait-reverse, landscape-reverse
Note As of 2023/01/28 a workaround for an api 31 issue, using KIVY_ORIENTATION, is no longer required.
If the fullscreen
value is 1
the Status Bar is not shown; defaults to 0
, Status Bar shown. When not shown, a swipe up/down will temporarily show the Status Bar.
fullscreen = 0
If the app contains any data files, for example icons. Then add the file extension to this list:
source.include_exts = py,png,jpg,kv,atlas
Research the Android permissions your app needs. For example
android.permissions = INTERNET, CAMERA, READ_EXTERNAL_STORAGE
If the app connects to a network you must include INTERNET permission.
The current buildozer default is 31, but should be "as high as possible".
android.api = 33
Python for Android enables android.minapi = 21
. Don't decrease this.
The current default is 25b. For typical usage don't change this.
(previously android.arch)
Defaults to building both for ARM7 and ARM8.
android.archs = armeabi-v7a, arm64-v8a
This is what you want when building for the Android Store. For debugging this is probably not what you want because it almost doubles the build time. Select one that matches your debug device.
android.archs = arm64-v8a
An install message INSTALL_FAILED_NO_MATCHING_ABIS means the apk was built for a different architecture than the phone or emulator.
On the desktop your friends are the Python stack trace, and logging or print statements. It is no different on Android. To get these we run the debugger.
First connect the device via USB, on the Android device enable 'Developer Mode' and 'USB debugging'.
If Buildozer was run on a virtual machine such as WSL then it may not be able to use the the physical USB port and the 'deploy run logcat' options will not work. In this case use adb instead.
Successful setup is indicated by log output similar to:
List of devices attached
0A052FDE40019P device
If 'List of devices attached' is followed by an empty line then the connection failed, regardless of what the Buildozer log says afterwards. Because either the device debug options are not set, or the debugger is run from a virtual machine that can't see a physical USB port.
In the logcat output look for 'Traceback', what follows is a Python stack trace, which usually indicates the cause of the issue. For example:
ModuleNotFoundError: No module named 'some-import-name'
Where 'some-import-name' is in 'some-pip-package-name', the error occurs because 'some-pip-package-name' is missing from buildozer.spec requirements. To get 'some-pip-package-name' look at the import statement in the Python code, it will typically be similar to from some-pip-package-name import some-import-name
. Get the file name and line number of the import statement from the traceback.
If you don't see an error message with the Python only messages, there is probably an error message from Android. If using Buildozer's 'deploy run logcat', comment the Python only filter:
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
It is possible to debug using an emulator but this is not recomended initially, as it adds unknowns to the debug process. The emulator is useful for checking a debugged app on various devices and Android versions.
On the desktop if you start a Kivy app from a desktop icon, the app is slower to start than say from an IDE. This is because from the icon Python has to start first, on Android the same delay due to Python starting exists. On Android the splash screen is used to distract from this delay; the splash screen is a work around, not a cause of the delay.
If your app is unusually slow to start, it is because it is doing too much work in the build()
and on_start()
methods. A common symptom of this is is a black screen after the splash screen has closed but before the app displays. This is your code, you can change this behavior. Common causes are monolithic kv
, compute intensive Python in the above methods, I/O intensive Python in the above methods, or too many autoclass()
statements.
A solution for monolithic kv
is to have a kv
file for each screen and to instantiate the screen manager in Python. At build()
only the first screen is added to the screen manager. Other screens are built and added after on_start()
, either on demand or on some schedule. This is known as lazy loading.
Another common solution is to schedule any initial Python compute or I/O intensive tasks to occur after on_start()
, either on demand or on some schedule.
Autoclass is expensive in startup time, if you have 10 or more autoclass statements this may be significant on older devices. The solution is to throw out most of that clever Pyjnius code you spent so long perfecting, and move the code to a Java class in a .java file. Then reference your Java with one or two autoclass in the usual way. Include your Java in the build with Buildozer's android.add_src
.
There may be other causes, its your code.
It is hard to get the Kivy Camera widget to work on Android, for example. There are several 3rd party alternatives. Look at the Preview widget in Camera4Kivy, and try its examples.
Also try the Xcamera widget from the Kivy Garden, or kivy-anderoid-camera. For OpenCV try Kivy-Android-Camera. Also there is Color blind camera and zbarcam.
The relationship between the Android keyboard and the layout is somewhat configurable with softinput_mode, consider Window.softinput_mode = 'below_target'
.
The type of keyboard can be set with input_type
for example TextInput(input_type = 'tel')
. Accepted values are 'text', 'number', 'url', 'mail', 'datetime', 'tel', or 'address'.
The keyboard_suggestions
property does not work on all Android devices. For a workaround see https://github.com/kivy/kivy/blob/master/kivy/uix/behaviors/focus.py#L246-L250 .
A back button/gesture can be detected with a test for key == 27, as shown in the documentation. The key handler must return a boolean.
Android 10 and up require that the back button/gesture can return the app to the Android home screen, therefore there must be a state in the app where back button/gesture is not consumed by the app. For example if the back button/gesture transitions between Kivy screens, from the "main" screen the app can on a back event go to the Android home screen:
class Main(ScreenManager):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Window.bind(on_keyboard = self.keyboard)
def keyboard(self,window,key,*args):
if key == 27 and self.sm.current != "main":
self.current = some_previous_screen
return True # key event consumed by app
else:
return False # key event passed to Android
# (but you probably want the code below here)
On an Android back gesture/button, by default Kivy incorrectly stops the app and leaves it in the app list. The correct behavior would be to pause the app. The workaround is:
from kivy.utils import platform
from android import mActivity
def keyboard(self,window,key,*args):
if key == 27 and platform == 'android':
mActivity.moveTaskToBack(True)
return True
If you wish to stop the app and remove it from the app list, use:
mActivity.finishAndRemoveTask()
This has been addressed in kivy==master, where the above workarounds are not required.
P4a provides Android specific utilities in the android package, this is only available on Android. It is as they say 'self documenting', which really means there isn't any documentation. Read the code.
Plyer is an OS independent api for some non-POSIX OS features. See Supported APIs. Plyer is not well maintained.
Some Plyer modules and examples work on older Android versions, but not on newer Android versions. The Supported APIs table does not reflect this. For example Camera, Speech to text, Audio, and FileChooser do not work on newer Android versions.
The Plyer examples are the documentation.
The following Plyer modules require Manifest and run time permissions:
Module | Permissions |
---|---|
audio | RECORD_AUDIO |
battery | BATTERY_STATS |
brightness | WRITE_SETTINGS |
call | CALL_PHONE |
flash | CAMERA, FLASHLIGHT |
gps | ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION |
sms | SEND_SMS |
stt | RECORD_AUDIO |
vibrator | VIBRATE |
If you plan to use Plyer, and the idea of it is very appealing, first try a small test case on your target Android versions. If it does what you expect, check that all the features you need are available; as Plyer has a platform lowest common denominator design.
Pyjnus allows import of Java code into Python code. It is an interface to the Java api and the Android api. The Android api is only available on Android devices, Android api calls must be debugged on Android.
from jnius import autoclass
# declare a Java Class
DownloadManager = autoclass('android.app.DownloadManager')
# Java sub classes are delimited by a '$' in place of a '.'
DownloadManagerRequest = autoclass('android.app.DownloadManager$Request')
# get a class constant
visibility = DownloadManagerRequest.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
# instance a Java class
self.request = DownloadManagerRequest(uri)
# call a method in that class
self.request.setNotificationVisibility(visibility)
Then use this to write code with Python syntax and semantics, and Java class semantics added. Some basic knowledge of Java semantics is required, get over it. Android classes will require (possibly extensive) reading of the Android Developer Guide and Android Reference.
In addition android.mActivity
gives access to some Android state that may be used by the Android API. For example:
from android import mActivity
mActivity.getWindowManager()
mActivity.getApplicationContext()
mActivity.getSystemService( <service type> )
mActivity.getContentResolver()
Add your own Java to the package using the Buildozer options android.add_src
and android.add_jars
, see buildozer.spec.
Add AndroidX Java packages (or other Maven packages) using the Buildozer option android.gradle_dependencies
, see buildozer.spec and Maven AndroidX Group.
More challenging, it is also possible to write Java class implementations in Python using PythonJavaClass
, RTFM and look at some examples. You will need to understand Java signature format. This implementation is only visible in Python, Java classes cannot see it (though they can see the Java defined interface).
Note: some documentation examples are obsolete. If you see '.renpy.' as a sub field in an autoclass argument replace it with '.kivy.'.
The autoclass()
statement adds significant latency during the app start. If the app contains more than perhaps a dozen autoclass()
calls you will probably notice the extra app startup time.
The way to address this is to create a Java file that references the Java classes that were referenced with autoclass(). Then reference your Java class with autoclass()
. A Java file is included in the project using Buildozer's android.add_src
. If your Java file is located at <project>/src/org/me/myproject/my.java
then use android.add_src = src
.
It is not possible to import Java abstract
classes or methods, as they have no implementation
(abstract and implementation are Java keywords). And it it is not possible to provide the implementation in Python. You must write the implementation in Java and import that new class.
You will be using two garbage collectors working on the same heap, but they don't know each other's boundaries. Python may free a local reference to a Java object because it cant see that the object is used. Obviously this will cause the app to crash in an ugly way. So use class variables, as shown below, to indicate persistence to the Python garbage collector.
###### DONT DO THIS ####
def foobar(self):
# instance a Java class
request = DownloadManagerRequest(uri)
# call a method in that class
request.setNotificationVisibility(visibility)
###### DO THIS ####
def foobar(self):
# instance a Java class
self.request = DownloadManagerRequest(uri)
# call a method in that class
self.request.setNotificationVisibility(visibility)
# Tell the Python garbage collector this will not be reused
self.request = None
Python for Android builds an apk with a minimum device api. Importing Java modules can invalidate this minimum. Check the Added in API level field in the class or method reference documentation.
And if the app includes an .aar
file, check that the version of Java used to build the aar is not newer than the version of OpenJDK you installed for Buildozer. If the aar is built with a newer Java you will get an error message similar to this.
The following illustrates calling a Python method ( here called from_java()
) from Java. In the example there is one argument which is a string. Java sees the method as having a different name, in this case callback_string()
.
The technique relies on passing the method inside a wrapper class. The interface
of the wrapper class is defined in Java. And the implementation
of this wrapper class is defined in Python. The wrapper class is initialized with the Python method to be called. Java calls a method inside an the same instance of the same wrapper class.
You are going to have to write some Java, get over it. To comprehend the following code fragments; read from top to bottom, then follow the path of the string from bottom to top.
In Python:
from jnius import autoclass, PythonJavaClass, java_method
SomeJavaClass = autoclass('org.wherever.whatever.SomeJavaClass')
class SomewhereInMyApp(somewidget):
# instantiate the wrapper
self.callback_instance = CallbackWrapper(self.from_java)
# pass the class instance to Java
SomeJavaClass(self.callback_instance)
# the method to be called
def from_java(self, filepath):
print(filepath) # prints "Greetings Earthlings"
# The wrapper implementation
class CallbackWrapper(PythonJavaClass):
__javacontext__ = 'app'
__javainterfaces__ = ['org/wherever/whatever/CallbackWrapper']
def __init__(self, callback):
super().__init__()
self.callback = callback
@java_method('(Ljava/lang/String;)V')
def callback_string(self, filepath):
if self.callback:
self.callback(filepath)
CallbackWrapper.java
package org.wherever.whatever;
// the wrapper interface
public interface CallbackWrapper {
public void callback_string(String filepath);
}
The Java method generating the callback will be called from Java. The same Java need to reference the CallbackWrapper
instance. Depending on the overall implementation you may need a Java wrapper to call the method and catch the result. Extra Java wrapper or not, call the Python like this:
SomeJavaClass.java
import org.wherever.whatever.CallbackWrapper;
class SomeJavaClass() {
CallbackWrapper callbackClass;
public SomeJavaClass(CallbackWrapper wrapper) {
// self.callback_instance passed from Python
CallbackWrapper callbackClass = wrapper;
}
// This would be the result of some Java callback, and not a static string.
String javaCallBackResult = "Greetings Earthlings";
// At last, the actual Java callback
callbackClass.callback_string(javaCallBackResult);
}
Place the Java files in <project>/src/org/wherever/whatever/
and in buildozer.spec
set android.add_src = src
.
Android-Notification-in-Python
A portable layout must be an elastic layout, because on a mobile device the Kivy window is defined by the screen and the screen changes between devices.
Setting widget properties such as size, size_hint, or orientation make the layout less elastic. But setting some of these layout constraints is required to achieve a particular layout, so portable layout is a balance between the two.
Mobile device screen resolution (dpi), screen orientation (landscape or portrait), and screen aspect ratio all impact the portability of a layout.
Screen resolution can be addressed by specifying font size in units of sp, and widget size in units of dp. See Kivy Metrics.
Screen orientation on a desktop defaults to landscape, and on a mobile device usually defaults to portrait, but can be landscape or both. Developing a layout in landscape will not provide a good layout in portrait. Develop for your target orientation, or support both orientations as follows.
For a mobile device the available orientation is set in buildozer.spec, orientation
it can be one of all
, portrait
, or landscape
.
Supporting orientation = <some list>
usually requires dynamically modifying layout parameters such as size, size_hint, or orientation. Do this using a Widget's on_size()
method, and testing for orientation. For example.
Window aspect ratio on a desktop defaults to 4:3, but screen aspect ratio varies widely on mobile devices typically about 16:9 to 20:9. So a portable layout must be elastic.
A truly elastic layout has only hints, and no sizes. In practice this may not look good in all cases. So we must specify the size of some widgets, but this requires the other widgets to be more elastic.
Geometry tells us there is no right answer. You can however localize issues by for example filling an AnchorLayout with a Label for background color, then centering a fixed size widget in this layout. There are probably many similar techniques, but as far as I know there is no documentation of these.
To test the portability of a layout using a desktop, set the required size and dpi on app start (change the numbers to whatever you want).
python3 main.py --size=420x720 --dpi=200
Manually making changes to this window size quickly explores many cases. This provides a sanity check that you really have a good layout design.
The KivyMD widgets have the look and feel that Android users expect, but the Material Design rules mean you don't have the same flexibility as Kivy widgets.
KivyMD is in development, which means some functionality is still changing. As a concequence the api does change with version changes, including removing parts of the api on a version change. Be certain to use the same version of KivyMD on the desktop and with Buildozer.
How to use KivyMD with Buildozer. There may be additional Buildozer settings required for KivyMD, see KivyMD's sample buildozer.spec.
Follow the Kivy Lifecycle, it abstracts the app behavior that Android expects.
Do not place code in the app that interacts with Android 'script style', to be executed before the Kivy build() call.
request_permissions()
must only be called from the App's build()
method, and only one once with an argument that is a list of all required permissions.
The App's on_stop()
method is not always called, use on_pause()
to save state.
Kivy Garden is a library of components ('flowers'). It is mostly not maintained. Anybody who has had a garden knows a garden needs a gardener, Kivy Garden doesn't have one. Set your expectations accordingly.
For flowers that are maintained add them to your buildozer.spec like this:
requirements = python3, kivy, kivy_garden.xcamera
. For flowers that are not maintained copy the code to your project and edit so that it builds.
There is a #garden_requirements =
field in older buildozer.spec files. This is depreciated and should not be used.
Some Python packages are not pure Python, they contain compiled code. This must be compiled for the specific platform.
Python packages are generally distributed in a wheel, this will contain pre-compiled binaries for supported platforms. Python wheels do not support Android, so to port a package to Android is must be explicitly compiled for ARM/Android.
P4a provides an api for creating package build scripts, such a script is called a "recipe". The task in creating a recipe is to port existing build instructions. To create a recipe you must understand both of these:
-
The p4a recipe API.
-
The existing build instruction for the package.
A recipe generally consists of two parts; a package build, and a package install.
The recipe api attempts to automate common cases such as Cython or setup.py
, but likely this will not be sufficient - you will have to dig deeper.
The documentation provides an overview. Your best resource is the examples.
Build instructions could be a setup.py
, a shell script
, Make
, CMake
, Bazel
, or something else. These instructions may be processor or OS specific, and usually depend on the GCC tools.
A port involves specifying the Android NDK supplied compiler tools (Clang and LLVM) in place of GCC tools; and patching any of the existing build scripts as necessary.
For background reading the Build System Maintainers Guide provides some perspective of the Android build tools.
This is full custom work. You can't schedule this, you are finished when the surprises stop. This task is for those who consider themselves self-supporting. Requests for help usually go unanswered because nobody will have experience of that specific issue in that context.
Really.
Don't rely on other sources, they can be obsolete.
Android for Python Examples contains examples of some Android features as used from Python. These examples only run on Android.
KivAds is a Library that allows you to monetize your Kivy Apps using Google Admob. To use KivAds minor modifications to p4a are currently required.
KivMob is a Library that allows you to monetize your Kivy Apps using Google Admob. The documentation for android.gradle_dependencies
needs to be updated.
There are a lot of useful features to be found at these links:
https://github.com/Sahil-pixel/Pykivdroid
https://github.com/yunus-ceyhan
https://github.com/Kulothungan16/Example-Kivy-Apps
https://github.com/AM-ash-OR-AM-I/Passlock
The following depends on using Buildozer 1.4.0 or later.
Setup signing before your release build.
The official Android signing overview is here.
For a Buildozer build, setting up signing consists of two steps:
-
Create a key, do this once for an app. You will use
keytool
for this. Save the keystore, you will need it in the future to update the app in the store. -
Tell Bulldozer to sign the app using the key. You will set environment variables to pass the information about the key to Buildozer.
Follow the Kivy signing instructions but don't just follow the instructions, read all the annotated comments by HeRo002. The instructions are flawed, but in combination with the comments they are good.
Here are some very detailed signing instructions.
If you are updating an app in the store, you must have the same keystore that was previously used to submit the app to the store. Check that the key values are exported (see links above) as this is how the keys are passed to Buildozer.
The Android Store requires that apps be built with a minimum API level of 31. Set
android.api = 33
To generate a multi-architecture apk or aab, android.archs
specifies a list of architectures. (The legacy android.arch
still works, but will be removed.)
android.archs = armeabi-v7a, arm64-v8a
A debug build always builds an .apk
. A release build creates an .aab
as a default. You can create a release .apk
by setting:
android.release_artifact = apk
After changing buildozer.spec you must:
buildozer appclean
Build a release apk or aab with:
buildozer android release
To install an .aab locally use Bundletool, in place of adb.
The easiest way to get adb
is to install Android Studio.
Add something like this to your PATH:
C:\Users\UserName\AppData\Local\Android\Sdk\platform-tools
Some adb
commands:
adb devices
adb install -r cameraxf-0.1-arm64-v8a-debug.apk
Android and Python messages to a file:
adb logcat -c
adb logcat > log.txt
Python messages only to the screen:
adb logcat *:S python:D
Now start your app.
Note that adb
does not finish, it continues logging until it is terminated.
It is possible to debug using an emulator but this is not recomended initially, as it adds unknowns to the debug process. The emulator is useful for checking a debugged app on various devices and Android versions.
The apk must be built with buildozer.spec android.arch
having the same as ABI the emulator, I find x86 works more reliably than x86_64.
Install Android Studio.
In Android Studio, go to Tools->AVD Manager
Use the 'Create Virtual Device..' button if the emulator you want is not listed.
Right click on an emulator (view details) to see it's name, for example: Nexus_4_API_21
Add something like this to your PATH: C:\Users\UserName\AppData\Local\Android\Sdk\emulator
Start an emulator using it's name
emulator @Nexus_4_API_21
Check the emulator is running using 'adb devices'. Then install an app in the emulator from adb.
Modify an existing recipe by making a local copy. Replace RECIPE_NAME with whatever recipe you are changing:
-
in buildozer.spec set
p4a.local_recipes = ./p4a-recipes
-
mkdir ./p4a-recipes
-
Copy the recipe you want from python-for-android/tree/develop/pythonforandroid/recipes/RECIPE_NAME to ./p4a-recipes
-
Change the files in a way that makes you happy
-
buildozer appclean
-
buildozer android debug
As an alternative to copying the apk from the WSL file system to the main Windows file system, and running adb in Windows, one can use two copies of adb as described here.
The easiest way to get adb is to install Android Studio for both Linux Subsystem (WSL) and Windows of same Android Debug Bridge version, use adb --version
command to find it out
Windows setup:
Add something like this to your PATH in Windows:
C:\Users\UserName\AppData\Local\Android\Sdk\platform-tools
In cmd / pwsh, run this command and this is your server and should not be closed until you finish debugging.
adb kill-server
adb -a -P 5037 nodaemon server
Add the below environmental variable to the ~/.bashrc or ~/.zshrc , depending on your shell
Linux Subsystem (WSL) setup:
export ADB_SERVER_SOCKET=tcp:192.168.1.250:5037
Note: 192.168.1.250
is local ip of your machine find it using ipconfig
in cmd
Finally, adb devices
in Linux Subsystem should show your device
List of devices attached
0A052FDE40019P device
To copy from app private storage on an Android device to the host computer:
Connect the device via USB, on device enable Developer Mode and USB debugging.
In Android Studio run View->Tool Windows->Device File Explorer
In Device File Explorer go to /data/data/org.test.example/files
or lower
Right click the item and Save As
Bundletool depends on JRE.
Download bundletool-all-1.8.0.jar (or newer) from https://github.com/google/bundletool/releases . Save in someplace that is not the project directory.
Simplify your life:
On Linux add to .bashrc
:
alias bundletool='java -jar <pathToSomeplace>/bundletool-all-1.8.0.jar'
On Windows create bundltool.bat containing:
@echo off
java -jar <pathToSomeplace>\bundletool-all-1.8.0.jar %*
Then, for example:
bundletool help
bundletool validate --bundle=Test-release-0.0.1-.aab
To install a .aab
to a locally connected device use bundletool:
bundletool build-apks --mode universal --bundle bin/myapp-0.0.1-armeabi-v7a_arm64-v8a-release.aab --output ./app.apk
bundletool install-multi-apks --apks app.apk
Buildozer clones p4a and places a copy in <project>/.buildozer
. Do not try modifying this, this is Buildozer's database - you don't control what happens there.
Make a local copy of p4a, but not in the <project>
directory. In buildozer.spec specify where the local copy is located:
p4a.source_dir = /someplace/python-for-android-develop
Modify the local copy in a way that makes you happy. This will be cloned into <project>/.buildozer
so remember to appclean
after changes.
As an alternative to making changes locally, you can reference changes saved on GitHub. The fork must be 'public' on Github. For example if you have a p4a fork on GitHub under SomeGitHubName
, and optionally some branch some_branch
:
p4a.fork = SomeGitHubName
p4a.branch = some_branch
- No module named 'msvcrt'
- Aidl not found
- Sdkmanager is not installed
- 64-bit instead of 32-bit
- EM_X86_64 instead of EM_AARCH64
- No module named '_Socket'
- weakly-referenced object
- OpenCV requires Android SDK Tools
- No such file or directory: 'ffmpeg'
- Unsupported class file major version 62
- Permission denied: '/storage/emulated/0/...'
- ModuleNotFoundError: No module named 'PIL'
- Requested API target 27 is not available
- BUILD FAILURE: No main.py(o)
- /usr/bin/gzip: 1: ELF : not found
- SSL: CERTIFICATE_VERIFY_FAILED
- gradlew failed!
- android:exported
- null pointer dereference
- No module named 'android'
- Hunk #1 FAILED
- Kivy is too old
- C compiler cannot create executables
- undefined macro LT_SYS_SYMBOL_USCORE
- all is not a valid value for orientation
- orientation have an invalid value
- build.gradle : 79: Unexpected input: '{'
- Could not resolve all files for configuration
- presplash-lottie: No such file or directory
No module named 'msvcrt'
and No module named '_posixsubprocess'
Newer versions of setuptools break the p4a build tools.
pip3 list | grep setuptools
will show a version >= 60
The workaround requires that you are using a venv.
pip3 uninstall setuptools
pip3 install setuptools==58.0.0
And buildozer appclean
.
# Aidl not found, please install it.
Aidl is part of the Google tools. To get the tools you have to accept the Google license agreements.
Delete ~/.buildozer
and <project>/.buildozer
, then buildozer android debug
and accept the Google license agreements.
# sdkmanager path "/home/????/.buildozer/android/platform/android-sdk/tools/bin/sdkmanager" does not exist, sdkmanager is notinstalled
Sdkmanager is part of the Google tools. To get the tools you have to accept the Google license agreements.
Delete ~/.buildozer
, then buildozer android debug
and accept the Google license agreements.
ImportError: dlopen failed: "/data/data/org.test.myapp/files/app/_python_bundle/site-packages/cv2/cv2.so" is 64-bit instead of 32-bit
The build process included the wrong binary file.
You can check this by changing arch from armeabi-v7a
to arm64-v8a
, buildozer appclean
, and rebuild you will get a different error. This will say failed to link an x86_64 object, which is a more informative message.
The cause is an x86_64 binary from PyPl was used; because of a requirements specification error in buildozer.spec
One of the app requirements needs a recipe (or needs to be specified differently).
In this case the requirements for OpenCV is incorrectly specified, it should be opencv
because that is the recipe name.
If this message is charset_normalizer
related, in Buildozer requirements set charset-normalizer==2.1.1
.
ImportError: dlopen failed: "<something>/_python_bundle/site-packages/<somepackage>/_binding.so" is for EM_X86_64 (62) instead of EM_AARCH64 (183)
The build process included the wrong binary file.
The cause is an x86_64 binary from PyPl was used; because of a requirements specification error in buildozer.spec
One of the app requirements needs a recipe (or needs to be specified differently).
If this message is charset-normalizer
related, in Buildozer requirements set charset-normalizer==2.1.1
.
Module not found error: No module named '_Socket'
Occurs with some VMs (just Colab?) in response to buildozer android deploy run
Because in most cases you can't deploy run
from a VM because the VM can't see the physical USB port.
Copy the .apk
to the desktop, and use adb.
ReferenceError: weakly-referenced object no longer exists
This almost always due to a known issue in kivy==1.11.1
with Python >= 3.8
The fix for this case is to use the current Kivy.
In any other case this issue can be hard to find. The programmer is assuming an object can be reused, and the garbage collector is assuming it will not be reused. One way to create this error is to assume some third party api is not stateful, when infact it does have state.
Android SDK Tools: OpenCV requires Android SDK Tools revision 14 or newer.
Yes, but OpenCV also requires Android SDK Tools revision 30 or older (I assume it is 30). Buildozer currently uses 31 and this revision does not contain the tools that the OpenCV build expects. Hence the misleading error message, which should be OpenCV requires Android SDK Tools revision 30 or older.
.
It is possible to address this with a patch to ~/.buildozer
to include the older tools as described in this thread.
Occurs when the app is using subprocess() to run ffmpeg.
There is no ffmpeg executable. You have to build it for ARM. The recipe builds a library, not an executable. After building the executable, copy it to the working directory. Android does not allow installing to the usual desktop directories.
[DEBUG]: General error during semantic analysis: Unsupported class file major version 62
Version 62 Is Java 18. Error is probably due to an added jar built with Java 18. This is not going to work.
The Java version used by p4a is the version required for the gradle version used. This can be any version between openJDK-11 and 17, the install instructions suggest 17.
PermissionError: [Errno 13] Permission denied: '/storage/emulated/0/org.test.x' - Kivy on android write and save file
This occurs on devices running Android 10 or higher. What was once known as "external strorage" is now "shared storage", and is a database not a file system. The app's "local storage" is now "private storage", private storage is still a file system. See the Android Storage section.
App file operations should occur in private storage. If you want to share the file then it must be 'copied' from private to shared storage, this requires a database 'create' operation.
The package androidstorage4kivy contains a copy_to_shared()
method that implements the database create. Sample usage is here.
ModuleNotFoundError: No module named 'PIL'
The most common cases are:
Either 'pillow' was not specified in buildozer.spec requirements, in which case it must be added.
Or buildozer has been run with root permissions. Running Buildozer as root is known to cause non deterministic behavior, this is one example.
Simply running buildozer as user is not sufficent to fix the issue. Before running buildozer with user permissions you must first cleanup the corrupted state.
cd <project directory>
sudo rm -rf .buildozer
sudo rm -rf ~/.buildozer
# and if either of these exist:
sudo rm -rf /.buildozer
sudo rm -rf ~/.gradle
Check also that your project files are owned by a user, and not by root.
[ERROR]: Build failed: Requested API target 27 is not available, install it with the SDK android tool.
In buildozer.spec set android.api to 33.
BUILD FAILURE: No main.py(o) found in your app directory
Things to check:
A hidden directory (the name starts with a period) in the project path will cause this failure. This is a tool error.
An app build error, try python3 -m compileall main.py
to check for errors.
/usr/bin/gzip: 1: ELF : not found
This is due to regressive behavior in WSL 1, ref.
Check the current WSL version at the Windows Command prompt with wsl -l -v
.
The fix is to upgrade to WSL 2.
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Add certifi
to requirements, and possibly explicitly specify the location of the certificate.
import certifi
resp = request.urlopen(req, cafile=certifi.where())
[WARNING]: ERROR: whatever/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/appname/gradlew failed!
The Gradle error message is further up the log file. Search upwards for 'BUILD FAILED' or 'what went wrong' (without the ''), the error messages will be in this general area.
Generally Gradle errors are due to an incorrect Java version, Java usage errors, missing Java files, a Java jar built with a newer version of Java, missing Android Java packages, or misconfigured Android resource or configuration files. Gradle does not analyze Python errors.
[DEBUG]: android:exported needs to be explicitly specified for element <whatever>
This is a Gradle error message, it is about using an obsolete version of the Android package named in the message.
You can research Android package versions at Maven, and you will need a version that is compatible which whatever Python code you are using.
DEBUG : Cause: null pointer dereference
This message is not from Python, it is from the Android run time system. Some Android api call has been corrupted. This is a memory corruption issue it may exhibit differently (or not at all) on different devices. Probably due to a misuse of Pyjnius, Plyer, or android_permissions.
ALERT There is an issue with KivyMD 1.1.1 that can cause this issue, this issue does not appear to exist in KivyMD 1.0.2 .
Memory issues are incredibly hard to debug, the error may or may not be local to the symptom. So removing code from the app may just move the issue, not remove the issue. That doesn't mean don't cut your app down, it means it is not sufficent to see your app work - you must also understand which code of yours broke the app.
Some possibilities:
-
A corrupted build try an appclean. This probably it not the issue but if it is you'll save a lot of work.
-
Pyjnius code that does not take into account the use of two garbage collectors, modify your code.
-
Plyer or android_permissions calls that do not occur in the 'App functions' block of the Kivy Lifecycle. Modify your code.
-
It is also possible that an impure Python package that has no recipe could generate this error; though other error messages or are more likely.
ModuleNotFoundError: No module named 'android'
The android package is only available on Android. This error occurs on a desktop, because there is no Android api implementation. The android package is only available on Android, and is installed by p4a.
Perhaps you want platform specific code:
from kivy.utils import platform
if platform == 'android':
import android
STDOUT:
patching file somefile
Hunk #1 FAILED
Usually this is due to version pinning a compile recipe, the error is due to pinning a version that is incompatible with the compile recipe.
Remove the version pin, or locally modify the recipe.
Exception: The version of Kivy installed on this system is too old. (You have 2.1.0, but the application requires 2.2.0)
This message comes from KivyMD, and is seen on all platforms.
The options are:
-
Use KivyMD==1.0.2 and Kivy==2.1.0
-
Use KivyMD==1.1.1 and Kivy==master (to install the Kivy master version (2.2.0.dev0) on a desktop, see the Kivy install documentation)
Background: KivyMD 1.1.1 uses a feature that is only available in the master version of Kivy (2.2.0.dev0); here is no Kivy 2.2.0 despite what its says in the KivyMD documentation. This Kivy feature (BoxShadow) has had issues - it has not been reliable, this may be fixed now.
[DEBUG]: configure: error: C compiler cannot create executables
May occur when building libffi
.
This can be due to an interaction between WSL 1 and Clang in NDK 25b.
Upgrade to WSL 2
Ref : microsoft/WSL#8681 kivy/buildozer#1543
[DEBUG]: configure.ac:215: error: possibly undefined macro: LT_SYS_SYMBOL_USCORE
Re-check that the Buildozer dependencies are installed https://github.com/kivy/buildozer/blob/master/docs/source/installation.rst#android-on-ubuntu-2004-and-2204-64bit
[app] "all" is not a valid value for "orientation"
As of Buildozer 1.5, in buildozer.spec
orientation = all
must be replaced with:
orientation = portrait, landscape, portrait-reverse, landscape-reverse
[app] "orientation" have an invalid value
Upgrade to Buildozer 1.5
build.gradle': 79: Unexpected input: '{' @ line 79, column 14.
As of Buildozer 1.5 the values of the gradle_dependencies option must not be in quotes. The previous approach was a security risk.
For example change:
android.gradle_dependencies = "org.tensorflow:tensorflow-lite:+",'org.tensorflow:tensorflow-lite-support:0.0.0-nightly'
to
android.gradle_dependencies = org.tensorflow:tensorflow-lite:+,org.tensorflow:tensorflow-lite-support:0.0.0-nightly
Could not resolve all files for configuration ':debugRuntimeClasspath'.
[DEBUG]: > Could not find "com.google.mlkit:face-detection:16.0.6".
As of Buildozer 1.5 the values of the gradle_dependencies option must not be in quotes. The previous approach was a security risk.
In the above example change:
android.gradle_dependencies = "com.google.mlkit:face-detection:16.0.6"
to
android.gradle_dependencies = com.google.mlkit:face-detection:16.0.6
'--presplash-lottie', "/Home/user/dir/'.pics/4.json'",
......
FileNotFoundError: [Errno 2] No such file or directory: "/Home/user/project/'.pics/4.json'"
As of Buildozer 1.5 the values of the Lottie file path option must not be in quotes. The previous approach was a security risk.
Change:
android.presplash_lottie = "./pics/4.json"
to
android.presplash_lottie = ./pics/4.json