What differs Android from other Linux based systems?


This article is an introduction to embedded Android from the perspective of an embedded Linux developer.

If you are an embedded Linux developer but have never worked with embedded Android, this article is for you. If you develop or maintain Linux distros, and I want to start developing Android distros, this article is for you. If you are an Android application developer and want to understand a bit of how Android (internally) works, this article is also for you. And if you are just curious, this article is also for you! :-)

We will compare a GNU/Linux system with Android in different perspectives, from the build system to the partition layout, from the content of the rootfs to the architecture of the operating system components, from the IPC/RPC mechanism to how hardware access works. I hope you have fun!

Now, if you prefer a one hour talk instead of reading this article, you can also watch the talk “What Differs the Android Open Source Project from Other Linux Distributions?” presented at Embedded Linux Conference Europe 2020 (YouTube link).

Let’s start with a high-level overview…

The difference in a nutshell

If you look at the architecture of any embedded Linux system, you will always find the same main components:

  1. One or more bootloader(s) to bootstrap, configure the hardware and boot the Linux kernel.
  2. The Linux kernel.
  3. The root filesystem (rootfs) with the libraries and applications.

Embedded Linux Architecture

Now, if you look at the same diagram for an Android-based system, what would be the differences?

  1. The bootloader is there. Android doesn’t require anything special in the bootloader, although it is common for people to add support to fastboot, a protocol Google created for users and developers to interact with bootloaders on Android-based systems.
  2. The Linux kernel is also there, but with a “few” changes. More on that later.
  3. The root filesystem is also there. But it is REALLY different! Nothing like a typical root filesystem from Debian or a custom-built rootfs from Buildroot or OpenEmbedded.

Android Architecture

As we can see from the image above, Android userspace components are clearly divided in three main layers:

  1. Native: this is where all native (C/C++) applications and libraries resides. It is called native because it runs outside the ART Virtual Machine. The native layer basically serves the purpose of abstracting Linux kernel interfaces to the framework layer.
  2. Framework: this is where all operating system services are implemented (mostly in Java). Access to operating system resources are (remotely) exposed via components called system services, using an IPC/RPC mechanism called Binder. And an API will abstract the access to those system services for the applications.
  3. Application: Usually written in Java or Kotlin, they just see the exposed operating system API.

Before studying Android userspace components in details, let’s talk a little bit about the kernel.

Linux kernel

To run an Android-based system, we need a few extra “features” enabled in the Linux kernel. This article would be too long if I would try to go over most features, but I can comment on some:

  • Binder: this is the IPC (Inter-Process Communication) and RPC (Remote Procedure Call) mechanism used in Android. You could do a rough comparition with DBUS, but DBUS runs in userspace, and Binder is a faster and lighter kernel-based implementation.
  • Ashmem: the default shared memory allocator in Android (Google doesn’t like POSIX SHM).
  • Low memory killer: a logic built on top of the kernel’s OOM (Out-of-Memory) killer, that in conjunction with a daemon (lmkd), helps to manage the system in low memory situations.

Most of those features are already available in the mainline kernel, but if you want the full list of “Androidisms”, you can clone Google’s kernel-common repository and search for commits starting with “ANDROID:”:

$ git clone https://android.googlesource.com/kernel/common kernel-common
$ git checkout remotes/origin/android11-5.4
$ git log --oneline | grep "ANDROID:" | less
5427f8b72fc0 ANDROID: GKI: update xiaomi symbol list
ecb88922f521 ANDROID: GKI: update Vivo symbol list
32b242337266 ANDROID: sysrq: add vendor hook for sysrq crash information
42e516f6b23b ANDROID: ABI: update allowed list for galaxy
de198b0f2d39 ANDROID: GKI: update Vivo symbol list

$ git log --oneline | grep "ANDROID:" | wc -l
1157

Despite these (and a few other) main changes in the Linux kernel, Android really differs from an GNU/Linux system in the userspace components and their architecture (the so-called Android platform), and the source-code for the Android platform is provided in a project called AOSP (Android Open Source Project).

AOSP

The AOSP is made of hundreds of repositories (specifically 780 in Android 11), and you can see all of them in https://android.googlesource.com/.

The source code is managed with known tools like repo and git, and it is huge! Android 11 is 100GB of source code plus 115GB after one build. So you really need a lot of disk space to work with the Android source code.

Cloning the latest AOSP source code is as simple as running the two commands below (-b can be used in repo init to clone an specific Android release tag or branch):

