前言
本篇文章主要分析 Android 原生模块 BootAnimation 开关机动画的相关内容。
正文
摘要
在 BootAnimation 模块中,通过 debug.sf.nobootanimation 属性去控制跳过开机动画,可以通过配置属性 debug.sf.nobootanimation = 1 将开机动画跳过。在编译前也可以通过配置属性 ro.boot.quiescent = 1 将开机动画默认跳过。
BootAnimation 不仅是控制开机动画,关机动画也在其中,在创建 BootAnimation 时通过判断 sys.powerctl 属性值获取 Power 模块的状态,以此来判断是应该播放开机动画还是关机动画。
BootAnimation 函数执行流程描述:BootAnimation 继承自 Thread 类,并且 BootAnimation 重写了 readyToRun() 和 threadLoop() 函数。readyToRun() 和 threadLoop() 函数是 Thread 类的函数,BootAnimation 重写会在线程的启动中自动调用。那么执行顺序就是
BootAnimation() 构造函数 -> readyToRun() -> threadLoop() -> android()/movie() 两种情况,显示原生的图片调用 android(),使用开机动画调用 movie() 函数。
movie() -> loadAnimation() -> playAnimation() -> releaseAnimation()
1、BootAnimation 模块
1.1 介绍
Android 系统在开机过程中会播放一段动画,表示系统在启动中,这一功能实现是由 BootAnimation 模块实现。
BootAnimation 的代码路径在 frameworks/base/cmds/bootanimation 下
路径文件结构如下所示:
ubuntu:frameworks/base/cmds/bootanimation$ tree
.
├── Android.mk // 编译配置文件
├── audioplay.cpp
├── audioplay.h
├── BootAnimation.cpp // bootanimation 功能文件
├── BootAnimation.h
├── bootanimation_main.cpp // bootanimation 入口文件
├── BootAnimationUtil.cpp // 工具类
├── BootAnimationUtil.h
├── bootanim.rc // rc 配置文件
├── FORMAT.md
└── iot
├── BootAction.cpp
├── BootAction.h
├── bootanim_iot.rc
├── BootParameters.cpp
├── BootParameters.h
└── iotbootanimation_main.cpp
1 directory, 16 files
1.2 BootAnimation 服务
1.2.1 rc 文件
BootAnimation 模块的 rc 文件如下所示:
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
writepid /dev/stune/top-app/tasks
在 rc 文件中,定义了一个 /system/bin/ 路径下的 bootanimation 服务,用户定义为 graphics,组定义为 graphics 和 audio,启动状态为 disabled 表示 init 解析 init.rc 的过程中不会自动启动该服务。
1.2.2 Android.mk 文件
再来看下 BootAnimation 服务的 Android.mk 文件,下面是 Android.mk 的部分内容:
# bootanimation executable
# =========================================================
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SHARED_LIBRARIES := \
libbootanimation \
LOCAL_SRC_FILES:= \
BootAnimationUtil.cpp \
LOCAL_SRC_FILES += \
bootanimation_main.cpp \
audioplay.cpp \
LOCAL_MODULE:= bootanimation
LOCAL_INIT_RC := bootanim.rc
include $(BUILD_EXECUTABLE)
# libbootanimation
# ===========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libbootanimation
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SRC_FILES:= \
BootAnimation.cpp
LOCAL_C_INCLUDES += \
external/tinyalsa/include \
frameworks/wilhelm/include
include ${BUILD_SHARED_LIBRARY}
从上述 Android.mk 文件中可以发现,定义了一个可执行文件 bootanimation 和一个动态库 libbootanimation 文件。
可执行文件 bootanimation 的 rc 指定为当前目录下的 bootanim.rc 文件,在编译时,系统会将 bootanim.rc 安装到 /system/etc/init 目录,init 进程在启动时会加载所有的 rc 文件,完成服务的声明。bootanimation 的入口文件为 bootanimation_main.cpp。
动态库 libbootanimation 是 bootanimation 所需的依赖文件,内容为 BootAnimation.cpp。
1.2.3 bootanimation_main.cpp
下面是 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();
//等待SurfaceFlinger初始化完成并加入到 ServiceManager。
//因为 bootanim 服务是在 SurfaceFlinger.init() 里面启动的,但在 SurfaceFlinger 服务是在执行完 SurfaceFlinger.init() 之后才加入到 ServiceManager 的,所以这里需要等一下。
waitForSurfaceFlinger();
// create the boot animation object
ALOGD("DEBUG: create BootAnimation boot");
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks(), 0);
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
变量 noBootAnimation 配置是在 BootAnimation 模块中,通过 debug.sf.nobootanimation 属性去控制跳过开机动画,可以通过配置属性 debug.sf.nobootanimation = 1 将开机动画跳过。在编译前也可以通过配置属性 ro.boot.quiescent = 1 将开机动画默认跳过。
这里等待 SurfaceFlinger,开机动画的播放是建立在系统图像服务就绪的基础上的,如果图像服务还没有初始化完成,那么 BootAnimation 也就无法播放动画了,这也涉及到 bootanimation 的启动,后续在 bootanimation 是如何启动的会介绍。
创建 BootAnimation 准备播放开机动画。
1.2.4 BootAnimation.cpp
初始化 BootAnimation 对象如下所示:
BootAnimation::BootAnimation(sp<Callbacks> callbacks, int dispalyId)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) , mDisplayId(dispalyId){
mSession = new SurfaceComposerClient();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
}
BootAnimation 不仅是控制开机动画,关机动画也在其中,在创建 BootAnimation 时通过判断 sys.powerctl 属性值获取 Power 模块的状态,以此来判断是应该播放开机动画还是关机动画。
初始化中并没有进行预载资源及动画播放的任务,那么 BootAnimation 是如何进行动画播放的呢?在 BootAnimation 类的定义中可以发现,BootAnimation 是继承自 Thread 类的,线程重写 threadLoop() 函数表明线程所需执行的任务,
这里对文件内函数做一些简单解释:
- onFirstRef()—— 属于其父类 RefBase,该函数在强引用 sp 新增引用计数時调用,就是当有 sp 包装的类初始化的时候调用;
- binderDied() ——当对象死掉或者其他情况导致该 Binder 结束时,就会回调 binderDied() 方法;
- readyToRun() ——Thread 执行前的初始化工作;
- threadLoop() ——每个线程类都要实现的,在这里定义 thread 的执行内容。这个函数如果返回 true,且没有调用 requestExit(),则该函数会再次执行;如果返回 false,则 threadloop 中的内容仅仅执行一次,线程就会退出。
其他内部函数简介:
- android() —— 显示系统默认的开机画面;
- movie() —— 显示用户自定义的开机动画;
- loadAnimation(const String8&) —— 加载动画;
- playAnimation(const Animation&) —— 播放动画;
- checkExit() —— 检查是否退出动画;
1.2.4.1 readyToRun()
当线程被执行 run() 函数时,Threads 基类中代码会先进入到 readyToRun() 函数,再调用 threadLoop() 函数。
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::Transaction t;
if (mDisplayId == 1) {
t.setLayerStack(control, 1);
} else {
t.setLayerStack(control, 0);
}
t.setLayer(control, 0x40000000)
.apply();
sp<Surface> s = control->getSurface();
// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h;
EGLint numConfigs;
EGLConfig config[10];
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, config, 10, &numConfigs);
int index = 0;
for (int i = 0; i < numConfigs; ++i) {
EGLint a = 0;
EGLint r, g, b;
r = g = b = 0;
eglGetConfigAttrib(display, config[i], EGL_RED_SIZE, &r);
eglGetConfigAttrib(display, config[i], EGL_GREEN_SIZE, &g);
eglGetConfigAttrib(display, config[i], EGL_BLUE_SIZE, &b);
eglGetConfigAttrib(display, config[i], EGL_ALPHA_SIZE, &a);
if (r == 8 && g == 8 && b == 8 && a == 8) {
index = i;
break;
}
}
surface = eglCreateWindowSurface(display, config[index], s.get(), NULL);
context = eglCreateContext(display, config[index], NULL, NULL);
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
mDisplay = display;
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;
mFlingerSurfaceControl = control;
mFlingerSurface = s;
// 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;
}
在 readyToRun() 函数中,主要完成了 surface 功能的使用初始化,准备后续的动画播放;完成了开关机动画资源文件的配置。
资源文件配置目录
开机动画目录:
“/product/media/bootanimation.zip”
“/oem/media/bootanimation.zip”
“/system/media/bootanimation.zip”
关机动画目录:
“/product/media/shutdownanimation.zip”
“/oem/media/shutdownanimation.zip”
“/system/media/shutdownanimation.zip”
通过循环遍历资源文件路径,配置开机或者关机动画资源位置。
1.2.4.2 threadLoop()
threadLoop() 函数执行如下:
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
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;
}
threadLoop() 函数中主要有两种执行,android() 和 movie(),当配置了动画资源,那么会执行 movie() 进行播放;如果没有执行动画资源,那么会执行 android(),播放原生的 logo 图片,logo 资源路径为【“images/android-logo-mask.png”、“images/android-logo-shine.png”】
1.2.4.3 movie()
播放动画会执行到 movie() 函数中。部分函数如下所示:
bool BootAnimation::movie()
{
Animation* animation = loadAnimation(mZipFileName);
...
playAnimation(*animation);
...
releaseAnimation(animation);
return false;
}
在 movie() 中包含加载动画、播放动画、释放动画等逻辑。这部分代码功能主要是通过 surface 和 opengl 环境去播放动画的。
这里我们来分析下动画是如何结束播放的。
在播放动画 playAnimation() 函数中,会通过循环去显示每一帧图片,每一帧图片显示后都会通过 checkExit() 函数判断是否可以退出显示了:
bool BootAnimation::playAnimation(const Animation& animation)
{
for (int r=0 ; !part.count || r<part.count ; r++) {
...
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
...
checkExit();
}
}
...
return true;
}
1.2.4.4 checkExit()
检测是否退出播放动画逻辑如下:
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
// static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
property_get(EXIT_PROP_NAME, value, "0");
int exitnow = atoi(value);
if (exitnow) {
requestExit();
if (mCallbacks != NULL)
mCallbacks->shutdown();
}
}
checkExit() 函数里面通过获取 service.bootanim.exit 这个属性值来判断是否要退出动画。如果这个属性被设置为 “1”, 则调用 requestExit() 请求结束线程, exitPending()函数的返回值就会变成 true。就退出播放线程。BootAnimation 的使命就完成了,可以退出了。
那么就是说 BootAnimation 的动画结束播放是通过 service.bootanim.exit 属性值控制的,至于是什么模块在什么时候通知的,需要再进行分析。
Android系统开发进阶-BootAnimation启动与结束流程 | 一叶知秋 (qiushao.net)
2、BootAnimation 是如何启动的
上一章节中,我们在 bootanim.rc 中发现启动状态是 disabled,表明在 init 阶段不会将服务启动。那么本章节我们主要来跟踪下,bootanimation 是如何启动的。前面提到开机动画实际是播放图片,它肯定是建立在 Surface 服务的基础上的,这里启动 bootanimation 是由 Surface 服务端 SurfaceFlinger 拉起的,我们来跟踪下如何拉起的。
SurfaceFlinger 的代码路径在 frameworks/native/services/surfaceflinger 下
cc_binary {
name: "surfaceflinger",
defaults: ["surfaceflinger_defaults"],
init_rc: ["surfaceflinger.rc"],
srcs: ["main_surfaceflinger.cpp"],
whole_static_libs: [
"libsigchain",
],
shared_libs: [
"[email protected]",
"android.hardware.configstore-utils",
"[email protected]",
"[email protected]",
"libbinder",
"libcutils",
"libdisplayservicehidl",
"libhidlbase",
"libhidltransport",
"liblayers_proto",
"liblog",
"libsurfaceflinger",
"libtimestats_proto",
"libutils",
],
static_libs: [
"libserviceutils",
"libtrace_proto",
],
ldflags: ["-Wl,--export-dynamic"],
// TODO(b/71715793): These version-scripts are required due to the use of
// whole_static_libs to pull in libsigchain. To work, the files had to be
// locally duplicated from their original location
// $ANDROID_ROOT/art/sigchainlib/
multilib: {
lib32: {
version_script: "version-script32.txt",
},
lib64: {
version_script: "version-script64.txt",
},
},
}
上面是 SurfaceFlinger 下 Android.bp 的二进制可执行文件 surfaceflinger 的配置内容,配置 surfaceflinger.rc 是服务的启动配置文件,入口文件是 main_surfaceflinger.cpp。
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
main_surfaceflinger.cpp 如下:
int main(int, char**) {
signal(SIGPIPE, SIG_IGN);
hardware::configureRpcThreadpool(1 /* maxThreads */,
false /* callerWillJoin */);
startGraphicsAllocatorService();
// When SF is launched in its own process, limit the number of
// binder threads to 4.
ProcessState::self()->setThreadPoolMaxThreadCount(4);
// start the thread pool
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = new SurfaceFlinger();
setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);
set_sched_policy(0, SP_FOREGROUND);
// Put most SurfaceFlinger threads in the system-background cpuset
// Keeps us from unnecessarily using big cores
// Do this after the binder thread pool init
if (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);
// initialize before clients can connect
flinger->init();
// publish surface flinger
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
// publish GpuService
sp<GpuService> gpuservice = new GpuService();
sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
startDisplayService(); // dependency on SF getting registered above
struct sched_param param = {0};
param.sched_priority = 2;
if (sched_setscheduler(0, SCHED_FIFO, ¶m) != 0) {
ALOGE("Couldn't set SCHED_FIFO");
}
// run surface flinger in this thread
flinger->run();
return 0;
}
surfaceflinger 的入口函数中,初始化了一系列服务,GraphicsAllocator、SurfaceFlinger、GpuService、
DisplayService。并且注册 SurfaceFlinger 服务,最后开启 surface flinger 线程。
这里进入 SurfaceFlinger 的初始化函数 init() 中:
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
ALOGI("Phase offest NS: %" PRId64 "", vsyncPhaseOffsetNs);
Mutex::Autolock _l(mStateLock);
// start the EventThread
mEventThreadSource =
std::make_unique<DispSyncSource>(&mPrimaryDispSync, SurfaceFlinger::vsyncPhaseOffsetNs,
true, "app");
mEventThread = std::make_unique<impl::EventThread>(mEventThreadSource.get(),
[this]() { resyncWithRateLimit(); },
impl::EventThread::InterceptVSyncsCallback(),
"appEventThread");
mSfEventThreadSource =
std::make_unique<DispSyncSource>(&mPrimaryDispSync,
SurfaceFlinger::sfVsyncPhaseOffsetNs, true, "sf");
mSFEventThread =
std::make_unique<impl::EventThread>(mSfEventThreadSource.get(),
[this]() { resyncWithRateLimit(); },
[this](nsecs_t timestamp) {
mInterceptor->saveVSyncEvent(timestamp);
},
"sfEventThread");
mEventQueue->setEventThread(mSFEventThread.get());
mVsyncModulator.setEventThread(mSFEventThread.get());
// Get a RenderEngine for the given display / config (can't fail)
getBE().mRenderEngine =
RE::impl::RenderEngine::create(HAL_PIXEL_FORMAT_RGBA_8888,
hasWideColorDisplay
? RE::RenderEngine::WIDE_COLOR_SUPPORT
: 0);
LOG_ALWAYS_FATAL_IF(getBE().mRenderEngine == nullptr, "couldn't create RenderEngine");
LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay,
"Starting with vr flinger active is not currently supported.");
getBE().mHwc.reset(
new HWComposer(std::make_unique<Hwc2::impl::Composer>(getBE().mHwcServiceName)));
getBE().mHwc->registerCallback(this, getBE().mComposerSequenceId);
// Process any initial hotplug and resulting display changes.
processDisplayHotplugEventsLocked();
LOG_ALWAYS_FATAL_IF(!getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY),
"Registered composer callback but didn't create the default primary display");
// make the default display GLContext current so that we can create textures
// when creating Layers (which may happens before we render something)
getDefaultDisplayDeviceLocked()->makeCurrent();
if (useVrFlinger) {
auto vrFlingerRequestDisplayCallback = [this] (bool requestDisplay) {
// This callback is called from the vr flinger dispatch thread. We
// need to call signalTransaction(), which requires holding
// mStateLock when we're not on the main thread. Acquiring
// mStateLock from the vr flinger dispatch thread might trigger a
// deadlock in surface flinger (see b/66916578), so post a message
// to be handled on the main thread instead.
sp<LambdaMessage> message = new LambdaMessage([=]() {
ALOGI("VR request display mode: requestDisplay=%d", requestDisplay);
mVrFlingerRequestsDisplay = requestDisplay;
signalTransaction();
});
postMessageAsync(message);
};
mVrFlinger = dvr::VrFlinger::Create(getBE().mHwc->getComposer(),
getBE().mHwc->getHwcDisplayId(HWC_DISPLAY_PRIMARY).value_or(0),
vrFlingerRequestDisplayCallback);
if (!mVrFlinger) {
ALOGE("Failed to start vrflinger");
}
}
mEventControlThread = std::make_unique<impl::EventControlThread>(
[this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });
// initialize our drawing state
mDrawingState = mCurrentState;
// set initial conditions (e.g. unblank default device)
initializeDisplays();
getBE().mRenderEngine->primeCache();
// Inform native graphics APIs whether the present timestamp is supported:
if (getHwComposer().hasCapability(
HWC2::Capability::PresentFenceIsNotReliable)) {
mStartPropertySetThread = new StartPropertySetThread(false);
} else {
mStartPropertySetThread = new StartPropertySetThread(true);
}
if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGE("Run StartPropertySetThread failed!");
}
mLegacySrgbSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(HWC_DISPLAY_PRIMARY,
Dataspace::SRGB_LINEAR);
ALOGV("Done initializing");
}
在 SurfaceFlinger.cpp 初始化结束时创建了 mStartPropertySetThread 对象,在 StartPropertySetThread 类中设置了开机动画的相关属性。
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;
}
这里设置了两个属性 service.bootanim.exit = 0 和 ctl.start = bootanim。
当 clt.start 属性发生改变时,init 进程就会接收到一个系统属性变化通知,这个通知在 property_service.cpp 中完成处理。当接收到 ctl.start = bootanim 时,init 就会启动 bootanimation 服务。
3、BootAnimation 是如何结束的
第一章节中我们提到开机动画的播放是循环播放的,在循环播放中会检测是否需要退出,而退出的条件是 service.bootanim.exit = 1。是通过 service.bootanim.exit 属性来控制的,也就是说,在启动过程结束时,系统会设置 service.bootanim.exit 属性来结束开机动画。这里我们来跟踪下是在什么时候设置的属性。
通过搜索 service.bootanim.exit,发现在源码中有两处对此属性设置为 1:
SurfaceFlinger.cpp 477 property_set("service.bootanim.exit", "1");
WindowManagerService.java 3506 SystemProperties.set("service.bootanim.exit", "1");
这里我们来看下 SurfaceFlinger.cpp 中的设置是如何调用的。
void SurfaceFlinger::bootFinished()
{
if (mStartPropertySetThread->join() != NO_ERROR) {
ALOGE("Join StartPropertySetThread failed!");
}
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
// wait patiently for the window manager death
const String16 name("window");
sp<IBinder> window(defaultServiceManager()->getService(name));
if (window != 0) {
window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
}
if (mVrFlinger) {
mVrFlinger->OnBootFinished();
}
// 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");
const int LOGTAG_SF_STOP_BOOTANIM = 60110;
LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,
ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
sp<LambdaMessage> readProperties = new LambdaMessage([&]() {
readPersistentProperties();
});
postMessageAsync(readProperties);
}
在 SurfaceFlinger 的 bootFinished() 函数中有关闭开机动画。bootFinished 在 SurfaceFlinger.h 中定义的是虚函数,是 ISurfaceComposer 的接口。
ISurfaceComposer 接口代码路径在 frameworks/native/libs/gui 下。搜索 bootFinished 函数:
ubuntu:/frameworks/native/libs/gui$ grep -irn "bootFinished"
include/gui/ISurfaceComposer.h:131: virtual void bootFinished() = 0;
tests/Surface_test.cpp:554: void bootFinished() override {}
ISurfaceComposer.cpp:98: virtual void bootFinished()
ISurfaceComposer.cpp:603: bootFinished();
ISurfaceComposer.cpp 中 bootFinished() 函数调用
status_t BnSurfaceComposer::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
...
case BOOT_FINISHED: {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
bootFinished();
return NO_ERROR;
}
...
}
}
这里 ISurfaceComposer 通过 Binder 提供 BOOT_FINISHED 服务,那么是谁通过 Binder 调用 BOOT_FINISHED 的呢?
WindowManagerService.java 3530 surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
发现在 WindowManagerService.java 中调用了 BOOT_FINISHED 服务。
private void performEnableScreen() {
synchronized(mWindowMap) {
if (DEBUG_BOOT) Slog.v(TAG_WM, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
+ " mForceDisplayEnabled=" + mForceDisplayEnabled
+ " mShowingBootMessages=" + mShowingBootMessages
+ " mSystemBooted=" + mSystemBooted
+ " mOnlyCore=" + mOnlyCore,
new RuntimeException("here").fillInStackTrace());
if (mDisplayEnabled) {
return;
}
if (!mSystemBooted && !mShowingBootMessages) {
return;
}
if (!mShowingBootMessages && !mPolicy.canDismissBootAnimation()) {
return;
}
// Don't enable the screen until all existing windows have been drawn.
if (!mForceDisplayEnabled
// TODO(multidisplay): Expand to all displays?
&& getDefaultDisplayContentLocked().checkWaitingForWindows()) {
return;
}
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;
}
if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
return;
}
// Bolt S: direct stop the bootanim service.
if (android.os.Bolt.BOLT_BOOTANIM) {
try {
Slog.i(android.os.Bolt.TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED by setting system property!");
SystemProperties.set("ctl.stop", "bootanim");
} catch (Exception e) {
Slog.d(android.os.Bolt.TAG, "Try 'setprop ctl.stop bootanim' failed. Check SELinux policy.");
}
} else {
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
}
}
EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
mDisplayEnabled = true;
if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");
// Enable input dispatch.
mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
}
try {
Slog.i(TAG_WM, "Process check[PHASE_BOOT_COMPLETED] step-3 performEnableScreen {am-start -> bootAnimationComplete}");
mActivityManager.bootAnimationComplete();
} catch (RemoteException e) {
}
mPolicy.enableScreenAfterBoot();
// Make sure the last requested orientation has been applied.
updateRotationUnchecked(false, false);
}
在 WindowManagerService.java 文件的 performEnableScreen() 函数中,不仅调用了 BOOT_FINISHED 接口,还直接设置了 service.bootanim.exit = 1,这里对应了搜索 service.bootanim.exit = 1 的两处调用。
这里我们继续搜索 performEnableScreen() 函数的调用,这里通过加堆栈日志发现,在 ActivityThread 中通过内部类 Idler 调用了am.activityIdle(a.token, a.createdConfig, stopProfiling),最终设置了结束开机动画标识。
Idler Handler –>
ActivityTaskManagerService.java:1675 activityIdle –>
ActivityStackSupervisor.java:1310 activityIdleInternalLocked –>
ActivityStackSupervisor.java:1253 checkFinishBootingLocked –>
ActivityStackSupervisor.java:1261 mService.postFinishBooting(booting, enableScreen); –>
ActivityTaskManagerService.java:5678 postFinishBooting –>
ActivityTaskManagerService.java:6482 enableScreenAfterBoot –>
WindowManagerService.java:3235 enableScreenAfterBoot –>
WindowManagerService.java:3282 performEnableScreen –>
SystemProperties.set(“service.bootanim.exit”, “1”);
这里的 ActivityThread 是应用的 UI 线程,应用主线程加载完毕之后,会结束开机动画。在系统启动的第一个应用是 Launcher。
下图引用资源描述了开关机动画的加载结束过程:
4、如何替换开关机动画
对于自定义开关机动画,可以在开关机路径下放入自定义的资源文件,采用原生机制实现替换。
#开机动画路径
static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] =
"/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] =
"/system/media/bootanimation.zip";
#关机动画路径
static const char OEM_SHUTDOWNANIMATION_FILE[] =
"/oem/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] =
"/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] =
"/system/media/shutdownanimation.zip";
开关机动画资源路径和文件名是固定的:
路径:/oem/media/、/product/media/、/system/media/
命名:bootanimation.zip(开机动画)、shutdownanimation.zip(关机动画)
若以上三个路径下均无动画素材,则会使用system/framework/framework-res.apk中assets目录下的 两张图片做动画
/assets/images/android-logo-shine.png
/assets/images/android-logo-mask.png
以 bootanimation.zip 为例:
资源结构为:
bootanimation.zip : tree
|-- desc.tdxt
|-- part0
|-- part1
desc.txt 是配置文件,描述了播放的规则,bootanimation 播放前会解析播放设置,内容解释如下所示:
1280 768 30 //1280、720 是图片的宽高 24 是每帧播放 24 张图片
c 1 0 part0
p 0 0 part1
第一行:
1280 720 表示图片的分辨率,这个可以根据系统屏幕分辨率和想要的动画效果自己定义
24 表示帧率,即每s播放几帧图片。
第二、三行:
c:为标识符,早期版本值都是p,即part的意思,代表这一行为一个动画片段。
【现在新增了c 标识符(具体什么版本加入未做深究),表示该动画片段至少会执行一次,如果系统发出终止动画指令,p标识的part会立刻停止执行,而c标识的part会继续播放完所有帧后停止,之后的part也是一样,如果标识为p,则不执行,为c,则执行一次,直到所有part都执行完。】
1:表示当前part循环次数,如果为0表示无限循环。
0:表示延时n帧的时间后播放下一个part,
【比如当前例子里,帧率是24,那一帧时长为1/24s,所以延时n帧的时间就是n*(1/24)s。】
part0:是指图片文件夹的名字。
图片的宽高最好保持一致,避免动画播放的效果不理想。 客户提供的素材根据黑屏分出每一个类似动画的素材,每份素材存放一个目录。根据需求调整帧率、动画执行的频率。
压缩包中不能存在二级目录 在 windows 上压缩时需要选择“仅存储”模式压缩。
mac、Linux下使用命令压缩:zip -r -0 bootanimation.zip *