The first second of an app's launch
Anas Ambri
Android Montreal group - September 2016
$whoami
Android & iOS freelancer
@AnasAmbri
Slides @ slides.com/anasambri/first-second
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
- How the OS detects a touch
- How an Android app receives a touch
- How the launcher screen works
- 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
Live dump
Live dump (2)
Live dump (3)
EventHub
A class that buffers all events
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
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
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 }
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
<!-- 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,151