$ repo init -u https://android.googlesource.com/platform/manifest
$ repo sync

After a few hours, you will have the Android source code there in your machine:

$ ls
Android.bp      dalvik       libcore           read-snapshot.txt
art             developers   libnativehelper   sdk
bionic          development  Makefile          system
bootable        device       out               test
bootstrap.bash  external     packages          toolchain
build           frameworks   pdk               tools
compatibility   hardware     platform_testing
cts             kernel       prebuilts

As an open source project, there are several discussion groups to communicate with the community and the developers, and anyone can contribute to the project via the Gerrit code review tool.

The vast majority of software components are under the permissive Apache and BSD licenses, some software components are under GPL/LGPL licenses, and some Google applications are closed source (e.g. Google Play, Gmail, Google Maps, YouTube, etc). Those applications are available in a package called Google Mobile Services (GMS), and to obtain them, you need to certify the device via Android Compatibility Program (ACP).

When we download AOSP source code, we can see some differences when compared to other approaches for embedded Linux development.

Unlike ready-to-be-used distros (e.g. Debian), you can easily download the full source code and build the distro from scratch (if you want to create a custom Debian system for example, you usually do it from pre-compiled packages).

Comparing to build system approaches (e.g. Buildroot, OpenEmbedded), in Android it seems we have a “big application” downloaded with the repo sync command. Of course this is not true. We have thousands of projects and repositories there, that will compose in the end the operating system images. And the responsibility to put everything together rests with the Android build system…

Android build system

In previous Android versions, the build system was purely based on makefiles, where instructions for compiling each software component were defined in Android.mk files. Here is just an example of an Android.mk file to build a “Hello World” C application in Android:

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES = helloworld.c
LOCAL_MODULE = helloworld
LOCAL_MODULE_TAGS = optional

include $(BUILD_EXECUTABLE)

The Makefile-based build system had several shortcomings, including low performance in incremental builds, and was replaced in the latest versions of Android by the Soong build system.

In the Soong build system, the rules for compiling software components are defined in Blueprint files (Android.bp), which have a syntax similar to JSON. Here is the same example of building a “Hello World” C application in Android, but using a Blueprint file:

cc_binary {
    name: "helloworld",
    srcs: ["helloworld.c"],
    tags: ["optional"],
}

Blueprint files are processed by a tool called Blueprint, which produces .ninja files with all the rules for compiling Android software components. The .ninja files are then processed by a tool called Ninja, which will compile all the software components and generate the Android images (more on the images later).

As of this writing, not all makefiles (Android.mk) were converted to Blueprint files (Android.bp). For this reason, there is a tool called kati, responsible for converting Android.mk files to .ninja files. Over time, all Android.mk files should gradually be converted to Android.bp, eliminating the need to use the kati tool in the Android build system.

The following diagram is a summary of the Android build process:

Android Build System

Building Android is very simple. You can do it with three simple commands: one to source a script that will initialize the environment, the second to select the target device (product-variant), and the last to start the build. The commands below will build Android for the emulator:

$ source build/envsetup.sh
$ lunch aosp_x86_64-eng
$ make

After a few hours, we have the images in out/target/product/:

$ cd out/target/product/generic_x86_64/ && ls *.img
cache.img          super_empty.img    vbmeta.img
dtb.img            super.img          vendor_boot-debug.img
encryptionkey.img  system.img         vendor_boot.img
ramdisk-debug.img  system-qemu.img    vendor.img
ramdisk.img        userdata.img       vendor-qemu.img
ramdisk-qemu.img   userdata-qemu.img

Now, what are all those images? How is the rootfs organized in Android?

Rootfs organization and partition layout

The rootfs organization on Linux systems is (mostly) standardized, basically defined by two standards: Filesystem Hierarchy Standard and Linux Standard Base.

Linux distributions try to conform to these standards, making the applications easily portable, and simplifying the life of users and developers when they need to work with different Linux systems.

But as you would expect, Android is an exception!

This is the listing of the root partition of an Android system (Android 11, built for QEMU). May I ask you where is /sbin, /usr, /lib and other common Linux system directories?

# ls /
acct        d              etc              mnt      sdcard
apex        data           init             odm      storage
bin         data_mirror    init.environ.rc  oem      sys
bugreports  debug_ramdisk  linkerconfig     proc     system
cache       default.prop   lost+found       product  system_ext
config      dev            metadata         res      vendor

