目录
1、源码获取与功能介绍
先看android9.0的开机动画的源代码,在Android OS 在线源代码网站:https://www.androidos.net.cn/sourcecode选择自己生产环境的android版本,就可以看到这个版本的所有源代码:
开机动画相关的代码路径位于/frameworks/base/cmds/bootanimation目录下,可以选择下载下来用工具阅读。
bootanimation模块重要的便是如下几个文件:
- bootanimation_main.cpp // 该模块的主程序入口
- Bootanimation.cpp // 该模块的核心代码
- Android.mk // 该模块的编译规则,在对系统进行全部编译的时候,会首先包含这个mk文件,然后等到编译这个模块时,根据mk文件中的定义,编译出指定的目标内容。
- bootanim.rc // rc文件,用于init主进程拉起该模块
Android.mk文件部分内容如下:
…………
# bootanimation executable
# =========================================================
…………
LOCAL_MODULE:= bootanimation
LOCAL_INIT_RC := bootanim.rc
ifdef TARGET_32_BIT_SURFACEFLINGER
LOCAL_32_BIT_ONLY := true
endif
include $(BUILD_EXECUTABLE)
# libbootanimation
# ===========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libbootanimation
…………
include ${BUILD_SHARED_LIBRARY}
其中LOCAL_MODULE:= bootanimation 和 include $(BUILD_EXECUTABLE)和语句意味着会编译出来一个bootanimation的可执行文件,这个可执行文件位于目标设备/system/bin目录下。而LOCAL_MODULE := libbootanimation和 include ${BUILD_SHARED_LIBRARY}也就代表着会编译出libbootanimation的调用库,在设备的/system/lib目录下,会看到一个libbootanimation.so库。
这个mk文件里还有个特殊的语句:LOCAL_INIT_RC := bootanim.rc
这个语句对应了这个模块下的bootanim.rc文件,在设备的/system/etc/init/目录下也会看到这个bootanim.rc文件(原因:编译的时候会直接copy过去):
service bootanim /system/bin/bootanimation //将该bin文件“挂载”到bootanim服务中,可以通过拉起bootanim服务,以执行该bin文件
class core animation //给服务指定为core和animation,这样方便操作多个服务同时启动或停止
user graphics //在执行此服务之前先切换用户名,当前默认root
group graphics audio //切换用户组
disabled //服务不会自动运行,必须显式地通过服务器来启动
oneshot //当此服务退出时不会自动重启
writepid /dev/stune/top-app/tasks //往指定的文件写内容
关于rc文件的语法和LOCAL_INIT_RC可以参考:
后面提到的其他路径的模块代码,都可以从该网站的文件树中获取到。
2、Android系统如何启动bootanimation
关于开机三个画面的流程,已经有大佬写的很详尽了,可以参考老罗的《Android系统的开机画面显示过程分析》一文,开机动画流程,这一部分大部分是借鉴该文,但由于版本不同,9.0版本代码位置和逻辑会有些差异。 第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面,在默认情况下,这个画面是不会出现的。第二个开机画面是在init进程启动的过程中出现的,它也是一个静态的画面。第三个开机画面是在系统服务启动的过程中出现的,它是一个动态的画面,就是使用比较多的bootanimation模块。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。
我对开机动画一开始也是什么也不了解,也是通过看网上的博客慢慢了解这个过程,所以也是非常感谢各位博主的分享,文章中会出现和一些博主相同的部分,我会尽可能放上原文链接。
这里借用一下博客《一篇文章看明白 Android 图形系统 Surface 与 SurfaceFlinger 之间的关系》中的图文说明:
如图,android9.0开机加载init进程的过程中会根据init.rc中的定义去启动SurfaceFlinger服务,9.0中该模块的rc文件规则是单独放在SurfaceFlinger模块下的。其在9.0中的代码路径:
/frameworks/native/services/surfaceflinger
该目录下有一个surfaceflinger.rc文件(低版本的rc语句都统一放在init.rc文件中,后来为了避免init.rc过于臃肿,便于模块维护和开发,一些服务会有单独的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
init进程会根据这个rc文件规则,去启动位于目标设备/system/bin/surfaceflinger的可执行文件,该可执行文件的程序入口是surfaceflinger模块的main_surfaceflinger.cpp文件,其main函数:
int main(int, char**) {
…………
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = new SurfaceFlinger();
…………
// initialize before clients can connect
flinger->init();
…………
// run surface flinger in this thread
flinger->run();
return 0;
}
其中的sp<SurfaceFlinger> flinger = new SurfaceFlinger()通过智能指针的方式去创建SurfaceFlinger对象,因为SurfaceFlinger重写了父类RefBase的成员函数onFirstRef,因此,在创建该对象是会去执行SurfaceFlinger类中的onFirstRef函数:
void SurfaceFlinger::onFirstRef()
{
mEventQueue->init(this);
}
关于mEventQueue,在SurfaceFlinger.h文件中有定义:
mutable std::unique_ptr<MessageQueue> mEventQueue{std::make_unique<impl::MessageQueue>()};
然后可以找到SurfaceFlinger模块下的MessageQueue.cpp文件中的init函数,其创建并初始化了handle:
void MessageQueue::init(const sp<SurfaceFlinger>& flinger) {
mFlinger = flinger;
mLooper = new Looper(true);
mHandler = new Handler(*this);
}
回到上面main_surfaceflinger的main函数,可以看到语句flinger->init(),其调用的便是SurfaceFlinger的成员函数init(),部分代码如下:
// Do not call property_set on main thread which will be blocked by init Use StartPropertySetThread instead.
void SurfaceFlinger::init() {
…………
if (getHwComposer().hasCapability(
HWC2::Capability::PresentFenceIsNotReliable)) {
mStartPropertySetThread = new StartPropertySetThread(false);
} else {
mStartPropertySetThread = new StartPropertySetThread(true);
}
if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGE("Run StartPropertySetThread failed!");
}
…………
}
根据函数的注释也能看到,这里为了避免设置系统属性时引起阻塞,启动了一个StartPropertySetThread单独线程, 该线程定义如下:
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
property_set("service.bootanim.exit", "0");
// Start BootAnimation if not started
property_set("ctl.start", "bootanim");
// Exit immediately
return false;
}
线程体中通过设置系统控制属性ctl.start为bootanim,来启动前面bootanim.rc中定义的bootanim服务:service bootanim /system/bin/bootanimation,以执行bootanimation可执行文件。而设置系统属性service.bootanim.exit为0,则是为了后面终止bootanimation程序做准备。
属性机制的坑已经填了,请见博客《Android4.4 property机制学习》。
//TODO2 关于SurfaceFlinger,只是根据网上别人的博客,追踪了一下开机动画的相关内容,具体的内容与原理,继续挖坑。
3、Bootanimation
3.1、初始化工作
此处借鉴《[Android5.1]开机动画显示工作流程分析》一文,启动bootanimation可执行文件后,首先会执行到上面介绍过的bootanimation_main.cpp文件中的main函数:
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;
}
其中的bootAnimationDisabled()函数会检测系统属性,以获取是否播放开机动画的属性值并赋值给noBootAnimation,如果noBootAnimation 为false,则直接退出程序,不进行播放开机动画。否则接下来就会启动一个Binder线程池,并且创建一个BootAnimation对象。这个BootAnimation对象就是用来显示第三个开机画面的。由于BootAnimation对象在显示第三个开机画面的过程中,需要与SurfaceFlinger服务通信,因此,应用程序bootanimation就需要启动一个Binder线程池。
BootAnimation类部分声明如下:
class BootAnimation : public Thread, public IBinder::DeathRecipient //继承了Thread类和IBinder::DeathRecipient类
{
…………
private:
virtual bool threadLoop(); //线程体,如果返回true,且requestExit()没有被调用,则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出
virtual status_t readyToRun(); //线程体执行前的初始化工作
virtual void onFirstRef(); //属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用
virtual void binderDied(const wp<IBinder>& who); //当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法
…………
}
BootAnimation类的构造函数如下:
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) {
mSession = new SurfaceComposerClient();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
}
其中 mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信。SurfaceComposerClient类内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,并执行binderDied函数:
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();
}
binderDied函数会杀死当前进程,并退出下面讲到的onFirstRef函数所启动的bootAnimation线程:
run("BootAnimation", PRIORITY_DISPLAY);
由于BootAnimation类间接继承了RefBase类,且上面的main函数创建BootAnimation对象的时候使用智能指针引用,所以执行BootAnimation类的构造函数创建对象时,也会执行onFirstRef函数,下面是onFirstRef函数:
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);
}
}
这里通过调用成员变量mSession的成员函数linkToComposerDeath,来注册SurfaceFlinger服务的死亡接收通知。并且调用了父类Thread的成员函数run来创建BootAnimation线程,由于该类重写了Thread类的readyToRun函数,所以在执行threadLoop之前,会先执行readyToRun来做一些初始化工作。其部分代码如下:
// If the device has encryption turned on or is in process of being encrypted we show the encrypted boot animation.
char decrypt[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt, "");
bool encryptedAnimation = atoi(decrypt) != 0 ||
!strcmp("trigger_restart_min_framework", decrypt);
if (!mShuttingDown && encryptedAnimation) {
static const char* encryptedBootFiles[] =
{PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE};
for (const char* f : encryptedBootFiles) {
if (access(f, R_OK) == 0) {
mZipFileName = f;
return NO_ERROR;
}
}
}
static const char* bootFiles[] =
{PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};
static const char* shutdownFiles[] =
{PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE};
for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) {
if (access(f, R_OK) == 0) {
mZipFileName = f;
return NO_ERROR;
}
}
return NO_ERROR;
首先会根据系统属性vold.decrypt来判断系统是否启动加密或者正在加密处理,如果是,则会根据优先级降级的方式,去播放如下两个路径的加密动画:
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
否则,将会根据Bootanimation构造函数中获取到的系统属性sys.powerctl判断系统是否正在关机,系统关机或非关机状态下依然根据优先级降序。这里的非关机状态是指,系统会在启动过程中或者启动后,都是可以通过运行bootanimation程序来播放开机动画。播放指定路径下的动画文件:
static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
…………
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";
初始化工作完成后,就会进入下面的线程体函数threadLoop(),其中android()和movie()函数会返回false,即threadLoop()函数会返回false,那么线程体执行一次就会退出。
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
如果初始化过程中能获取到动画文件,就播放该自定义动画,否则将执行android()函数,播放android原生动画
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;
}
3.2、android()
根据readyToRun()函数中的初始化结果,如果不存在自定义的动画文件,threadLoop()就会执行android()函数,其部分代码如下:
bool BootAnimation::android()
{
ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
mCallbacks->init({});
// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
…………
const nsecs_t startTime = systemTime();
do {
nsecs_t now = systemTime();
…………
// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
android()原生的开机动画位于/frameworks/base/core/res/assets/images目录下,由android-logo-shine.png和android-logo-mask.png两张png图片组成:


根据/frameworks/base/core/res/路径下的Android.bp文件:
android_app {
name: "framework-res",
no_framework_libs: true,
certificate: "platform",
// Soong special-cases framework-res to install this alongside
// the libraries at /system/framework/framework-res.apk.
// Generate private symbols into the com.android.internal.R class
// so they are not accessible to 3rd party apps.
aaptflags: [
"--private-symbols",
"com.android.internal",
// Framework doesn't need versioning since it IS the platform.
"--no-auto-version",
// Allow overlay to add resource
"--auto-add-overlay",
],
// Create package-export.apk, which other packages can use to get
// PRODUCT-agnostic resource data like IDs and type definitions.
export_package_resources: true,
}
它们最终会编译成framework-res.apk并放在目标设备的/system/framework/目录下,而initTexture()函数会根据这两张图片来分别创建两个纹理对象,并存储在Bootanimation类的成员变量数组mAndroid中。通过混合渲染这两个纹理对象,我们就可以得到一个开机动画,这是通过中间的while循环语句来实现的。
图片android-logo-mask.png用作动画前景,它是一个镂空的“ANDROID”图像。图片android-logo-shine.png用作动画背景,它的中间包含有一个高亮的呈45度角的条纹。在每一次循环中,图片android-logo-shine.png被划分成左右两部分内容来显示。左右两个部分的图像宽度随着时间的推移而此消彼长,这样就可以使得图片android-logo-shine.png中间高亮的条纹好像在移动一样。另一方面,在每一次循环中,图片android-logo-shine.png都作为一个整体来渲染,而且它的位置是恒定不变的。由于它是一个镂空的“ANDROID”图像,因此,我们就可以通过它的镂空来看到它背后的图片android-logo-shine.png的条纹一闪一闪地划过。
3.3、movie()
如果存在自定义的动画文件,则会执行movie()函数,其部分代码如下:
bool BootAnimation::movie()
{
Animation* animation = loadAnimation(mZipFileName);
if (animation == NULL)
return false;
…………
playAnimation(*animation);
…………
releaseAnimation(animation);
…………
return false;
}
其中 loadAnimation函数如下:
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
{
…………
Animation *animation = new Animation;
animation->fileName = fn;
animation->zip = zip;
animation->clockFont.map = nullptr;
mLoadedFiles.add(animation->fileName);
parseAnimationDesc(*animation);
if (!preloadZip(*animation)) {
return NULL;
}
mLoadedFiles.remove(fn);
return animation;
}
loadAnimation函数中的parseAnimationDesc函数,会去解析bootanimation*.zip压缩包中的desc.txt文件。举一个desc.txt文件的例子:

681 300 20
p 1 0 android
p 0 10 loading
如上desc.txt文件共三行,第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps),剩余的每一行都用来描述一个动画片断,这些行一般要以字符“p”来开头,后面紧跟着两个数字以及一个文件目录路径名称。第一个数字表示一个片断的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。第二个数字表示每一个片断在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中。参考《[Android5.1]开机动画desc.txt描述文件的分析》。
以上面这个desc.txt文件的内容为例,它描述了一个大小为681 x 300 的开机动画,动画的显示速度为20帧每秒。这个开机动画包含有两个片断android和loading。片断android只显示一次,它对应的png图片保存在目录android中。片断loading无限循环地显示,其中,每两次循环显示的时间间隔为10 x (1 / 24)秒,它对应的png图片保存在目录loading中。
parseAnimationDesc函数根据desc.txt文件规则解析完成后,根据解析结果和那两个目录的图片资源,去执行preloadZip函数以进行预加载处理。解析和预加载这两个过程都是以一个Animation类型的实例animation为主体进行处理,其定义如下:
struct Animation {
struct Frame {
String8 name;
FileMap* map;
int trimX;
int trimY;
int trimWidth;
int trimHeight;
mutable GLuint tid;
bool operator < (const Frame& rhs) const {
return name < rhs.name;
}
};
struct Part {
int count; // The number of times this part should repeat, 0 for infinite
int pause; // The number of frames to pause for at the end of this part
int clockPosX; // The x position of the clock, in pixels. Positive values offset from
// the left of the screen, negative values offset from the right.
int clockPosY; // The y position of the clock, in pixels. Positive values offset from
// the bottom of the screen, negative values offset from the top.
// If either of the above are INT_MIN the clock is disabled, if INT_MAX
// the clock is centred on that axis.
String8 path;
String8 trimData;
SortedVector<Frame> frames;
bool playUntilComplete;
float backgroundColor[3];
uint8_t* audioData;
int audioLength;
Animation* animation;
};
int fps;
int width;
int height;
Vector<Part> parts;
String8 audioConf;
String8 fileName;
ZipFileRO* zip;
Font clockFont;
};
loadAnimation函数执行完成后就会去执行playAnimation函数进行播放动画,播放完成后到releaseAnimation函数释放相关资源。
3.4、停止播放
当SystemServer将系统中的关键服务启动完成后,会启动桌面启动器Launcher,Launcher启动后,最终以SurfaceFlinger服务接收到类型为IBinder::FIRST_CALL_TRANSACTION,即类型为BOOT_FINISHED的进程间通信请求时,它就会将该请求交给它的成员函数bootFinished来处理:
//SurfaceFlinger::bootFinished
// 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.
property_set("service.bootanim.exit", "1");
可以看到其将系统属性service.bootanim.exit设置为1,而在前面的android()函数体和movie()函数的调用链中都可以找到checkExit()和exitPending()这两个函数:
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0"); //static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
int exitnow = atoi(value);
if (exitnow) {
requestExit();
mCallbacks->shutdown();
}
}
显而易见,checkExit函数在检测到该系统属性为1的时候,就会调用requestExit函数停止bootanimation的线程体threadLoop。而while循环中的循环判断条件表达式中的exitPending()函数,会去判断requestExit函数是否被调用过,如果调用过则返回true,否则为false,以终止android函数或movie函数中的while循环。
4、待补充
文章中关于系统属性、SurfaceFlinger、以及在过程中遇到的SELinux域转换的问题,都是比较复杂的模块,以后认真学习过后再单独写博文。本文中很多省略的代码主要是太菜了,还看不明白,所以后面了解地更深刻以后,会及时补充本博文。