What actually happens when my code calls Android APIs? We all know about onCreate, but who actually calls my method?

In this talk, we’re going to go behind the curtain and look at AOSP itself. I’ll dig into the internals of how Android OS manages the apps that we write and see:

How Intents work and how they get resolved.

How our app works together with other Intents, Activities, Services, and Content Providers in features such as sharing.

How the app lifecycle is managed by the system.

How these features are built on top of the built-in Linux primitives such as processes and Linux user accounts.

Introduction

I’m Effie, I work for Pinterest, and I want to talk to you about Android Internals for Developers. In particular, I want to give an overview of the Android operating system for developers who are unfamiliar with it or don’t know what happens with our apps.

People often note that Android is based on Linux. Linux is an open-source operating system that is made for all sorts of computers, and it has many distributions. But does it mean that Android also a Linux distribution?

The answer is no. Android is based on the Linux kernel but it also made up of modifications and greatly deviates from Linux. It’s not a Linux distribution, and it’s only based on it on a very small subsection.

Below is a graphic that depicts basic Android architecture:

I’m going to start with the bottom with the kernel.

Kernel

What is a kernel? A kernel is a computer program that is the core of a computer’s operating system. It has complete control of everything inside of the system. It’s the first program to be loaded on startup and it handles the rest of the startup process. It handles the CPU, memory, manages input-output devices, and communicates with other processes in the operating system through system calls. System calls occur when other processes want to do something that is hardware related.

Get more development news like this

The modifications on Android from the original kernel came from the fact that Android runs on phones, which have limited memory, and is not always connected to a power source.

Out of memory killer: this was created to help kill apps that users leave running. Because Android also run on low-end devices, it must make the effort clean up unused apps before it runs out of memory to a catastrophic level.

Wakelocks: Allows Android to sleep, and consume as little energy as possible.

Init

The next step, in our list of architecture is the init. The init process is a binary that’s picked up by the kernel run on startup. It is also the root of all other processes, which will be created from this process, spawning everything else.

Another way of looking at it is a list of instructions and settings about what other processes to wake up, and how they need to be configured. Most of the things that init will start up are called daemons.

Daemons are processes that will run in the background. Some that might look familiar to you are adbd or installd that’s being used to install apps.

Zygote

The Zygote is a special process; a special daemon. It’s the base of all other Java or Kotlin based applications. It contains a readily available runtime environment/virtual machine that’s ready to run the app.

When we write Java, it is precompiled, and it’s changed and managed by someone before running on an environment. We know this as Dalvik, and this environment has things like garbage collection, memory allocations, heap, stack, etc. All these things exist within Zygote.

Dalvik is helpful on Android because we do not have to pre-initialize anything, and as a result, saves us start-up time.

Processes and UIDs

In Linux, apps run under the same user. Apps that run that way on Linux can share common resources; they can share memory and files, but Android decided against this. Instead, a sandbox is created for each process and app running on the phone.

This is accomplished by pretending that every app is a different user, as such, they get their own user ID (UID). They cannot corrupt each other’s memory, and they cannot write into each other’s files. This makes Android very secure.

When apps need to be able to interact with each other, they use Binder.

Binder

Binder allows inter-app communication in a safe way.

Considering the following ConnectivityManager code:

ConnectivityManager connectivityManager = ( ConnectivityManager ) MyApplication . getInstance (). getSystemService ( Context . CONNECTIVITY_SERVICE ); NetworkInfo info = connectivityManager . getActiveNetworkInfo ();

The result of the network information is coming from the system service. You may wonder why the call cannot simply be in the app.

The reason is that it’s doing complex things such as asking the Wi-Fi driver or asking other hardware components about their situation. If this was to be handled internally, it would lack many of the permissions, so putting this in a centralized service makes sense.

Suppose I encounter a bug, and want to see the source code of the above method:

public class ConnectivityManager { private final IConnectivity Manager mService ; ... public NetworkInfo getActiveNetworkInfo () { try { return mService . getActiveNetworkInfo (); } catch ( RemoteException e ) { throw e . rethrowFromSystemServer (); } } ... }