They are not there! In Android, operating system components (applications, libraries) are located in the /system directory (mount point for the system partition), and user data/configuration (including applications installed at runtime) is located in the /data directory (mount point for the data partition). And there are many other partitions like cache (downloaded files and temporary data), vendor (specific files from the SoC manufacturer) and odm (specific files from the device manufacturer).

This is a basic overview of the partition layout on Android 11 (may vary depending on the manufacturer/device):

Android Partition Layout

Very different from a typical embedded Linux system, right?

So now, why don’t we talk a little bit about the contents of the filesystem? Because they also differs, a lot. Let’s start with the C library.

Android C library

One of the main components of an operating system based on the Linux kernel is the C library.

The C library implements the operating system’s API, providing an interface for applications to access kernel services through system calls.

Several C libraries are available for Linux systems, including glibc, uclibc-ng and musl. But Android has its own C library: Bionic!

I can imagine at least three reasons that may have motivated Google to implement its own C library: license, speed and size. The implementation is really simple, lightweight and released under the BSD license.

One important thing to mention is that it doesn’t have full POSIX support, which can make it more difficult to build native Linux applications for Android.

See for example the code snippet below from libbb/missing_syscalls.c in BusyBox. The #if defined(ANDROID) was required since some Bionic functions do not follow the POSIX standard.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#if defined(ANDROID) || defined(__ANDROID__)
/*# include <linux/timex.h> - for struct timex, but may collide with <time.h> */
# include <sys/syscall.h>
pid_t getsid(pid_t pid)
{
	return syscall(__NR_getsid, pid);
}

int sethostname(const char *name, size_t len)
{
	return syscall(__NR_sethostname, name, len);
}

struct timex;
int adjtimex(struct timex *buf)
{
	return syscall(__NR_adjtimex, buf);
}

int pivot_root(const char *new_root, const char *put_old)
{
	return syscall(__NR_pivot_root, new_root, put_old);
}

And talking about BusyBox…

Why doesn’t Android use BusyBox?

It is very common to use BusyBox on embedded Linux devices.

Busybox provides the (re)implementation of common tools and applications such as an init program, a shell and several utilities to manipulate and configure the system.

But Android does not ship with BusyBox by default!

Android uses two other (conceptually similar) implementations called Toolbox and Toybox, both released under a BSD license. Toolbox is a tool implemented by Google and Toybox is a tool implemented by the community (started by Rob Landley, BusyBox ex-maintainer).

And because these tools have some limitations, it is common to install BusyBox on an Android device, especially during development. One motivation would be to have a good text editor (vi) available in the command line.

Now, an essential part of a Linux-based operating system is the init system. And how does the initialization work in Android?

Android init system

In a nutshell, the init application is executed by the kernel right after mounting the rootfs, being responsible for system initialization and management.

There are several implementations of the init process for Linux systems, including sysvinit, systemd and upstart. And as you may already expect, Android has its own init system!

The Android init process has 3 primary responsibilities:

  1. Initialize and configure the operating system execution environment (export environment variables, create and set permissions on files and directories, create links, mount file systems, setup selinux, etc).
  2. Start and monitor daemons.
  3. Manage system properties.

The behavior of the init process is defined in a configuration file (/etc/init/hw/init.rc by default), and it’s very different from any init configuration file we are used to. Here is a snippet from the default Android init.rc file:

import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc

# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0

    # Android doesn't need kernel module autoloading, and it causes SELinux
    # denials.  So disable it by setting modprobe to the empty string.  Note: to
    # explicitly set a sysctl to an empty string, a trailing newline is needed.
    write /proc/sys/kernel/modprobe \n

    ...

on init
    sysclktz 0

    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /system/etc/prop.default /dev/urandom

    symlink /proc/self/fd/0 /dev/stdin
    symlink /proc/self/fd/1 /dev/stdout
    symlink /proc/self/fd/2 /dev/stderr

    ...

# Mount filesystems and start core system services.
on late-init
    trigger early-fs

    # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter
    # '--early' can be specified to skip entries with 'latemount'.
    # /system and /vendor must be mounted by the end of the fs stage,
    # while /data is optional.
    trigger fs

on property:ro.debuggable=1
    # Give writes to anyone for the trace folder on debug builds.
    # The folder is used to store method traces.
    chmod 0773 /data/misc/trace
    # Give reads to anyone for the window trace folder on debug builds.
    chmod 0775 /data/misc/wmtrace

