The first second of an app's launch

Anas Ambri

Android Montreal group - September 2016

$whoami

Android & iOS freelancer

@AnasAmbri

Inspiration

Taken from [1]

Question of the day

Can we create a timeline for what happens when we click on app icon?

Motivation

Taken from [2]

Just to make it clear

Will this be useful for you tomorrow?

Probably not

Will it help you make better decisions ?

Definitely

More practical reasons

Outline

  1. How the OS detects a touch
  2. How an Android app receives a touch
  3. How the launcher screen works
  4. How the selected app is launched

If you have a question, at any point, stop me.

Tools

Part 1: How your phone can feel your touch

At the lowest* level

Driver converts touches into a stream of events

 

Exposes them in a file (/dev/input/eventX)

  • /dev/input/event0 is the screen
  • /dev/input/event1 is the home button

getevent -lt

getevent gives a live dump of input events

If you have a rooted device:

adb shell su getevent -lt

(sendevent can actually send events too)

Live dump

Live dump (2)

Live dump (3)

EventHub

A class that buffers all events

Full implementation

EventHub (2)

1  size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    //...
3     RawEvent* event = buffer;
4     while (mPendingEventIndex < mPendingEventCount) {
5         Device* device = mDevices.valueAt(deviceIndex);
        //...
7         if (eventItem.events & EPOLLIN) {
8             int32_t readSize = read(device->fd, readBuffer, 
                sizeof(struct input_event) * capacity);
            //...
10            size_t count = size_t(readSize) / sizeof(struct input_event);
11            for (size_t i = 0; i < count; i++) {
12                struct input_event& iev = readBuffer[i];
13                    event->deviceId = deviceId;
14                    event->type = iev.type;
15                    event->code = iev.code;
16                    event->value = iev.value;
17                    event += 1;
18                }
19            }
20        }
21    }
22 }

InputReader

A class that notifies upper layers of change

Full implementation

InputReader (2)

1  void InputReader::loopOnce() {
2      Vector<InputDeviceInfo> inputDevices;
3      size_t count = mEventHub->getEvents(
           timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

5      { //...
6          if (count) {
7              processEventsLocked(mEventBuffer, count);
8      }
       //...

10     // Send out a message that the describes the changed input devices.
11     if (inputDevicesChanged) {
12         mPolicy->notifyInputDevicesChanged(inputDevices);
13     }
14 }

InputManagerService

This is a JNI class (C++ & Java implementations)

GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
    "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");

private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
    //...
    mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
                    mInputDevices).sendToTarget();
}

private final class InputManagerHandler extends Handler {
    //...
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                deliverInputDevicesChanged((InputDevice[])msg.obj);
                break;
        }
        //...
    }
}

WindowManagerService

  • A system service
  • Takes care of communication between apps and anything that has to do with the Window
  • Full implementation

Part 2: How an app receives a touch

A Familiar face: View

A view has a ViewRootImpl

A ViewRootImpl has a WindowInputEventReceiver

WindowInputEventReceiver extends InputEventReceiver

 InputEventReceiver has two methods 

- handleEvent()

- consumeEvents()

WindowManagerService keeps an eye on all InputEventReceivers

InputEventReceiver

1 int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
     //...
2    JNIEnv* env = AndroidRuntime::getJNIEnv();
3    status_t status = consumeEvents(env, false /*consumeBatches*/, -1);
4 }

6 status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
7     bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {

9     for (;;) {
10        InputEvent* inputEvent;
11        status_t status = mInputConsumer.consume(&mInputEventFactory,
              consumeBatches, frameTime, &seq, &inputEvent);

          //...
13        inputEventObj = android_view_KeyEvent_fromNative(env,
            static_cast<KeyEvent*>(inputEvent));
          //...
15        env->CallVoidMethod(receiverObj.get(),
             gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
16    }
17 }

Also JNI code (C++ & Java parts)

Back to the View

1  public abstract class InputEventReceiver {
2      private void dispatchInputEvent(int seq, InputEvent event) {
3          mSeqMap.put(event.getSequenceNumber(), seq);
4          onInputEvent(event);
5      }
6  }

7  final class WindowInputEventReceiver extends InputEventReceiver {
8      public void onInputEvent(InputEvent event) {
9          enqueueInputEvent(event, this, 0, true);
10     }
11 }

12 public final class ViewRootImpl {
13     void enqueueInputEvent(InputEvent event,
14         InputEventReceiver receiver, int flags, boolean processImmediately) {
           //...
16         if (processImmediately) {
17             doProcessInputEvents();
18         } else {
19             scheduleProcessInputEvents();
20         }
21     }
22 }

The View, at last

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;

    // Deliver the key to the view hierarchy.
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
}