This is merely a wrapper around an interface, so digging deeper:

interface IConnectivityManager { Network getActiveNetwork (); Network getActiveNetworkForUid ( int uid , boolean ignoreBlocked ); NetworkInfo getActiveNetworkInfo (); NetworkInfo getActiveNetworkInfoForUid ( int uid , boolean ignoreBlocked ); NetworkInfo getNetworkInfo ( int networkType ); NetworkInfo getNetworkInfoForUid ( in Network network , int uid , boolean ignoreBlocked ); NetworkInfo [] getAllNetorkInfo (); Network getNetworkForType ( int networkType ); Network [] getAllNetworks (); NetworkCapabilities [] getDefaultNetworkCapabilitiesForUser ( int userId ); }

We find an interface - it’s not actually not speaking directly to the activity manager, but going through a Binder.

Our app is not directly talking to the other app. Instead, it’s going through a journey down to the kernel, then up again to send our message with the result.

The Binder Java class has two main methods:

public boolean transact ( int code , Parcel data , Parcel reply , int flags ) protected boolean onTransact ( int code , Parcel data , Parcel reply , int flags ) throws RemoteException

The sender will call transact and the recipient will get onTransact , which passes a code, a Parcel, which is all the data, and maybe the method arguments that we wanted to send.

On the recipient side, we open these up to see what we got and figure out what we want to do with it.

The AIDL tool comes in and it serves as the bridge between the actual method and a transaction and it generates. It auto-generates a lot of code. One is the proxy which is the sender and one is the stub which is the recipient. This is an example of how the proxy would look.

@Override public android . net . Network getActiveNetwork () throws android . os . RemoteException { android . os . Parcel _data = android . os . Parcel . obtain (); android . os . Parcel _reply = android . os . Parcel . obtain (); android . net . Network _result ; try { _data . writeInterfaceToken ( DESCRIPTOR ); mRemote . transact ( Stub . TRANSACTION_getActiveNetwork , _data , _reply , 0 ); _reply . readException (); if (( 0 != _reply . readInt ())) { _result = android . net . Network . CREATOR . createFromParcel ( _reply ); } else { _result = null ; } } finally { _reply . recycle (); _data . recycle (); } return _result ; }

In the middle of there is the transact method which is the actual Binder. This is the real implementation:

System Server

The system server is the heart of Android, and it’s responsible for all things Android. It’s one process, and it’s written in Java. It tells Zygote, to fork, create the system server, and run the Java code on a new instance.

It has three managers:

Activity Manager

Window Manager

Package Manager

The window manager is responsible for windowing and being in charge of all things windows. It keeps tracks of window activities and it keeps track of what is visible. It is also responsible for transitions between activities for an overall good UI experience.

The package manager is responsible for installing new applications. It’s responsible for resolving the correct activity when I send out an intent.

The activities manager is the heart of the heart of Android. It is responsible for everything: all the activities that we create along with the services, and content providers are managed by the activities manager.

Processes and Activities

Every app sits in a different process, but all activities that are going to be created for that app is created in the same process. For a simple example, we have activity manager monitoring one app so it has one task and it keeps records of all the activities that it has. The activity record shows what state they are, in this case, resumed because it’s the top activity.

If I add another activity from it, say navigate in my app to a secondary activity, another record would be added to the same stack, and another activity would be added to the same process and the states have changed because there is a secondary app activity resumed and the one below it is no longer active.

The main function of the activity manager service is to manage the lifecycle of all our apps. The one top rule that they have could only be one resumed app up at all times.

The activity manager is also responsible for telling the window manager to create a surface for an app or remove a surface for an activity.

In activity manager service, there are two methods that are applied: adjustLocked and computed . Those are the methods that are going to be get called whenever anything changes, and whenever any activity gets created, resumes, or stops.

Questions

When you call something like getNetwork , where does that get excecuted?

It gets executed in the system service, which is a different process.