service ueventd /system/bin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0
    shutdown critical

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group shell log readproc
    seclabel u:r:shell:s0
    setenv HOSTNAME console

...

It is indeed very different. You have declarations of actions (e.g. on init) and declarations of services (e.g. service console /system/bin/sh). When an action is triggered at boot time, let’s say early-init, the commands declared in that trigger will be executed.

There are a lot of details here, but we don’t have much space in this article to talk about it (we could actually have a full article on this). For now, let’s focus on an important piece of this puzzle, the daemons!

Android daemons

Daemons are processes that run in the background and are responsible for managing some system functionality. Most of them are executed at startup by the init process, and usually they run in the background for as long as the system is functional.

Daemons are normally used to control and centralize access to a system resource, and on Android, many daemons are an interface between the Android framework (Java code) and system resources (network, storage, energy, radio, logging, etc). Some examples:

  • ueventd: responsible for managing the connection of hardware devices (device hotplugging). It is the equivalent of udev or mdev on GNU/Linux systems.
  • vold: (volume daemon) is responsible for monitoring events from storage devices.
  • rild: (radio interface layer daemon) manages communication with the modem chip (voice and data).
  • netd: (network management service daemon) is responsible for managing network connections (Bluetooth, Wi-Fi, USB, etc). It is the equivalent of NetworkManager or connman on GNU/Linux systems.
  • installd: (install daemon) is responsible for managing the installation of Android applications (* .apk) and their associated resources.
  • lmkd: (low memory killer daemon) is responsible for managing the kernel low memory killer interface.

Here is a (mostly) complete list of daemons running on Android 11:

# ps -A
USER            PID   PPID     VSZ    RSS WCHAN            ADDR S NAME                       
root              1      0 10782796  9696 do_epoll_+          0 S init
root            122      1 10761204  7376 do_sys_po+          0 S ueventd
logd            145      1 10764228  7932 __x64_sys+          0 S logd
lmkd            146      1 10756496  2456 do_epoll_+          0 S lmkd
system          147      1 10759476  5016 do_epoll_+          0 S servicemanager
system          148      1 10761244  6488 do_epoll_+          0 S hwservicemanager
system          149      1 10759572  4028 do_epoll_+          0 S vndservicemanager
root            153      1 10770096  8732 binder_th+          0 S vold
tombstoned      250      1 10755388  2128 do_epoll_+          0 S tombstoned
statsd          266      1 10766140  4572 do_epoll_+          0 S statsd
root            267      1 10781776  9532 binder_th+          0 S netd
credstore       306      1 10764440  7296 binder_th+          0 S credstore
gpu_service     307      1 10762672  6804 binder_th+          0 S gpuservice
system          308      1 10873496 31972 do_epoll_+          0 S surfaceflinger
root            316      1 10756876  2656 do_sys_po+          0 S netmgr
root            318      1 10758880  3072 do_sys_po+          0 S wifi_forwarder
wifi            320      1 10759960  5464 do_select           0 S hostapd_nohidl
logd            326      1 10756544  3160 __skb_wai+          0 S logcat
root            352      1 10773084  6376 0                   0 S adbd
nobody          354      1 10757496  3164 do_sys_po+          0 S traced_probes
nobody          355      1 10757632  3464 do_sys_po+          0 S traced
cameraserver    356      1   58984  17240 binder_th+          0 S cameraserver
drm             357      1   25952   6512 binder_th+          0 S drmserver
incidentd       359      1 10761968  4992 do_epoll_+          0 S incidentd
root            360      1 10765704  6452 binder_th+          0 S installd
iorapd          361      1 10775424  9536 futex_wai+          0 S iorapd
keystore        362      1 10764916  7404 binder_th+          0 S keystore
root            366      1 10765596  5648 binder_th+          0 S storaged
...

Have you realized that almost all daemons are Android specific? Yes, they really are. Android is an operating system where almost all userspace components were built from scratch!

In particular, it is worth mentioning how logging works on Android, since it doesn’t use common logging Linux daemons like journald or rsyslog. So let’s talk about this now.

Android logging system

In Android, the log daemon (logd) is responsible for managing all operating system logs, from applications down to the framework and native applications.

Access to logs is done through sockets exported in /dev/socket/:

# ls /dev/socket/logd*
/dev/socket/logd  /dev/socket/logdr  /dev/socket/logdw

