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-level build.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.

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 in AndroidManifest.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:

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.