Tuesday, April 20, 2010

Android Application Launch Part 2

There are three distinct phases of process launch :
  1. Process Creation
  2. Binding Application
  3. Launching Activity / Starting Service / Invoking intent receiver ...
Process Creation -

ActivityManagerService creates a new process by invoking startProcessLocked() method which sends arguments to Zygote process over the socket connection. Zygote forks itself and calls ZygoteInit.main() which then instantiates ActivityThread object and finally returns pid of newly created process.
ActivityThread then starts the message loop by calling Looper.prepareLoop() and Looper.loop() subsequently.

The following sequence captures the call sequence in detail -

Application Binding -

The next step is to attach the process to the specific application. This is done by calling bindApplication on the thread object. This method sends BIND_APPLICATION message to the message queue. This message is retrieved by the handler object which then invokes handleMessage() method to trigger the message specific action - handleBindApplication(). This method invokes makeApplication() method which loads app specific classes into memory.

This call sequence is depicted in following figure.

Activity Launch -

After the previous step, the system contains the process responsible for the application with application classes loaded in process's private memory. The call sequence to launch an activity is common between a newly created process and an existing process. The actual process of launching starts in realStartActivity() method which calls sheduleLaunchActivity() on the application thread object. This method sends LAUNCH_ACTIVITY message to the message queue. The message is handled by handleLaunchActivity() method as shown below.
Assuming that user clicks on Video Browser application. the call sequence to launch the activity is as shown in the figure.

The Activity starts its managed lifecycle with onCreate() method call. The activity comes to foreground with onRestart() call and starts interacting with the user with onStart() call.

Android Application Launch

I was part of Android Performance team at Qualcomm and I was working on optimizing the application launch time on Android platform. The first step was to understand the app launch process in detail. Here are some results -

Android Applications are different than standard mobile applications in two major ways.
  1. Every Android application lives in its own world, meaning it runs in a separate process, has its own Dalvik VM instance and is assigned a unique user ID.
  2. Android apps are composed of different components and they can invoke the components owned by other apps. Typically, they don't have a single entry point like main() method.
Application components include :
  • Activities : Encapsulation of a particular operation, optionally associated with GUI, provide an execution context.
  • Services : Background tasks that run in the context of application process.
  • Broadcast Receivers : Broadcast Intent listeners
  • Content providers : Data storage and sharing interface of an app.
Android process is same as Linux process. By default, every installed .apk runs in its own Linux process. Also by default, there exists 1 thread per process. The main thread has a Looper instance to handle the messages from the message queue and it calls Looper.loop() in its every iteration of run() method. It's the job of a looper to pop off the messages from message queue and invoke the corresponding methods to handle it.

When does a process get started? The short version is a process get started whenever it is required. Any time a user or some other system component request the component (could be a service, an activity or an intent receiver) belonging to your apk be executed, the system spins off a new process for your apk if it's not already running. General processes remain running until killed by the system. The point is, processes are created on demand.
For example, if you click on a hyper-link in your e-mail, the web page opens in a browser window. Your mail client and the browser are two separate apps and they run in their two separate processes. The click event causes Android platform to launch a new process so that it can instantiate the browser activity in the context of its own process. The same holds good for any other component in an application.

Let's step back for a moment and have a quick look on the start-up process. Like the most Linux based systems, at startup, the bootloader loads the kernel and starts the init process. The init then spawns the low level Linux processes called "daemons" e.g. android debug daemon, USB daemon etc. These daemons typically handle the low level hardware interfaces including radio interface.

Init process then starts a very interesting process called 'Zygote'. As the name implies it's the very beginning for the rest of the Android platform. This is the process which initializes the very first instance of Dalvik virtual machine and pre-loads all the common classed used by the application framework and the various apps. Then it starts listening on a socket interface for future requests to spawn off new vms for managing new app processes. On receiving a new request, it forks itself to create a new process which gets a pre-initialized vm instance.
After zygote, init starts the runtime process. The zygote then forks to start a well managed process called system server. It starts all core platform services e.g activity manager service and hardware services in its own context. At this point the full stack is ready to launch the first app process - Home app which displays the home screen.

So many things happen behind the scene when a user clicks on an icon and a new application gets launched. Here is the full picture :

