Android¶
Chaquopy is distributed as a plugin for Android’s Gradle-based build system.
Prerequisites:
Android Gradle plugin version should be between 3.1.x and 3.4.x. This is specified as
com.android.tools.build:gradle
in your project’s top-levelbuild.gradle
file, and will usually be the same as your Android Studio version. Newer versions may also work, but have not been tested with this version of Chaquopy.Older versions as far back as 2.2.x are supported by older versions of Chaquopy: for details, see this page.
- minSdkVersion must be at least 15 (Android 4.0.3).
Basic setup¶
Note
Previous versions of Chaquopy required you to specify a Python version to build into your app. From Chaquopy 5.x onwards, this is no longer necessary because each Chaquopy version comes with only one Python version. For the mapping between versions, see this page.
Gradle plugin¶
In the project’s top-level build.gradle
file, add the Chaquopy Maven repository and
dependency to the end of the existing repositories
and dependencies
blocks:
buildscript { repositories { ... maven { url "https://chaquo.com/maven" } } dependencies { ... classpath "com.chaquo.python:gradle:6.2.1" } }
Then, in the module-level build.gradle
file (usually in the app
directory), apply the
Chaquopy plugin at the top of the file, but after the Android plugin:
apply plugin: 'com.android.application'
apply plugin: 'com.chaquo.python' // Add this line
All other configuration will be done in this module-level build.gradle
. The examples below
will show the configuration within defaultConfig
, but it can also be done within a product
flavor.
ABI selection¶
The Python interpreter is a native component, so you must use the abiFilters setting to specify which ABIs you want the app to support. The currently available ABIs are:
armeabi-v7a
, supported by virtually all Android devices.arm64-v8a
, supported by most recent Android devices.x86
, for the Android emulator.x86_64
, for the Android emulator.
During development you will probably want to enable ABIs for both the emulator and your devices, e.g.:
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
There’s no need to actually install the Android native development kit (NDK), as Chaquopy will download pre-compiled CPython binaries for the selected ABIs.
Note
Each ABI will add several MB to the size of the app, plus the size of any native requirements. Because of the way the native components are packaged, the split APK and app bundle features cannot currently mitigate this. Instead, if your multi-ABI APKs are too large, try using a product flavor dimension:
android {
flavorDimensions "abi"
productFlavors {
arm {
dimension "abi"
ndk { abiFilters "armeabi-v7a" }
}
x86 {
dimension "abi"
ndk { abiFilters "x86" }
}
}
}
Development¶
Some features require Python 3.4 or later to be available on the build machine. By default,
Chaquopy will execute python3
on Linux and Mac, or py -3
on Windows, so if you have a
standard version of Python installed, no special setup is required.
Otherwise, set the Python executable using the buildPython
setting. For example, on Windows
you might use the following:
defaultConfig {
python {
buildPython "C:/Python36/python.exe"
}
}
Source code¶
By default, Chaquopy will look for Python source code in the python
subdirectory of each
source set. For example,
the Python code for the main
source set should go in src/main/python
.
To add or change source directories, use the android.sourceSets block. For example:
android {
sourceSets {
main {
python {
srcDirs = ["replacement/dir"]
srcDir "additional/dir"
}
}
}
}
Note
The setRoot method only takes effect on the standard Android directories. If you want to set the Python directory as well, you must do so explicitly, e.g.:
main {
setRoot "some/other/main"
python.srcDirs = ["some/other/main/python"]
}
As with Java, it is
usually an error if the source directories for a given build variant include multiple copies of
the same filename. This is only permitted if the duplicate files are all empty, such as may
happen with __init__.py
.
Startup¶
It’s important to structure the app so that Python.start() is always called with an AndroidPlatform before attempting to run Python code. There are two basic ways to achieve this:
If the app always uses Python, then call Python.start() from a location which is guaranteed to run exactly once per process, such as Application.onCreate(). A PyApplication subclass is provided to make this easy: simply add the following attribute to the
<application>
element inAndroidManifest.xml
:android:name="com.chaquo.python.android.PyApplication"
You can also use your own subclass of
PyApplication
here.Alternatively, if the app only sometimes uses Python, then call Python.start() after first checking whether it’s already been started:
// "context" must be an Activity, Service or Application object from your app. if (! Python.isStarted()) { Python.start(new AndroidPlatform(context)); }
Requirements¶
Note
This feature requires Python on the build machine, which can be configured with the buildPython setting.
External Python packages may be built into the app by adding a python.pip
block to
build.gradle
. Within this block, add install
lines, each specifying a package in one of the
following forms:
- A pip requirement specifier.
- A local sdist or wheel filename (relative to the project directory).
"-r"
followed by a local requirements filename (relative to the project directory).
Examples:
defaultConfig {
python {
pip {
install "six==1.10.0"
install "scipy==1.0.1"
install "LocalPackage-1.2.3-py2.py3-none-any.whl"
install "-r", "requirements.txt"
}
}
}
In our most recent tests, Chaquopy could install about 88% of the top 1000 packages on PyPI. This includes almost all pure-Python packages, plus a constantly-growing selection of packages with native components. To see which native packages and versions are currently available, you can browse the repository here. To request a package to be added or updated, or for any other problem with installing requirements, please visit our issue tracker.
To pass options to pip install
, give them as a comma-separated list to the options
setting.
For example:
pip {
options "--extra-index-url", "https://example.com/private/repository"
install "PrivatePackage==1.2.3"
}
Any options in the pip documentation may be used, except for those
which relate to the target environment, such as --target
, --user
or -e
. If there are
multiple options
lines, they will be combined in the order given.
Static proxy generator¶
Note
This feature requires Python on the build machine, which can be configured with the buildPython setting.
In order for a Python class to extend a Java class, or to be referenced by name in Java code or
in AndroidManifest.xml
, a Java proxy class must be generated for it. The staticProxy
setting specifies which Python modules to search for these classes:
defaultConfig {
python {
staticProxy "module.one", "module.two"
}
}
The app’s source tree and its requirements will be searched, in that order, for the specified modules. Either
simple modules (e.g. module/one.py
) or packages (e.g. module/one/__init__.py
) may be found.
Within the modules, static proxy classes must be declared using the syntax described in the static proxy section. For all declarations found, Java proxy classes will be generated and built into the app.
Packaging¶
Bytecode compilation¶
Your app will start up faster if its Python code is compiled to .pyc
format. This is
currently only supported for the Python standard library, but may be extended to app code and
pip-installed packages in a future version.
Compilation prevents source code text from appearing in Python stack traces, so you may wish to disable it during development. The default settings are as follows:
defaultConfig {
python {
pyc {
stdlib true
}
}
}
Resource files¶
By default, Python modules are loaded directly from the APK assets at runtime and don’t exist
as separate files. Because of this, any code which depends upon __file__
to locate
resource files will fail. There are two ways of dealing with this.
The most efficient way is to change the code to use pkgutil.get_data
instead. For
example, to read package1/subdir/README.txt
:
from pkgutil import get_data
# From any Python file directly within package1/:
readme_bytes = get_data(__name__, "subdir/README.txt")
# Or from elsewhere:
import package1
readme_bytes = get_data(package1.__name__, "subdir/README.txt")
# Then, to open it like a file:
import io
readme_file = io.StringIO(readme_bytes.decode())
Alternatively, you can specify certain Python packages to extract at runtime using the
extractPackages
setting. For example:
defaultConfig {
python {
extractPackages "package1"
}
}
Then you can use __file__
in the normal way:
from os.path import dirname, join
# From any Python file directly within package1/:
readme_file = open(join(dirname(__file__), "subdir/README.txt"))
# Or from elsewhere:
import package1
readme_file = open(join(dirname(package1.__file__), "subdir/README.txt"))
Extracted packages will load slower and use more storage space, so you should extract the
deepest possible package which contains both the module on which __file__
is looked up, and
the files being loaded.
extractPackages
is used by default for certain PyPI packages which are known to require it.
If you discover any more, please let us know.
Python standard library¶
ssl¶
For consistency across different devices, the ssl
module is configured to use a copy of the CA
bundle from certifi. The current version is
from certifi 2019.3.9.
sys¶
stdout
and stderr
are redirected to Logcat with the tags python.stdout
and
python.stderr
respectively. The streams will produce one log line for each call to write()
,
which may result in lines being split up in the log. Lines may also be split if they exceed the
Logcat message length limit of approximately 4000 bytes.
stdin
always returns EOF. If you want to run some code which takes interactive text input, you
may find the console app template useful.
Android Studio plugin¶
To add Python suppport to the Android Studio user interface, you may optionally install the JetBrains Python plugin.
Note
Chaquopy is not fully integrated with this plugin. It will show numerous “unresolved reference” warnings, and it will not support Python debugging. We hope to improve this in a future version.
- In Android Studio, select File > Settings.
- Go to the Plugins page, and click “Install JetBrains plugin”.
- Select “Python Community Edition”, and click “Install”.
- Restart Android Studio when prompted.