The View, at last

Summary of first two parts

Bottom-up

Drivers send events

Events are buffered, then forwarded to the WindowManagerService

Top-down

Each view has an InputEventReceiver

InputEventReceiver receive events from the WindowManagerService

Part 3: How the launcher works

Pretty easy, actually

A launcher is just an app

Demo

<!-- This is the home screen -->
<activity
    android:name=".activity.HomeActivity" android:label="Fake Launcher"
    android:theme="@android:style/Theme.Wallpaper.NoTitleBar"
    android:launchMode="singleTask" android:stateNotNeeded="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<!-- This is needed, or else your app won't compile -->
<activity
    android:name=".activity.LaunchActivity" 
    android:theme="@android:style/Theme.NoDisplay">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Pretty easy (2)

1  //Retrieve all apps on the phone
2  Intent i = new Intent(Intent.ACTION_MAIN, null);
3  i.addCategory(Intent.CATEGORY_LAUNCHER);

5  List<ResolveInfo> availableActivities = packageManager.queryIntentActivities(i, 0);
6  for(ResolveInfo ri:availableActivities){
7  AppDetails details = new AppDetails(
8      ri.activityInfo.loadLabel(packageManager).toString(),
9      ri.activityInfo.packageName,
10     ri.activityInfo.loadIcon(packageManager)
11 );
12 }

14 //Start App
15 Intent intent = context.getPackageManager()
    .getLaunchIntentForPackage(appDetails.getPackageName());
16 if (intent != null) {
17     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
18     context.startActivity(intent);
19 }

Part 4: How the selected app is launched

ActivityManagerService

Like WindowManagerService, but for activities

ResolveInfo rInfo = ActivityThread.getPackageManager().resolveIntent(
            intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS);

intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));

This will submit a request to start the Activity (after checking permissions)

Process

Eventually, we create a process

// Is this activity's application already running?
1  ProcessRecord app = getProcessRecordLocked(r.processName, r.info.appInfo.uid);

2  if (app != null && app.thread != null) {
3      try {
4          realStartActivityLocked(r, app, andResume, checkConfig);
5          return;
6      } catch (RemoteException e) { }
7  }

9  startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
          "activity", r.intent.getComponent(), false);


10 int pid = Process.start("android.app.ActivityThread",
         mSimpleProcessManagement ? app.processName : null, uid, uid,
         gids, debugFlags, null);

Process (2)

Eventually, we create a process

1  try {
       return startViaZygote(processClass, niceName, uid, gid, gids,
              debugFlags, zygoteArgs);
2  } catch (ZygoteStartFailedEx ex) {
3      throw new RuntimeException(
            "Starting VM process through Zygote failed", ex);
4  }

5  private static ProcessStartResult startViaZygote(final String processClass,
                                  //...) throws ZygoteStartFailedEx {
6  return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}

Zygote

This is a process that gets copied for every app

pid = Zygote.fork();

A process is a wrapper around a task

By forking, the OS saves resources

Remember this?

Summary

Resources

  • androidxref http://androidxref.com/7.0.0_r1/
  • getevent https://source.android.com/devices/input/getevent.html
  • sendevent http://ktnr74.blogspot.ca/2013/06/emulating-touchscreen-interaction-with.html
  • System services https://anatomyofandroid.com/2013/10/03/system-services/
  • Input handling https://seasonofcode.com/posts/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace.html

References

[1]: http://archive.ncsa.illinois.edu/Cyberia/Cosmos/CosmicMysteryTour.html

[2]: http://www2.r3gis.fr/CSSPresentation/#/8

Thank you!

Would love to hear your feedback @AnasAmbri

The first second of an app's launch

By Anas Ambri

The first second of an app's launch

What happens between the moment the screen is touched, and the moment the app you clicked on finally appears. This is what we are gonna answer.

  • 3,005