To read or write to the logs, it’s not necessary to directly access these sockets. For this, applications can use the liblog library. And in the terminal, the user can write to the logs with the log command, and read the logs using the logcat tool:

# logcat
...
10-14 13:36:51.722   771   934 D SmsNumberUtils: enter filterDestAddr. destAddr="[BajqU4K5_YhSYbs-7QUn0dOwcmI]"
10-14 13:36:51.723   771   934 D SmsNumberUtils: destAddr is not formatted.
10-14 13:36:51.723   771   934 D SmsNumberUtils: leave filterDestAddr, new destAddr="[BajqU4K5_YhSYbs-7QUn0dOwcmI]"
10-14 13:36:57.054   316   316 E netmgr  : qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:network' service: 
10-14 13:36:57.054   316   316 E netmgr  : Failed to open QEMU pipe 'qemud:network': Invalid argument
10-14 13:36:57.324   318   318 E wifi_forwarder: qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:wififorward' service: 
10-14 13:36:57.325   318   318 E wifi_forwarder: RemoteConnection failed to initialize: RemoteConnection failed to open pipe
...
10-14 14:37:45.408   494  1324 D WifiNl80211Manager: Scan result ready event
10-14 14:37:45.408   494  1324 D WifiNative: Scan result ready event
10-14 14:37:59.109   316   316 E netmgr  : qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:network' service: 
10-14 14:37:59.109   316   316 E netmgr  : Failed to open QEMU pipe 'qemud:network': Invalid argument
10-14 14:37:59.574   318   318 E wifi_forwarder: qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:wififorward' service: 
10-14 14:37:59.575   318   318 E wifi_forwarder: RemoteConnection failed to initialize: RemoteConnection failed to open pipe
10-14 14:38:00.003   642   642 D KeyguardClockSwitch: Updating clock: 238
10-14 14:38:59.127   316   316 E netmgr  : qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:network' service: 
10-14 14:38:59.127   316   316 E netmgr  : Failed to open QEMU pipe 'qemud:network': Invalid argument
10-14 14:38:59.585   318   318 E wifi_forwarder: qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:wififorward' service: 
10-14 14:38:59.585   318   318 E wifi_forwarder: RemoteConnection failed to initialize: RemoteConnection failed to open pipe
10-14 14:39:00.003   642   642 D KeyguardClockSwitch: Updating clock: 239
10-14 14:39:59.142   316   316 E netmgr  : qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:network' service: 
10-14 14:39:59.142   316   316 E netmgr  : Failed to open QEMU pipe 'qemud:network': Invalid argument
10-14 14:39:59.634   318   318 E wifi_forwarder: qemu_pipe_open_ns:62: Could not connect to the 'pipe:qemud:wififorward' service: 
10-14 14:39:59.634   318   318 E wifi_forwarder: RemoteConnection failed to initialize: RemoteConnection failed to open pipe
10-14 14:40:00.006   642   642 D KeyguardClockSwitch: Updating clock: 240
...

After several years working with Android, I can say that the Android logging system is pretty decent and very useful when developing in the platform.

And talking about development, when porting Android to an embedded device, writing code that will talk to the hardware is an important part of the process. So why don’t we talk now about how hardware access works in Android?

Hardware access and Android HAL

On an embedded Linux system, access to hardware devices is usually exposed to applications via entries in /dev or /sys. But on Android, we rely on an additional layer called HAL (Hardware Abstraction Layer) to abstract access to hardware devices.

Android HAL

The main idea is to decouple the system services (more on it later) from the interfaces exposed by the Linux kernel, so if a kernel interface change, you have just to replace the HAL, and the system services will keep working. Most HALs are basically services running as a separated process that exposes an interface (declared in a language called HIDL) consumed via Binder. There are HAL definitions and implementations for most supported hardware devices in Android like displays, cameras, audio, sensors, etc.

But to completely understand the picture above, we have to talk about the Android framework and the system services.

Android framework and system services

So the Android framework is where all Java (and also now Kotlin) code are. We have there the system services and the APIs exposed to applications.

The system services in particular are a very important part of the Android operating system. They are basically objects that expose an interface (via Binder) to be consumed by other system services and applications (via API).

On an Android terminal, you can list the system services with the command below:

