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