The click event gets translated into startActivity(intent) call which gets routed to startActivity(intent) call in ActivityManagerService through Binder IPC. The ActvityManagerService takes couple of actions -
  • The first step is to collect information about the target of the intent object. This is done by using resolveIntent() method on PackageManager object. PackageManager.MATCH_DEFAULT_ONLY and PackageManager.GET_SHARED_LIBRARY_FILES flags are used by default.
  • The target information is saved back into the intent object to avoid re-doing this step.
  • Next important step is to check if user has enough privileges to invoke the target component of the intent. This is done by calling grantUriPermissionLocked() method.
  • If user has enough permissions, ActivityManagerService checks if the target activity requires to be launched in a new task. The task creation depends on Intent flags such as FLAG_ACTIVITY_NEW_TASK and other flags such as FLAG_ACTIVITY_CLEAR_TOP.
  • Now, it's the time to check if the ProcessRecord already exists for the process.
If the ProcessRecord is null, the ActivityManager has to create a new process to instantiate the target component.

(Continued in part2...)

Monday, April 19, 2010

OSome Months ...

Days 3, 4 and 5 were equally eventful and I started the "OSome" week with great enthusiasm. I realized that recording daily events was soon going to be monotonous. It was like this :
while(1) {
on receiving INT_SLEEP : sleep_for_a_while();

Never thought that I would regret a single decision of my life for more than 2 semesters. For some stupid reason, I decided to keep my Spring 2009 semester as light as possible. 2nd mistake. I dropped 15-410 without attending even a single class. 3rd mistake. I hardly spent time at INI during my first semester. 1st mistake. So I hardly knew anyone and I thought I would never get a partner for OS projects. For next two semesters, all I could hear was how great OS was, how people got internships/job only because they did OS and blah... There was a time; I started venerating these special people and it kinda gave me a complex. And finally, I decided not to runaway from this challenge but to face it. I had the golden opportunity where I could have graduated in Dec 2009, could have escaped the snowstorm, 4 months at the haunted house and could have "begun" my married life with my beloved but I had promised myself to rectify those "3 mistakes of my life". I knew I was getting into self-invited mental torture and sleepless nights just to compensate my "last-sem" frame of mind.

15-410... the 5 digits are probably fused in my brain forever. This time I was lucky with the partner. I casually asked one of my juniors (not-so-casually, I was impressed by his ES performance) and he immediately agreed. Well, he was saying yes to his ES TA so it was a perfect win-win situation :)

What's so great about 15-410? 15-410 - Operating System Design and Implementation - the course describes itself as "An experience like no other". Most people say it's a C course.. meaning it's an achievement to get a "C" in the course and it requires a hell lot of effort to get a B and an A is possible only if you are as smart as the CS undergraduate students here. Prof David and his teaching style is at the kernel of the success of this unparalleled course.

So the course began with simple ( most certainly didn't seem simple back then ) labs. The first one was stack crawler to traceback the function calls which was just a warm up exercise as says the handout. Then we wrote some device drivers for keyboard, console and timer and a small game, which gave us a hint of kernel programming. It was just a tip of iceberg! So far so good...

Along came P2 ... the first team project in OS. our own version of POSIX thread library. We had 2 weeks to complete this project. My partner and I manged to evade meeting each other for more than 1 week. Those were the days, when the snowstorm hit Pittsburgh and blanketed the region with a thick white coating and CMU, very reluctantly, closed all classes for 3 days. I was enjoying the snow and had no motivation to work with creeping simics from home. But finally we met, had the stack diagrams ready. I started with mutex implementation and he started with thread_fork(). We gradually conquered the further hurdles such as condition variables and thread_join() and just few hours before the midnight we nailed it. We were jumping with excitement when all test cases returned "END__SUCCESS". We actually worked only for 3 days, which was an achievement! It gained us a lot of respect from peers who thought we would never do it so gracefully.

We were all set to repeat our success story in P3 ... a preemptible multi-threaded kernel from scratch ! a 6 weeks project which carries 25% grade. To keep up the momentum, we started on day 1 !!! First things first, we set up a git repository; we had learnt our lesson during p2. What's next? We read the handout (39 pages) twice but still quite clueless. In other words, we didn't do anything. There comes the checkpoint 1. We missed it by small margin. In TA's words "we were 2 days behind the schedule". Good enough by our standards. The next week was spring break and we both were out of Pittsburgh. The enjoyment ended only to realize that we had missed the checkpoint 2 by a large margin. This time we were 1.5 weeks behind the schedule . Obviously we didn't write a single line of code ... Here after, things began to take a nasty turn.

More in my next post...
(It's 2am right now and I'm still not feeling sleepy. Thanks to 15-410 and my partner who kept me awake through those sleepless nights :P)