# service list
Found 184 services:
0	DockObserver: []
1	SurfaceFlinger: [android.ui.ISurfaceComposer]
2	accessibility: [android.view.accessibility.IAccessibilityManager]
3	account: [android.accounts.IAccountManager]
4	activity: [android.app.IActivityManager]
5	activity_task: [android.app.IActivityTaskManager]
6	adb: [android.debug.IAdbManager]
7	alarm: [android.app.IAlarmManager]
8	android.hardware.identity.IIdentityCredentialStore/default: [android.hardware.identity.IIdentityCredentialStore]
9	android.hardware.light.ILights/default: [android.hardware.light.ILights]
10	android.hardware.power.IPower/default: [android.hardware.power.IPower]
11	android.hardware.rebootescrow.IRebootEscrow/default: [android.hardware.rebootescrow.IRebootEscrow]
12	android.hardware.vibrator.IVibrator/default: [android.hardware.vibrator.IVibrator]
13	android.security.identity: [android.security.identity.ICredentialStoreFactory]
14	android.security.keystore: [android.security.keystore.IKeystoreService]
15	android.service.gatekeeper.IGateKeeperService: [android.service.gatekeeper.IGateKeeperService]
16	app_binding: []
17	app_integrity: [android.content.integrity.IAppIntegrityManager]
18	appops: [com.android.internal.app.IAppOpsService]
19	appwidget: [com.android.internal.appwidget.IAppWidgetService]
20	audio: [android.media.IAudioService]
21	auth: [android.hardware.biometrics.IAuthService]
...

Let’s say you are writing an Android application to read a sensor. This is what is going to happen:

  1. The application will call methods from the Android API (SensorManager) to request data from the sensor.
  2. The API (SensorManager) will send a message (via Binder) to a system service (in this case SensorService).
  3. The system service is responsible for managing the access to the resource it controls. In this example, the SensorService manages the access to sensors. And the first thing it will do is check for permissions: does the application has permission to access the sensors? If not, it will return an exception to the application. If access is granted, it will send a message via Binder to the HAL (Sensors HAL) to request data from the sensor.
  4. The Sensors HAL will read the sensor, probably via files exposed by a kernel IIO driver, and return the data to the system service (SensorService).
  5. The system service (SensorService) will return sensor data to the application.

Here is another diagram explaining how those components communicate with each other:

Android System Services

Now you see how Android really differs from a typical Linux system?

And we still need to talk about applications…

Android applications

One of the advantages of using Android on embedded devices is the well-defined set of APIs, which simplifies the development a lot and improves productivity significantly.

In Android, applications are written in Java or Kotlin using the Google SDK, and packaged in files with the .apk extension, which contains the compiled code, data and resources used by the application.

Android applications are basically composed of 4 types of components: activities, services, broadcast receivers and content providers. An application can contain one or more components, and one nice thing is that components can be reused and communicate with each other via a mechanism called intent.

In the end, even if we want to work with Android but don’t care about the operating system internal architecture, we still need to learn the Android paradigm, that again, differs a lot from usual Linux application development, let’s say in Qt or GTK.

Let’s wrap up now?

Is Android a Linux distribution or not?

We could see in this article that Android really differs from a typical GNU/Linux system, from the way we manage the source code and build the system, to the organization and components of the filesystem, and the modular architecture where all components communicate via IPC/RPC.

The only similarity we can clearly see is the usage of the Linux kernel. Almost everything else is different in Android.

So is Android a Linux distribution?

It depends on how you “classify” a Linux distribution. If a Linux distribution is any system that uses the Linux kernel, then we can probably say that Android is a Linux distribution. If a Linux distribution is a system that follows the same standards and shares some common components, like those provided by the GNU project, then Android is definitely not a Linux distribution.

It is important to mention that Android is evolving a lot over time. One of the main challenges that Google faces is software updates. They cannot update any device out there because it is not in their control.

Apple manufactures the SoC, the device itself (of course!) and the software, controlling almost every aspect of the supply chain. So they can update all devices in the field (at any time) if they want.

Now Google doesn’t have this ability. With the exception of Pixel devices, they don’t have much control over devices manufactured by other companies like Samsung or Xiaomi.

But they want this kind of control. And that is why they created projects to improve this situation, like Project Trebble, Generic System Images and Generic Kernel Image.

Many of the architectural designs we saw in this article came from those projects, and probably there will be more to come. If it’s for the better, only the users and the market will tell.

About the author: Sergio Prado has been working with embedded systems for more than 25 years. If you want to know more about his work, please visit the About Me page or Embedded Labworks website.

Please email your comments or questions to hello at sergioprado.blog, or sign up the newsletter to receive updates.