Android 开机动画实现方式目前主要方式:逐帧动画和OpenGL直接编程绘制动画
- 逐帧动画 :逐帧动画的序列内容不一样,给制作增加了负担而且输出文件量很大,逐帧动画具有非常大的灵活性。实现原理是将一系列图片打包成
bootanimation.zip
放入/system/media/
目录,系统会将图片一帧一帧循环播放形成一个动画效果,但是zip大于5M的时候,动画会有明显的卡顿,文件越大越不流畅。 - OpenGL:OpenGL 是个定义一个跨编程语言、跨平台的应用程序接口 API 的规范,可以用于生成二维、三维图像。这个接口由近三百五十个不同的函数调用组成,用来从简单的图形比特绘制复杂的三维景象。
开机动画启动流程
先简单了解下系统的启动过程(后面会详细讲解),内核启动后会启动第一个进程,就是init进程,init 进程会根据init.rc
配置启动surfaceflinger
进程,需要注意在Android系统中一切和屏幕绘制相关的都需要用到surfaceflinger
,而bootanimation
开机动画也属于屏幕绘制,所以bootanimation
肯定在surfaceflinger
进程启动之后,但是init.rc
不会启动bootanim.rc
,因为bootanim.rc
中配置了disable
就不会启动bootanim,那么什么时候启动bootanim呢?
开机动画相关的代码:
- bootanimation :
frameworks/base/cmds/bootanimation/
看下bootanimation的源码目录:可以看到Android.bp的配置,整体的开机动画就是C++写的,同时还有bootanim.rc
文件
看下Android.bp的核心配置:cc_binary
最终会编译成Native 执行程序
css
cc_binary {
name: "bootanimation",
defaults: ["bootanimation_defaults"],
shared_libs: [
"libOpenSLES",
"libbootanimation",
],
srcs: [
"BootAnimationUtil.cpp",
"bootanimation_main.cpp",
"audioplay.cpp",
],
product_variables: {
product_is_iot: {
shared_libs: [
"libandroidthings",
"libandroidthings_protos",
"libchrome",
"libprotobuf-cpp-lite",
],
static_libs: ["libjsoncpp"],
srcs: [
"iot/iotbootanimation_main.cpp",
"iot/BootAction.cpp",
"iot/BootParameters.cpp",
],
exclude_srcs: [
"bootanimation_main.cpp",
"audioplay.cpp",
],
},
},
init_rc: ["bootanim.rc"],
}
在out目录下的 su_os/system/bin
目录下,就可以看到编译好的bootanimation的可执行程序
bootanim.rc
: 由于配置了disabled
那么在init.rc 执行的时候就不会拉起bootanim.rc
css
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
ioprio rt 0
writepid /dev/stune/top-app/tasks
- sufaceflinger :
frameworks/native/services/surfaceflinger/
一切和屏幕绘制有关的都和sufaceflinger相关,同时在源码目录中也会看到surfaceflinger.rc
文件,会在init进程启动的时候启动sufaceflinger 进程
perl
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 :
system/core/init
开机启动的第一个进程init 进程
init进程会根据 init.rc
配置启动surfaceflinger
进程,那么看下surfaceflinger的main函数: 主要初始化了SurfaceFlinger实例,然后执行了init
和 run
方法
ini
sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();
......
flinger->init();
......
flinger->run();
看下init方法干了什么:初始化graphics之后,创建了StartPropertySetThread
实例,从名字上翻译"开始属性设置线程",然后调用了Start()方法,线程运行起来了,播放开机动画.
scss
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
......
mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);
if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGE("Run StartPropertySetThread failed!");
}
......
那么线程中干了什么呢:可以看到调用了run
方法,线程运行起来了,threadLoop
相当于线程中执行的方法,可以理解为java中的线程执行调用了start,就会执行runnable方法,这个runnable
和threadLoop
类似。
arduino
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;
}
threadLoop
方法中进行了属性设置,可以看到bootanim,这里可以猜测调用了bootanim.rc
property_set 到底是干什么呢?为什么设置了一个属性就启动了呢?这里就需要看init进程的代码(/system/core/init/init.cpp
),来看init.cpp中的SecondStageMain
函数(注意AOSP10源码中,main函数位于main.cpp中):
scss
......
property_init();
StartPropertyService(&epoll);
......
可以推测StartPropertyService
方法中启动了属性服务,如下代码:可以看到创建了一个socket
,而socket
可以用来跨进程通信 ,那么socket会一直监听属性设置的工作,那么就是在init进程
中创建了一个属性服务的socket
,用来和其他进程间进行通信 ,设置了属性就会监听到作出相应的动作处理(epoll 是一个多路复用的机制,监听fd有没有消息,有消息来就会调用回调的方法后面在详细讲解epoll机制)
ini
void StartPropertyService(Epoll* epoll) {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr);
if (property_set_fd == -1) {
PLOG(FATAL) << "start_property_service socket creation failed";
}
listen(property_set_fd, 8);
if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
}
当监听到有属性设置epoll就会触发property_set_fd
有消息来,就会调用handle_property_set_fd
的回调方法,如下代码:最终会调用HandlePropertySet
方法去处理设置的属性
arduino
static void handle_property_set_fd() {
.......
switch (cmd) {
case PROP_MSG_SETPROP: {
//............
uint32_t result =
HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
//..............
break;
}
.......
}
HandlePropertySet
方法如下:首先判断属性名的前缀ctl.
控制的属性, 在surfaceflinger中的属性名是: property_set("ctl.start", "bootanim")
这里就会匹配到ctl.start
然后执行HandleControlMessage
方法
c
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr, std::string* error) {
if (StartsWith(name, "ctl.")) {
HandleControlMessage(name.c_str() + 4, value, cr.pid);
return PROP_SUCCESS;
}
.......
return PropertySet(name, value, error);
}
HandleControlMessage
方法如下:
传递的msg 的参数name.c_str() + 4
其实就是取属性名向后移动4位的字符串,那么ctl.start
,移动4位就是start
,
name 参数对应的就是value: bootanim
arduino
void HandleControlMessage(const std::string& msg, const std::string& name, pid_t pid) {
const auto& map = get_control_message_map();
const auto it = map.find(msg);
Service* svc = nullptr;
const ControlMessageFunction& function = it->second; //start
switch (function.target) {
case ControlTarget::SERVICE:
svc = ServiceList::GetInstance().FindService(name);
break;
case ControlTarget::INTERFACE:
svc = ServiceList::GetInstance().FindInterface(name);
break;
default:
LOG(ERROR) << "Invalid function target from static map key '" << msg << "': "
<< static_cast<std::underlying_type<ControlTarget>::type>(function.target);
return;
}
//...............
if (svc == nullptr) {
LOG(ERROR) << "Could not find '" << name << "' for ctl." << msg;
return;
}
if (auto result = function.action(svc); !result) {
LOG(ERROR) << "Could not ctl." << msg << " for '" << name << "': " << result.error();
}
}
有个关键的的代码:function
是什么?
ini
const auto& map = get_control_message_map();
const auto it = map.find(msg);
Service* svc = nullptr;
const ControlMessageFunction& function = it->second; //start
get_control_message_map
方法如下其实就是map结构,msg = start
,那么it 对应的就是{"start",{ControlTarget::SERVICE, DoControlStart}}
start
对应{ControlTarget::SERVICE, DoControlStart}}
那么it→second
就是{ControlTarget::SERVICE, DoControlStart}}
c
static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
// clang-format off
static const std::map<std::string, ControlMessageFunction> control_message_functions = {
{"sigstop_on", {ControlTarget::SERVICE,
[](auto* service) { service->set_sigstop(true); return Success(); }}},
{"sigstop_off", {ControlTarget::SERVICE,
[](auto* service) { service->set_sigstop(false); return Success(); }}},
{"start", {ControlTarget::SERVICE, DoControlStart}},
{"stop", {ControlTarget::SERVICE, DoControlStop}},
{"restart", {ControlTarget::SERVICE, DoControlRestart}},
{"interface_start", {ControlTarget::INTERFACE, DoControlStart}},
{"interface_stop", {ControlTarget::INTERFACE, DoControlStop}},
{"interface_restart", {ControlTarget::INTERFACE, DoControlRestart}},
};
// clang-format on
return control_message_functions;
}
function对应的是一个结构体:
arduino
struct ControlMessageFunction {
ControlTarget target;
std::function<Result<Success>(Service*)> action;
};
target ControlTarget::SERVICE
action 就是DoControlStart
其实就是一个函数:调用了service→Start
方法
scss
static Result<Success> DoControlStart(Service* service) {
return service->Start();
}
在回过头来看HandleControlMessage
方法:function.target 对应的ControlTarget::SERVICE
就会执行从ServiceList
中查找Service,name
其实就是传递属性值也就是bootanim
,最终调用了function.action(svc)
,也就是调用了**service->Start()**方法执行启动了开机动画
arduino
switch (function.target) {
case ControlTarget::SERVICE:
svc = ServiceList::GetInstance().FindService(name);
break;
case ControlTarget::INTERFACE:
svc = ServiceList::GetInstance().FindInterface(name);
break;
default:
LOG(ERROR) << "Invalid function target from static map key '" << msg << "': "
<< static_cast<std::underlying_type<ControlTarget>::type>(function.target);
return;
}
///执行
if (auto result = function.action(svc); !result) {
LOG(ERROR) << "Could not ctl." << msg << " for '" << name << "': " << result.error();
}
ServiceList
中为什么能找到bootanim呢?这里就要回看bootanim.rc
文件:我们知道在执行init.rc的时候由于bootanim.rc
配置了disabled 属性并没有启动bootanim 服务,但是会把这个服务记录下来存放到ServieList
中,手动启动
css
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
ioprio rt 0
writepid /dev/stune/top-app/tasks
bootanimation 开机动画绘制图像,依赖于sufaceflinger 所以需要在surfacefligner 启动之后手动调用开机动画进程
详细的流程图总结如下所示:
开机动画结束流程
在上述中清晰的描述了开机动画的启动流程,那么什么时候结束开机动画呢?首先要去看bootanimation模块下的main方法,理清执行流程:
- 首先去查询是否禁用了开机动画,一般Android可以移植到其他设备中,可能有的设备不需要开机动画就会禁止掉
- 启动binder线程池,主要用于binder通信线程和sf(surfacefligner简写)跨进程通信
- 创建BootAnimation的实例,需要注意的是
sp
是一个智能指针 waitForSurfaceFlinger
等待sf进程启动,涉及到屏幕绘制的都需要sf- BootAnimation 继承了Thread 调用run方法运行线程,会执行
readyToRun
和threadloop
方法
scss
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
bool noBootAnimation = bootAnimationDisabled();//开机动画有没有被禁止?
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();//启动线程池 主要binder通信线程和sf跨进程通信
// create the boot animation object (may take up to 200ms for 2MB zip)
sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());//sp 智能指针会执行onFirstRef
waitForSurfaceFlinger();//等待sf进程启动
boot->run("BootAnimation", PRIORITY_DISPLAY);//运行线程,执行 readyToRun 和 threadloop方法
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
BootAnimation 创建实例流程
首先分析BootAnimation的创建实例流程,需要注意的是 sp智能指针一般都会有onFirstRef
方法 在初始化的时候都会执行onFirstRef
方法:
在创建BootAnimation的实例时会先创建binder通信client端代理,然后调用preloadAnimation预加载开机动画文件
scss
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
mSession = new SurfaceComposerClient();//binder通信 client端的代理
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");//获取一个属性判断是开机还是关机
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
}
void BootAnimation::onFirstRef() {//sp<>智能指针 一般都会有onFirstRef方法 在初始化的时候都会执行onFirstRef方法
status_t err = mSession->linkToComposerDeath(this);
if (err == NO_ERROR) {
////..............
preloadAnimation();//预加载开机动画文件
////.................
}
}
如下代码,主要将开机动画文件bootFiles
以及关机动画文件 shutdownFiles
进行查找并将文件名存储到了mZipFileName
的变量中:
arduino
bool BootAnimation::preloadAnimation() {
findBootAnimationFile();
if (!mZipFileName.isEmpty()) {
mAnimation = loadAnimation(mZipFileName);
return (mAnimation != nullptr);
}
return false;
}
void BootAnimation::findBootAnimationFile() {
......................
const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;
//查找开机动画文件的路径
static const char* bootFiles[] =
{APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : 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;
}
}
}
从上述代码中可推断出开机动画的文件在哪里配置的:这些有助于自定义开机动画的zip配置
arduino
static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
执行线程
由于BootAnimation继承了Thread 所以可以调用run方法开始线程执行
kotlin
class BootAnimation : public Thread, public IBinder::DeathRecipient
肯定会执行 readyToRun
和 threadloop
方法,先执行readyToRun
方法:
主要是和sf跨进程通信构建一个画布,初始化opengl,用于在屏幕上绘制开机动画.
ini
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();//对assets文件的初始化
mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();//跨进程通信 用sf获取屏幕的display信息
if (mDisplayToken == nullptr)
return -1;
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(mDisplayToken, &dinfo);
if (status)
return -1;
//利用DisplayInfo 构建一个画布
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::Transaction t;
t.setLayer(control, 0x40000000)
.apply();
//获取一个画布 绘制
sp<Surface> s = control->getSurface();
// OPENGL 初始化
// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
..........
return NO_ERROR;
}
然后再看threadloop
方法:如下代码在创建BootAnimation实例的时候,通过调用onFirstRef方法预加载了开机动画文件,并且将文件地址复制给mZipFileName
,这里先看mZipFileName
为空的时候,因为在编译源码的时候并没有设置开机动画bootanimation.zip
所以mZipFileName
肯定是空的。
scss
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();
}
}
下面看默认的开机动画是如何实现的 android()
方法如下:
从代码中可以看出其实就会通过opengl进行绘制,do-while循环不断的绘制,chekExit方法检查是否退出开机动画
scss
bool BootAnimation::android()
{
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
mCallbacks->init({});
......
const nsecs_t startTime = systemTime();
do {
.............
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
. ........
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
退出开机动画
那么如何检查退出开机动画呢?checkExit
方法如下:其实就是获取EXIT_PROP_NAME
的属性值,如果为1就会退出
开机动画结束的标志是系统的首个 Activity(通常是 Launcher)进入空闲状态,即
Activity Idle
事件被触发时。这个事件会通知ActivityManagerService
,然后通过WindowManagerService
终止开机动画
scss
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");//EXIT_PROP_NAME 属性值为1的时候会进行退出
int exitnow = atoi(value);
if (exitnow) {
requestExit();
mCallbacks->shutdown();
}
}
通过grep命令搜索,在frameworks目录中搜索更快,哪里设置了service.bootanim.exit
这个属性值:
perl
grep "service.bootanim.exit" ./ -rn
在StartPropertySetThread 属性service.bootanim.exit
设置成了0
表示了开机动画启动
arduino
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;
}
在WindowsManagerService
中performEnableScreen
方法:service.bootanim.exit
设置为了1,同时通过binder 跨进程通信调用了SurfaceFlinger
中的方法执行关闭操作,同时在SurfaceFlinger
也设置了1,其实两次设置并没有特别的意义,wms只是设置了属性值为1,在sf中时调用了一系列的退出开机动画的操作
kotlin
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
Log.d("lsm2","WindowManagerService performEnableScreen Stop bootanim",new Exception());
}
..........
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!");
}
如何跨进程调用到sf进程呢?IBinder.FIRST_CALL_TRANSACTION
,可以在ISurfaceComposer.h
看到这个赋值给了BOOT_FINISHED
kotlin
class BnSurfaceComposer: public BnInterface<ISurfaceComposer>{
public:
enum ISurfaceComposerTag {
BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,
........
}
.........
}
在ISurfaceComposer.cpp
中的onTransact
接受跨进程传递的信息:
kotlin
status_t BnSurfaceComposer::onTransact(){
.....
case BOOT_FINISHED: {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
bootFinished();
return NO_ERROR;
}
......
}
class SurfaceFlinger : public BnSurfaceComposer,
在SurfaceFlinger
继承了BnSurfaceComposer
,最终会调用到Surfaceflinger.cpp
中的bootFinished
:
javascript
void SurfaceFlinger::bootFinished()
{
........ 一系列的操作
property_set("service.bootanim.exit", "1");
.......
}
那么整体的退出开机动画就清晰了,其实就是设置service.bootanim.exit
属性值为1就会退出开机动画,那么还有一个问题通过wms中的performEnableScreen方法才会设置这个属性值,如何触发的呢?可以加入java的日志调用栈分析Android 系统中调试追踪方法以Activity的启动流程为例
日志分析:
- 进入Activity第一次进入idle状态
- 跨进程调用到了AMS中,执行了
postFinishBooting
方法 - 跨进程调用了WMS中的
enableScreenAfterBoot
方法 - 调用了
performEnableScreen
设置停止开机动画属性值为1 ,并且跨进程调用了SurfaceFlinger
的bootFinished()
方法
php
//1. 进入idle
09-23 14:04:03.465 1602 2169 D lsm2 : Activity idle
09-23 14:04:03.465 1602 2169 D lsm2 : java.lang.Exception
09-23 14:04:03.465 1602 2169 D lsm2 : at com.android.server.wm.ActivityStackSupervisor.activityIdleInternalLocked(ActivityStackSupervisor.java:1359)
09-23 14:04:03.465 1602 2169 D lsm2 : at com.android.server.wm.ActivityTaskManagerService.activityIdle(ActivityTaskManagerService.java:1675)
09-23 14:04:03.465 1602 2169 D lsm2 : at android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:1957)
09-23 14:04:03.465 1602 2169 D lsm2 : at android.os.Binder.execTransactInternal(Binder.java:1021)
09-23 14:04:03.465 1602 2169 D lsm2 : at android.os.Binder.execTransact(Binder.java:994)
//2. 跨进程调用到AMS中,执行了postFinishBooting
09-23 14:04:03.465 1602 1726 D lsm2 : postFinishBooting finishBooting true enableScreen true
09-23 14:04:03.465 1602 1726 D lsm2 : java.lang.Exception
09-23 14:04:03.465 1602 1726 D lsm2 : at com.android.server.wm.ActivityTaskManagerService.lambda$postFinishBooting$6$ActivityTaskManagerService(ActivityTaskManagerService.java:5675)
09-23 14:04:03.465 1602 1726 D lsm2 : at com.android.server.wm.-$$Lambda$ActivityTaskManagerService$oP6xxIfnD4kb4JN7aSJU073ULR4.run(Unknown Source:6)
09-23 14:04:03.465 1602 1726 D lsm2 : at android.os.Handler.handleCallback(Handler.java:883)
09-23 14:04:03.465 1602 1726 D lsm2 : at android.os.Handler.dispatchMessage(Handler.java:100)
09-23 14:04:03.465 1602 1726 D lsm2 : at android.os.Looper.loop(Looper.java:214)
09-23 14:04:03.465 1602 1726 D lsm2 : at android.os.HandlerThread.run(HandlerThread.java:67)
09-23 14:04:03.465 1602 1726 D lsm2 : at com.android.server.ServiceThread.run(ServiceThread.java:44)
//3. 跨进程调用了WMS中的enableScreenAfterBoot 方法
09-23 14:04:03.473 1602 1726 D lsm2 : enableScreenAfterBoot -> performEnableScreen
09-23 14:04:03.473 1602 1726 D lsm2 : java.lang.Exception
09-23 14:04:03.473 1602 1726 D lsm2 : at com.android.server.wm.WindowManagerService.enableScreenAfterBoot(WindowManagerService.java:3242)
09-23 14:04:03.473 1602 1726 D lsm2 : at com.android.server.wm.ActivityTaskManagerService$LocalService.enableScreenAfterBoot(ActivityTaskManagerService.java:6485)
09-23 14:04:03.473 1602 1726 D lsm2 : at com.android.server.wm.ActivityTaskManagerService.lambda$postFinishBooting$6$ActivityTaskManagerService(ActivityTaskManagerService.java:5680)
09-23 14:04:03.473 1602 1726 D lsm2 : at com.android.server.wm.-$$Lambda$ActivityTaskManagerService$oP6xxIfnD4kb4JN7aSJU073ULR4.run(Unknown Source:6)
09-23 14:04:03.473 1602 1726 D lsm2 : at android.os.Handler.handleCallback(Handler.java:883)
09-23 14:04:03.473 1602 1726 D lsm2 : at android.os.Handler.dispatchMessage(Handler.java:100)
09-23 14:04:03.473 1602 1726 D lsm2 : at android.os.Looper.loop(Looper.java:214)
09-23 14:04:03.473 1602 1726 D lsm2 : at android.os.HandlerThread.run(HandlerThread.java:67)
09-23 14:04:03.473 1602 1726 D lsm2 : at com.android.server.ServiceThread.run(ServiceThread.java:44)
//4. 调用了performEnableScreen 设置停止开机动画属性值为1,并且跨进程调用了SurfaceFlinger
09-23 14:04:04.248 1602 1726 D lsm2 : WindowManagerService performEnableScreen Stop bootanim
09-23 14:04:04.248 1602 1726 D lsm2 : java.lang.Exception
09-23 14:04:04.248 1602 1726 D lsm2 : at com.android.server.wm.WindowManagerService.performEnableScreen(WindowManagerService.java:3322)
09-23 14:04:04.248 1602 1726 D lsm2 : at com.android.server.wm.WindowManagerService.access$1100(WindowManagerService.java:288)
09-23 14:04:04.248 1602 1726 D lsm2 : at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:4743)
09-23 14:04:04.248 1602 1726 D lsm2 : at android.os.Handler.dispatchMessage(Handler.java:107)
09-23 14:04:04.248 1602 1726 D lsm2 : at android.os.Looper.loop(Looper.java:214)
09-23 14:04:04.248 1602 1726 D lsm2 : at android.os.HandlerThread.run(HandlerThread.java:67)
09-23 14:04:04.248 1602 1726 D lsm2 : at com.android.server.ServiceThread.run(ServiceThread.java:44)
整个开机动画的完整流程如下图所示: