1. 谁拉起了开机动画

BootAnimation启动时序.png

​ 在系统上电后,通过内置的rom_code将uboot映像从flash中加载到内存中运行,之后跳转到uboot执行,做一些硬件外设参数的初始化工作,然后从flash中加载kernel到内存中运行并跳转到kernel执行。

​ init进程是kernel的第一个进程,也是Android系统的第一个进程。而跟开机动画有关的,主要是init中做的两件事,一件事是启动surfaceflinger进程,开机动画的启动就是在这里触发;另外一件事是启动zygote进程,zygote进程起来后就fork出了system_server,system_server主要是启动系统服务,如AMS,WMS,PMS等。等待一些关键服务ready后,就开始加载launcher,launcher加载好就触发结束开机动画的操作,从而进入到launcher界面。

​ 开机动画是在surfaceflinger中触发,而surfaceflinger进程是在init.rc中启动的,如下:

1
service surfaceflinger /system/bin/surfaceflinger

​ 需要注意的是高版本的Android上,如AndroidP,surfaceflinger进程并不是直接在init.rc文件中启动的,而是通过Android.bp文件去包含启动surfaceflinger.rc文件,然后在该文件中再去启动surfaceflinger:

1
2
3
4
5
6
7
8
9
10
11
# frameworks/native/services/surfaceflinger/Android.bp
cc_binary {
name: "surfaceflinger",
defaults: ["surfaceflinger_defaults"],
init_rc: ["surfaceflinger.rc"], # 编译的时候将启动脚本放在surfaceflinger.rc
srcs: ["main_surfaceflinger.cpp"], # 启动之后,会调用main_surfaceflinger的main方法
whole_static_libs: [
"libsigchain",
],
......
}
1
2
3
4
5
6
7
8
9
10
# frameworks/native/services/surfaceflinger/surfaceflinger.rc
service surfaceflinger /system/bin/surfaceflinger
class core animation
user system
group graphics drmrpc readproc
onrestart restart zygote
writepid /dev/stune/foreground/tasks
socket pdx/system/vr/display/client stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
socket pdx/system/vr/display/manager stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0
socket pdx/system/vr/display/vsync stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0
1
2
# system/etc/init/surfaceflinger.rc
# 系统打出来之后,rc文件会放在system/etc/init目录,init进程启动的时候,会自动解析该目录的rc文件,并且启动对应的服务。
1
2
3
4
5
6
7
8
9
10
11
// frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

int main(int, char**) {
...
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = new SurfaceFlinger();
...
flinger->init();
...
flinger->run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {

......
// Inform native graphics APIs whether the present timestamp is supported:
// 创建一个StartPropertySetThread实例,并且调用Start()启动开机动画
if (getHwComposer().hasCapability(
HWC2::Capability::PresentFenceIsNotReliable)) {
mStartPropertySetThread = new StartPropertySetThread(false);
} else {
mStartPropertySetThread = new StartPropertySetThread(true);
}

if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGE("Run StartPropertySetThread failed!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp
namespace android {

StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}

status_t StartPropertySetThread::Start() {
return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}

bool StartPropertySetThread::threadLoop() {
// Set property service.sf.present_timestamp, consumer need check its readiness
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
// Clear BootAnimation exit flag
// 清楚bootanimation标志位,当为1的时候,退出开机动画
property_set("service.bootanim.exit", "0");
// Start BootAnimation if not started
// 启动开机动画service.
property_set("ctl.start", "bootanim");
// Exit immediately
return false;
}

}

2. Bootanimation实现流程

1
2
3
4
5
6
7
8
// frameworks/base/cmds/bootanimation/bootanim.rc
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
writepid /dev/stune/top-app/tasks

通过SurfaceFlinger启动开机动画进程。调用bootanimation_main的main方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {

sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();

waitForSurfaceFlinger();

// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");

IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}

new了一个BootAnimation实例,然后创建了一个binder线程池,用于显示动画时,与surfaceflinger进程通信用。接下来看看BootAnimation的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// frameworks/base/cmds/bootanimation/BootAnimation.cpp
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) {
// 创建一个mSession与SurfaceFlinger通信
mSession = new SurfaceComposerClient();

std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
}

这里创建了SurfaceComposerClient,用于与surfaceflinger通讯。接下来就到了onFirstRef:

1
2
3
4
5
6
7
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}

这里先注册surfaceflinger的死亡消息通知书linkToComposerDeath,只要surfaceflinger挂掉了,bootanimation进程就会收到通知,从而执行如下代码:

1
2
3
4
5
6
7
8
9
10
void BootAnimation::binderDied(const wp<IBinder>&)
{
// woah, surfaceflinger died!
ALOGD("SurfaceFlinger died, exiting...");

// calling requestExit() is not enough here because the Surface code
// might be blocked on a condition variable that will never be updated.
kill( getpid(), SIGKILL );
requestExit();
}

直接退出,等待surfaceflinger的下一次重启,如果还有来生的话。

onFirstRef在创建了死亡通知书后,还做了一件事,那就是run bootanimation,个中细节不在这里列出,bootanimation重写了readyToRun和threadLoop,我们直接看threadLoop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
// 如果没有自定义的开机动画包,就是用Android原生的logo做开机动画
if (mZipFileName.isEmpty()) {
r = android();
} else {
r = movie();
}

eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return r;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool BootAnimation::movie()
{
// 加载该机动画资源
// 读取zip包,并且解析Desc描述文件,将每部分的配置解析出来保存在animation中,后面播放的时候根据这个配置文件播放开机动画
Animation* animation = loadAnimation(mZipFileName);
if (animation == NULL)
return false;

.....
// 播放开机动画
playAnimation(*animation);

......
// 释放animation对象
releaseAnimation(animation);

......
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
bool BootAnimation::playAnimation(const Animation& animation)
{
// 获得fps和动画宽高
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
const int animationX = (mWidth - animation.width) / 2;
const int animationY = (mHeight - animation.height) / 2;

// 取出每一个part一帧一帧播放
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);

// Handle animation package
if (part.animation != NULL) {
playAnimation(*part.animation);
if (exitPending())
break;
continue; //to next part
}

for (int r=0 ; !part.count || r<part.count ; r++) {
......
// 每一帧播完之后都检测一次是否退出播放
checkExit();
}

.....
}

}

......
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
// EXIT_PROP_NAME = "service.bootanim.exit"
property_get(EXIT_PROP_NAME, value, "0");
int exitnow = atoi(value);
// 如果属性值为1,就请求退出开机动画
if (exitnow) {
requestExit();
mCallbacks->shutdown();
}
}

至此,从每一帧开机动画播完之后,都会检测service.bootanim.exit的值,当属性值为1的时候,则开机动画会requestExit, 从而结束开机动画。那是谁给service.bootanim.exit的属性值设置为1呢?

3.bootanimation的结束

init启动zygote进程之后,由zygote孵化出了system_server,然后system_server启动了各种各种的系统所需的服务,其中就有AMS,AMS启动并ready后,会执行startHomeActivityLocked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
startHomeActivityLocked(currentUserId, "systemReady");
...
}

boolean startHomeActivityLocked(int userId, String reason) {
...
Intent intent = getHomeIntent();
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
mActivityStartController.startHomeActivity(intent, aInfo, myReason);
...
}

launcher在这里开始加载启动,在launcher的主线程处于空闲时,就会向ActivityManagerService发送一个activityIdle的消息:

1
2
3
4
5
6
7
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
...
ActivityRecord r = mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */, false /* processPausingActivities */, config);

...
}

这里通过activityIdleInternalLocked获取到ActivityRecord实例,我们看看具体实现:

1
2
3
4
5
6
7
8
9
10
11
// frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
boolean processPausingActivities, Configuration config) {

....
//Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
if (isFocusedStack(r.getStack()) || fromTimeout) {
booting = checkFinishBootingLocked();
}
....
}

这里会检测开机是否结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
private boolean checkFinishBootingLocked() {
final boolean booting = mService.mBooting;
boolean enableScreen = false;
mService.mBooting = false;
if (!mService.mBooted) {
mService.mBooted = true;
enableScreen = true;
}
if (booting || enableScreen) {
mService.postFinishBooting(booting, enableScreen);
}
return booting;
}

这里会直接进入到postFinishBooting方法中执行:

1
2
3
4
5
//AMS
void postFinishBooting(boolean finishBooting, boolean enableScreen) {
mHandler.sendMessage(mHandler.obtainMessage(FINISH_BOOTING_MSG,
finishBooting ? 1 : 0, enableScreen ? 1 : 0));
}

这里直接发一条消息FINISH_BOOTING_MSG,我们看看具体handler的处理:

1
2
3
4
5
6
7
8
9
10
case FINISH_BOOTING_MSG: {
if (msg.arg1 != 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
finishBooting();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
if (msg.arg2 != 0) {
enableScreenAfterBoot();
}
break;

最终执行到了enableScreenAfterBoot方法:

1
2
3
4
5
void enableScreenAfterBoot() {
...
mWindowManager.enableScreenAfterBoot();
...
}

这里调用了WMS的方法enableScreenAfterBoot,我们跳入看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void enableScreenAfterBoot() {

...
performEnableScreen();
...
}

private void performEnableScreen() {
...
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// stop boot animation
// formerly we would just kill the process, but we now ask it to exit so it
// can choose where to stop the animation.
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
}

...
}

到了这里,最终通过设置service.bootanim.exit的值,stop掉了开机动画,接着来的就是发出了开机广播。

4.开机动画优化

由于当前项目的开机动画由QNX来控制,因此,我们尽量减轻Android开机动画的播放就行。

4.1 分析描述文件

优化前的配置文件

1
2
3
4
5
6
7
832 520 30
c 1 30 part0
c 1 0 part1
c 0 0 part2
c 1 30 part3
c 1 0 part4
c 1 0 part5
  • 第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。

  • 剩余的每一行都用来描述一个动画片断,p 这个flag表示某一个part的图片的处理方式,bootanimation 程序在处理某part的图片时会参考这个标志.P表示bootanimation可以随时退出当处理完一张图片后,如果bootanimation可以退出. ”C” 则表示必须把这部分的图片处理完毕才可以退出.事实上这个标识只要不为C, 则其行为完全一致.

  • “1”这个flag表示这part的图片需要循环处理多少次. 1表示处理循环处理一次.”0” 表示无限循环.

  • “0” 这个flag 表示播放完这部分所有图片后需要pause多久时间。”0” 表示不需要pause。“part0”表示动画的第一部分内容。“part1” 表示动画的第二部分内容。

开始处理新的一帧, 如果这部分part的处理标识不是”c”则可以退出.否则不能退出.
处理完一次循环后, 如果这部分part的处理标识是”c”,并且这部分是无限循环,则可以退出.如果不是无限循环则不能退出, 需要完全处理完这部分的图片才可能退出.

优化后的配置文件

1
2
832 520 30
p 1 0 part1

然后将资源包放在车机:/system/media/bootanimation.zip

原生的开机动画路径:packages/services/Car/car_product/bootanimations/bootanimation.zip

5.总结

优化开机动画主要原理是开机动画会比较消耗资源,在开机启动服务期间减少开机动画对CPU的消耗,从侧面就提升了Android的启动耗时。