android深入了解开机动画
开机动画的种类
1:View绘制
2:逐帧动画:比较主流的方式,一般动画的文件打包成 bootanimation.zip 存储到 /system/media/ 下。一般.zip文件 > 5M 就会有明显的卡顿,所以一般开机动画只有中间一块有图案,四周是黑色,这样可以采用更小分辨率的图片。
3:OpenGL:定义了跨平台语言,跨平台的应用接口API规范,用于生成二维、三维图像。
开机动画启动流程
- 代码路径介绍
- bootanimation: /frameworks/base/cmds/bootanimation/
- surfaceflinger: /frameworks/native/services/surfaceflinger/
- init: /system/core/init/
内核启动之后启动的第一个进程就是 init 进程,init进程会根据 init.rc 的配置启动 surfaceflinger 进程,在/frameworks/native/services/surfaceflinger/ 下有 surfaceflinger.rc 如下:
1 service surfaceflinger /system/bin/surfaceflinger
2 class core animation
3 user system
4 group graphics drmrpc readproc
5 onrestart restart zygote
6 writepid /dev/stune/foreground/tasks
而 bootanim.rc 配置了 disabled ,因此在 init 进程解析时不会启动 bootanimation 进程。
1 service bootanim /system/bin/bootanimation
2 class core animation
3 user graphics
4 group graphics audio
5 disabled
6 oneshot
7 writepid /dev/stune/top-app/tasks
- surfaceflinger 进程启动以后,执行了 main_surfaceflinger.cpp 的 main 方法。
71 int main(int, char**) {// ..............
83 // start the thread pool
84 sp<ProcessState> ps(ProcessState::self());
85 ps->startThreadPool();
86
87 // instantiate surfaceflinger 创建 SurfaceFlinger 实例
88 sp<SurfaceFlinger> flinger = new SurfaceFlinger();
89
99 // 调用 flinger 的 init()
100 flinger->init();
101
102 // publish surface flinger 将 flinger 服务添加到 ServiceManager 中。
103 sp<IServiceManager> sm(defaultServiceManager());
104 sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
105 IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
106
107 // publish GpuService
108 sp<GpuService> gpuservice = new GpuService();
109 sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
110
119 // run surface flinger in this thread 执行了 run() 方法
120 flinger->run();
121
122 return 0;
123 }
main_surfaceflinger.cpp 的 main 方法。,创建了 SurfaceFlinger 实例,并执行了其 init() 和 run() 方法。在 init() 函数中,有如下相关代码
637 void SurfaceFlinger::init() {
724 if (getHwComposer().hasCapability(
725 HWC2::Capability::PresentFenceIsNotReliable)) {
726 mStartPropertySetThread = new StartPropertySetThread(false);
727 } else {
728 mStartPropertySetThread = new StartPropertySetThread(true);
729 }
731 if (mStartPropertySetThread->Start() != NO_ERROR) {
732 ALOGE("Run StartPropertySetThread failed!");
733 }
739 }
创建了一个 Thread 并且调用start() , 运行起来该线程以后看一看做了什么工作。接下来,看一下 StartPropertySetThread.cpp 的代码。
17#include <cutils/properties.h>
18#include "StartPropertySetThread.h"
19
20namespace android {
21
22StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
23 Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
24
25status_t StartPropertySetThread::Start() {
26 return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
27}
28
29bool StartPropertySetThread::threadLoop() {
30 // Set property service.sf.present_timestamp, consumer need check its readiness
31 property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
32 // Clear BootAnimation exit flag 这个不是结束,就是设置初始状态
33 property_set("service.bootanim.exit", "0");
34 // Start BootAnimation if not started 开启 BootAnimation 进程
35 property_set("ctl.start", "bootanim");
36 // Exit immediately
37 return false;
38}
39
40} // namespace android
41
| property_set(“”, “”); 函数是Android框架中的一个函数,用于设置系统属性的值。系统属性是一种用于存储系统配置信息的键值对。property_set()函数的作用是将指定的属性名和属性值存储到系统属性中,并且进行后续动作和调整。
在之前提到的 /system/core/init/init.cpp 中,执行 main 函数有下面的代码
545 int main(int argc, char** argv) {701 property_load_boot_defaults();
702 export_oem_lock_status();
703 start_property_service(); // 关键代码 启动监听 property 的服务
704 set_usb_controller();790 }
start_property_service(); 调用的是 /system/core/init/property_service.cpp 下的 start_property_service () 函数如下:
840 void start_property_service() {
841 selinux_callback cb;
842 cb.func_audit = SelinuxAuditCallback;
843 selinux_set_callback(SELINUX_CB_AUDIT, cb);
844
845 property_set("ro.property_service.version", "2");
846 // 创建了 Socket 来进行跨进程通信。
847 property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
848 false, 0666, 0, 0, nullptr);
849 if (property_set_fd == -1) {
850 PLOG(FATAL) << "start_property_service socket creation failed";
851 }
852 // 监听 property_set_fd 这个 fd
853 listen(property_set_fd, 8);
854 // 通过 epoll 机智,来监听 property_set_fd 是否有消息过来,如果有消息进来就会回调 handle_property_set_fd 方法。
855 register_epoll_handler(property_set_fd, handle_property_set_fd);
856 }
857
858 }
上面讲述了,监听到 property_set 以后,会调用到 handle_property_set_fd 方法,接下来看看,设置了 property_set(“ctl.start”, “bootanim”); 后执行流程是怎么样的。
481 static void handle_property_set_fd() {
482
506
507 switch (cmd) {
508 case PROP_MSG_SETPROP: {
509 char prop_name[PROP_NAME_MAX];
510 char prop_value[PROP_VALUE_MAX];
521 const auto& cr = socket.cred();
522 std::string error;// 主要代码
523 uint32_t result =
524 HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
531 break;
532 }
533
534 case PROP_MSG_SETPROP2: {
535 std::string name;
536 std::string value;
546 uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
553 break;
554 }
555
556 default:
557 LOG(ERROR) << "sys_prop: invalid command " << cmd;
558 socket.SendUint32(PROP_ERROR_INVALID_CMD);
559 break;
560 }
561}
上述代码,调用时,我们是设置属性,最终会调用到 HandlePropertySet() 函数,
425// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
426 uint32_t HandlePropertySet(const std::string& name, const std::string& value,
427 const std::string& source_context, const ucred& cr, std::string* error) {
428 if (!IsLegalPropertyName(name)) {
429 *error = "Illegal property name";
430 return PROP_ERROR_INVALID_NAME;
431 }
432 // 上面调用的是 property_set("ctl.start", "bootanim"); 满足 StartsWith(name, "ctl.") 条件
433 if (StartsWith(name, "ctl.")) {
434 if (!CheckControlPropertyPerms(name, value, source_context, cr)) {
435 *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
436 value.c_str());
437 return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
438 }
439 // 调用到了 HandleControlMessage()
440 HandleControlMessage(name.c_str() + 4, value, cr.pid);
441 return PROP_SUCCESS;
442 }
443
478 return PropertySet(name, value, error);
479}
刚开始设置的 property_set(“ctl.start”, “bootanim”); 在这个方法中,满足 StartsWith(name, “ctl.”) 条件,最后调用到了 HandleControlMessage() 函数,
253 void HandleControlMessage(const std::string& msg, const std::string& name, pid_t pid) {
254 const auto& map = get_control_message_map();
255 const auto it = map.find(msg); // 通过 msg 查找 这里,msg 是 start
256
257 if (it == map.end()) {
258 LOG(ERROR) << "Unknown control msg '" << msg << "'";
259 return;
260 }
261
262 std::string cmdline_path = StringPrintf("proc/%d/cmdline", pid);
263 std::string process_cmdline;
264 if (ReadFileToString(cmdline_path, &process_cmdline)) {
265 std::replace(process_cmdline.begin(), process_cmdline.end(), '\0', ' ');
266 process_cmdline = Trim(process_cmdline);
267 } else {
268 process_cmdline = "unknown process";
269 }
270
271 LOG(INFO) << "Received control message '" << msg << "' for '" << name << "' from pid: " << pid
272 << " (" << process_cmdline << ")";
273
274 const ControlMessageFunction& function = it->second;
275
276 if (function.target == ControlTarget::SERVICE) {
277 Service* svc = ServiceList::GetInstance().FindService(name);
278 if (svc == nullptr) {
279 LOG(ERROR) << "No such service '" << name << "' for ctl." << msg;
280 return;
281 }
282 if (auto result = function.action(svc); !result) {
283 LOG(ERROR) << "Could not ctl." << msg << " for service " << name << ": "
284 << result.error();
285 }
286
287 return;
288 }
289
290 if (function.target == ControlTarget::INTERFACE) {
291 for (const auto& svc : ServiceList::GetInstance()) {
292 if (svc->interfaces().count(name) == 0) {
293 continue;
294 }
295
296 if (auto result = function.action(svc.get()); !result) {
297 LOG(ERROR) << "Could not handle ctl." << msg << " for service " << svc->name()
298 << " with interface " << name << ": " << result.error();
299 }
300
301 return;
302 }
303
304 LOG(ERROR) << "Could not find service hosting interface " << name;
305 return;
306 }
name 传入的是 bootanim ,get_control_message_map() 传入的 msg = start,找到对应的 Service 后,调用 function(svc) , function 下面对应的 Start ,最后调用了 DoControlStart 也就是 service -> start();
238static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
239 // clang-format off
240 static const std::map<std::string, ControlMessageFunction> control_message_functions = {
241 {"start", {ControlTarget::SERVICE, DoControlStart}},
242 {"stop", {ControlTarget::SERVICE, DoControlStop}},
243 {"restart", {ControlTarget::SERVICE, DoControlRestart}},
244 {"interface_start", {ControlTarget::INTERFACE, DoControlStart}},
245 {"interface_stop", {ControlTarget::INTERFACE, DoControlStop}},
246 {"interface_restart", {ControlTarget::INTERFACE, DoControlRestart}},
247 };
248 // clang-format on
249
250 return control_message_functions;
251}
215 return service->Start();
216}
-
动画进程的启动流程就结束了,总结一下启动流程:
init 进程启动 -> surfaceflinger进程启动,执行 init() 方法 -> 启动 StartPropertySetThread 线程的执行,修改了 property_set(“ctl.start”, “bootanim”); -> 监听到 setprop 以后,查找到对应的 Service 并且启动该服务。
开机动画的结束流程
上面说到了开机动画启动,启动后会执行动画进程的[bootanimation_main.cpp](http://androidxref.com/9.0.0_r3/xref/frameworks/base/cmds/bootanimation/bootanimation_main.cpp) 的 main() 函数。
145int main()
146{
147 setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
148 // 开机动画是否被禁止,这个一般是不禁止的,内部就是通过 property_get 读取参数是否设置禁止。
149 bool noBootAnimation = bootAnimationDisabled();
150 ALOGI_IF(noBootAnimation, "boot animation disabled");
151 if (!noBootAnimation) {
152 // 进程相关的类, 为了获取线程池初始化服务
153 sp<ProcessState> proc(ProcessState::self());// 启动线程池,里面初始化了一些Binder相关内容进行跨进程通信
154 ProcessState::self()->startThreadPool();
155 // 因为依赖 SurfaceFlinger 所以需要等待
156 waitForSurfaceFlinger();
157
158 // 关键 new 了BootAnimation 对象。
159 sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
160 ALOGV("Boot animation set up. Joining pool.");
161
162 IPCThreadState::self()->joinThreadPool();
163 }
164 ALOGV("Boot animation exit");
165 return 0;
166}
167
上面代码 sp <BootAnimation>
boot = new BootAnimation(new AudioAnimationCallbacks()); 创建了 BootAnimation.cpp 对象,所以看一下 BootAnimation 中干了什么事情
102 BootAnimation::BootAnimation(sp<Callbacks> callbacks)
103 : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
104 mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) {// 创建了 SurfaceComposerClient ,用来和 SurfaceFlinger 进行通信
105 mSession = new SurfaceComposerClient();
106 // 获取了系统变量
107 std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
108 if (powerCtl.empty()) {
109 mShuttingDown = false;
110 } else {
111 mShuttingDown = true;
112 }
113 }
有个点上面代码创建 sp <BootAnimation>
boot = new BootAnimation(); 对象是通过 sp<> 的方式,这种方式在对象初始化以后会调用 ::onFirstRef() 对象。接下来看一下 BootAnimation.cpp 中的 ::onFirstRef()
115 void BootAnimation::onFirstRef() {
116 status_t err = mSession->linkToComposerDeath(this);
117 ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
118 if (err == NO_ERROR) {// 执行了 run 方法
119 run("BootAnimation", PRIORITY_DISPLAY);
120 }
121 }
122
onFirstRef() 中,执行了 run 方法。有 BootAnimation 继承了 Thread,所以有 run 方法。调用该方法先回调 readyToRun() 方法,然后调用 threadLoop。接下来看看 readyToRun()
252status_t BootAnimation::readyToRun() {
253 mAssets.addDefaultAssets(); // 对 assets文件进行一些初始化
254 // 这部分是初始化屏幕相关的 Binder ,通过Binder获取 display 相关信息
255 sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
256 ISurfaceComposer::eDisplayIdMain));
257 DisplayInfo dinfo;
258 status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
259 if (status)
260 return -1;
261
262 // create the native surface 这部分是创建一个画布,用来绘制动画
263 sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
264 dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
265
266 SurfaceComposerClient::Transaction t;
267 t.setLayer(control, 0x40000000)
268 .apply();
269
270 sp<Surface> s = control->getSurface();
271
272 // initialize opengl and egl 初始化 OpenGL
273 const EGLint attribs[] = {
274 EGL_RED_SIZE, 8,
275 EGL_GREEN_SIZE, 8,
276 EGL_BLUE_SIZE, 8,
277 EGL_DEPTH_SIZE, 0,
278 EGL_NONE
279 };
280 EGLint w, h;
281 EGLint numConfigs;
282 EGLConfig config;
283 EGLSurface surface;
284 EGLContext context;
285
286 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
287
288 eglInitialize(display, 0, 0);
289 eglChooseConfig(display, attribs, &config, 1, &numConfigs);
290 surface = eglCreateWindowSurface(display, config, s.get(), NULL);
291 context = eglCreateContext(display, config, NULL, NULL);
292 eglQuerySurface(display, surface, EGL_WIDTH, &w);
293 eglQuerySurface(display, surface, EGL_HEIGHT, &h);
294
295 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
296 return NO_INIT;
297
298 mDisplay = display;
299 mContext = context;
300 mSurface = surface;
301 mWidth = w;
302 mHeight = h;
303 mFlingerSurfaceControl = control;
304 mFlingerSurface = s;
305
306 // If the device has encryption turned on or is in process
307 // of being encrypted we show the encrypted boot animation.
308 char decrypt[PROPERTY_VALUE_MAX];
309 property_get("vold.decrypt", decrypt, "");
310 // 看描述是加密的开机动画 一般也不用这种
311 bool encryptedAnimation = atoi(decrypt) != 0 ||
312 !strcmp("trigger_restart_min_framework", decrypt);
313
314 if (!mShuttingDown && encryptedAnimation) {
315 static const char* encryptedBootFiles[] =
316 {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE};
317 for (const char* f : encryptedBootFiles) {
318 if (access(f, R_OK) == 0) {
319 mZipFileName = f;
320 return NO_ERROR;
321 }
322 }
323 }// OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
69 // PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
70 // SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";// 这三个都是对应的 bootanimation.zip 文件,这就是上面提到的替换对应的zip文件就可以更换动画
324 static const char* bootFiles[] =
325 {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};// 关机动画
326 static const char* shutdownFiles[] =
327 {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE};
328 // 判断是否存在file,存在则直接赋值了
329 for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) {
330 if (access(f, R_OK) == 0) {
331 mZipFileName = f;
332 return NO_ERROR;
333 }
334 }
335 return NO_ERROR;
336}
readyToRun() 以后,接下来看 threadLoop() 中是怎么执行的。
338 bool BootAnimation::threadLoop()
339 {
340 bool r;
341 // We have no bootanimation file, so we use the stock android logo
342 // animation.// 之前赋值的变量,如果是空调用 android()
343 if (mZipFileName.isEmpty()) {
344 r = android();
345 } else {// 如果不是空,则放映该动画
346 r = movie();
347 }
348 // 下面就是收尾 结束释放各种资源了
349 eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
350 eglDestroyContext(mDisplay, mContext);
351 eglDestroySurface(mDisplay, mSurface);
352 mFlingerSurface.clear();
353 mFlingerSurfaceControl.clear();
354 eglTerminate(mDisplay);
355 eglReleaseThread();
356 IPCThreadState::self()->stopProcess();
357 return r;
358 }
上面代码主要是如果 bootanimation.zip 是空的话,则走android开机动画,否则播放 bootanimation.zip 中的动画。android() 就是通过 openGL 创建纹理,然后不断绘制。重点是 OpenGL绘制到什么时候结束动画。看下面代码
360 bool BootAnimation::android()
361 {
362 ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
363 elapsedRealtime());
364 initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
365 initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
366
367 mCallbacks->init({});
368
369 // ... 初始化 OpenGL 一些代码省略
390
391 const nsecs_t startTime = systemTime();
392 do {
393 //... 代码省略 这部分是 OpenGL 绘制动画的代码
421 checkExit();
422 } while (!exitPending());
423
424 glDeleteTextures(1, &mAndroid[0].name);
425 glDeleteTextures(1, &mAndroid[1].name);
426 return false;
427 }
上面的代码可以看出,调用 android() 以后,开启了 do{} while() 循环,退出循环的关键就在于 !exitPending() 和 checkExit(); 两个方法。接下来看一下 checkExit(); 方法的代码。
429 void BootAnimation::checkExit() {
430 // Allow surface flinger to gracefully request shutdown
431 char value[PROPERTY_VALUE_MAX];
432 property_get(EXIT_PROP_NAME, value, "0");
433 int exitnow = atoi(value);
434 if (exitnow) {
435 requestExit();
436 mCallbacks->shutdown();
437 }
438 }
上面的代码就是一直去查看系统属性 EXIT_PROP_NAME ,如果满足则请求退出动画,这个属性定义如下
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
那么,这个属性是谁设置的呢?通过检索 grep “service.bootanim.exit” ./ -rn 这个属性的设置可以发现以下几个位置
设置为退出的主要有两个位置,一个是其中的 SurfaceFlinger.cpp 中设置了属性 1 ,还有一个就是 WMS 中设置了1 。
AMS 中,只要有Activity已经起来,并且处于 idle 状态了就会调用到 WMS中的 performEnableScreen() 中,在这个方法设置了关闭动画的属性。
private void performEnableScreen() {
3413 synchronized(mWindowMap) {
3414 // ....
3437
3438 if (!mBootAnimationStopped) {
3439 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
3440 // stop boot animation
3441 // formerly we would just kill the process, but we now ask it to exit so it
3442 // can choose where to stop the animation.// 关闭属性
3443 SystemProperties.set("service.bootanim.exit", "1");
3444 mBootAnimationStopped = true;
3445 }try {
3453 IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
3454 if (surfaceFlinger != null) {
3455 Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
3456 Parcel data = Parcel.obtain();
3457 data.writeInterfaceToken("android.ui.ISurfaceComposer");
3458 surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
3459 data, null, 0);
3460 data.recycle();
3461 }
3462 } catch (RemoteException ex) {
3463 Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
3464 }
在 surfaceFlinger 中设置 service.bootanim.exit 属性的代码实际上也是通过 WMS 通过 Binder 跨进程调用过去的。也是再上面的 performEnableScreen() 方法中。surfaceFlinger.transact() 函数调用就是能让底层修改动画属性的代码。
开机动画 zip 包的解析原理
在 framework 源码中的 /frameworks/base/cmds/bootanimation/FORMAT.md 已经定义了 ZIP 文件的规则,关键信息如下:
1# bootanimation format
2
3## zipfile paths
4
5The system selects a boot animation zipfile from the following locations, in order:
6 // zip 文件存储的路径
7 /system/media/bootanimation-encrypted.zip (if getprop("vold.decrypt") = '1')
8 /system/media/bootanimation.zip
9 /oem/media/bootanimation.zip
10
11## zipfile layout
12
13The `bootanimation.zip` archive file includes:
14 // bootanimation.zip 的结构如下
15 desc.txt - a text file // 需要有一个 desc.txt 的一个 txt 文件
16 part0 \
17 part1 \ directories full of PNG frames // 图片的文件路径,我们最好也命名成 part0 1 2 3 ,也可以自己修改名字。
18 ... /
19 partN /
20
21## desc.txt format
22 // desc.txt 文件的格式
23The first line defines the general parameters of the animation:
24 // 第一行 宽 高 FPS ,就是图片的宽高,以及播放的帧率 60就可以
25 WIDTH HEIGHT FPS
26
27 * **WIDTH:** animation width (pixels)
28 * **HEIGHT:** animation height (pixels)
29 * **FPS:** frames per second, e.g. 60
30 // 下面是第二行的含义
31It is followed by a number of rows of the form:
32
33 TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]]
34 // type 就是代表是否可以被打断,在 boot启动以后,一般都是 C
35 * **TYPE:** a single char indicating what type of animation segment this is:
36 + `p` -- this part will play unless interrupted by the end of the boot
37 + `c` -- this part will play to completion, no matter what// count 是循环次数,0 就是一直循环直到对应属性更改
38 * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete// pause 是一个part循环以后是否停留,0 就是不停留继续下个part循环,1 就是停留 1帧 ,如果按照 60fps 就是停留 16ms
39 * **PAUSE:** number of FRAMES to delay after this part ends// 就是上面的 part0 1 2 3 4 的位置
40 * **PATH:** directory in which to find the frames for this part (e.g. `part0`)// 可选 背景的颜色 RGB的值
41 * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`// 可选 坐标位置 展示当前时间
42 * **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches):
43 + If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate
44 defaults to `c`
45 + If both `CLOCK1` and `CLOCK2` are provided then `CLOCK1` is the x-coordinate and `CLOCK2` is
46 the y-coodinate
47 + Values can be either a positive integer, a negative integer, or `c`
48 - `c` -- will centre the text
49 - `n` -- will position the text n pixels from the start; left edge for x-axis, bottom edge
50 for y-axis
51 - `-n` -- will position the text n pixels from the end; right edge for x-axis, top edge
52 for y-axis
53 - Examples:
54 * `-24` or `c -24` will position the text 24 pixels from the top of the screen,
55 centred horizontally
56 * `16 c` will position the text 16 pixels from the left of the screen, centred
57 vertically
58 * `-32 32` will position the text such that the bottom right corner is 32 pixels above
59 and 32 pixels left of the edges of the screen
60
61There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip`
62and plays that.
63
64## clock_font.png
65
66The file used to draw the time on top of the boot animation. The font format is as follows:
67 * The file specifies glyphs for the ascii characters 32-127 (0x20-0x7F), both regular weight and
68 bold weight.
69 * The image is divided into a grid of characters
70 * There are 16 columns and 6 rows
71 * Each row is divided in half: regular weight glyphs on the top half, bold glyphs on the bottom
72 * For a NxM image each character glyph will be N/16 pixels wide and M/(12*2) pixels high
73
74## loading and playing frames
75
76Each part is scanned and loaded directly from the zip archive. Within a part directory, every file
77(except `trim.txt` and `audio.wav`; see next sections) is expected to be a PNG file that represents
78one frame in that part (at the specified resolution). For this reason it is important that frames be
79named sequentially (e.g. `part000.png`, `part001.png`, ...) and added to the zip archive in that
80order.
81
82## trim.txt
83
84To save on memory, textures may be trimmed by their background color. trim.txt sequentially lists
85the trim output for each frame in its directory, so the frames may be properly positioned.
86Output should be of the form: `WxH+X+Y`. Example:
87
88 713x165+388+914
89 708x152+388+912
90 707x139+388+911
91 649x92+388+910
92
93If the file is not present, each frame is assumed to be the same size as the animation.
94
95## audio.wav
96
97Each part may optionally play a `wav` sample when it starts. To enable this, add a file
98with the name `audio.wav` in the part directory.
99
100## exiting
101
102The system will end the boot animation (first completing any incomplete or even entirely unplayed
103parts that are of type `c`) when the system is finished booting. (This is accomplished by setting
104the system property `service.bootanim.exit` to a nonzero string.)
105
106## protips
107
108### PNG compression
109
110Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.:
111
112 for fn in *.png ; do
113 zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn}
114 # or: pngcrush -q ....
115 done
116
117Some animations benefit from being reduced to 256 colors:
118
119 pngquant --force --ext .png *.png
120 # alternatively: mogrify -colors 256 anim-tmp/*/*.png
121
122### creating the ZIP archive
123
124 cd <path-to-pieces>
125 zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part*
126
127Note that the ZIP archive is not actually compressed! The PNG files are already as compressed
128as they can reasonably get, and there is unlikely to be any redundancy between files.
129
- 上面讲到过 threadLoop() 中如果满足播放 zip 的条件,调用 BootAnimation::movie() 函数,其内部调用了 loadAnimation() 函数
1001BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
1002{
1003 if (mLoadedFiles.indexOf(fn) >= 0) {
1004 ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
1005 fn.string());
1006 return NULL;
1007 }// 先打开 zip file
1008 ZipFileRO *zip = ZipFileRO::open(fn);
1009 if (zip == NULL) {
1010 ALOGE("Failed to open animation zip \"%s\": %s",
1011 fn.string(), strerror(errno));
1012 return NULL;
1013 }
1014 // 赋值各种参数
1015 Animation *animation = new Animation;
1016 animation->fileName = fn;
1017 animation->zip = zip;
1018 animation->clockFont.map = nullptr;
1019 mLoadedFiles.add(animation->fileName);
1020 // 解析 Desc.txt 文件, 并且解析好的信息存储在 animation 中
1021 parseAnimationDesc(*animation);// 然后加载zip
1022 if (!preloadZip(*animation)) {
1023 return NULL;
1024 }
1025
1026
1027 mLoadedFiles.remove(fn);
1028 return animation;
1029}
loadAnimation 主要是解析并且加载 zip 文件,首先看看如何解析的文件。parseAnimationDesc(*animation);
629bool BootAnimation::parseAnimationDesc(Animation& animation)
630{
631 String8 desString;
632 // 如果 desc.txt 文件不存在则返回 false
633 if (!readFile(animation.zip, "desc.txt", desString)) {
634 return false;
635 }
636 char const* s = desString.string();
637 // for(;;) 逐行读取
638 // Parse the description file
639 for (;;) {
640 const char* endl = strstr(s, "\n");// 读取到最后就结束循环
641 if (endl == NULL) break;
642 String8 line(s, endl - s);
643 const char* l = line.string();
644 int fps = 0;
645 int width = 0;
646 int height = 0;
647 int count = 0;
648 int pause = 0;
649 char path[ANIM_ENTRY_NAME_MAX];
650 char color[7] = "000000"; // default to black if unspecified
651 char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
652 char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
653
654 char pathType;// 先读取 width height 和 fps ,读取后赋值到 animation
655 if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
656 // ALOGD("> w=%d, h=%d, fps=%d", width, height, fps);
657 animation.width = width;
658 animation.height = height;
659 animation.fps = fps;// 读取 &pathType, &count, &pause, path, color, clockPos1, clockPos2 只要保证读取数量 >= 4 也就是保证前四个有值就可以// 解析后保存到 animation 的 parts 对象中。
660 } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s",
661 &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
662 //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
663 // pathType, count, pause, path, color, clockPos1, clockPos2);
664 Animation::Part part;
665 part.playUntilComplete = pathType == 'c';
666 part.count = count;
667 part.pause = pause;
668 part.path = path;
669 part.audioData = NULL;
670 part.animation = NULL;
671 if (!parseColor(color, part.backgroundColor)) {
672 ALOGE("> invalid color '#%s'", color);
673 part.backgroundColor[0] = 0.0f;
674 part.backgroundColor[1] = 0.0f;
675 part.backgroundColor[2] = 0.0f;
676 }
677 parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY);
678 animation.parts.add(part);
679 }
680 else if (strcmp(l, "$SYSTEM") == 0) {
681 // ALOGD("> SYSTEM");
682 Animation::Part part;
683 part.playUntilComplete = false;
684 part.count = 1;
685 part.pause = 0;
686 part.audioData = NULL;
687 part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE));
688 if (part.animation != NULL)
689 animation.parts.add(part);
690 }
691 s = ++endl;
692 }
693
694 return true;
695}
解析好了Desc.txt 以后,就可以解析到具体的文件了。
bool BootAnimation::preloadZip(Animation& animation)
697bool BootAnimation::preloadZip(Animation& animation)
698{
699 // read all the data structures
700 const size_t pcount = animation.parts.size();
701 void *cookie = NULL;
702 ZipFileRO* zip = animation.zip;
703 if (!zip->startIteration(&cookie)) {
704 return false;
705 }
706
707 ZipEntryRO entry;
708 char name[ANIM_ENTRY_NAME_MAX];
709 while ((entry = zip->nextEntry(cookie)) != NULL) {
710 const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
715
716 const String8 entryName(name);
717 const String8 path(entryName.getPathDir());
718 const String8 leaf(entryName.getPathLeaf());
719 if (leaf.size() > 0) {
727
728 for (size_t j = 0; j < pcount; j++) {
729 if (path == animation.parts[j].path) {
730 uint16_t method;
731 // supports only stored png files
732 if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
733 if (method == ZipFileRO::kCompressStored) {
734 FileMap* map = zip->createEntryFileMap(entry);
735 if (map) {
736 Animation::Part& part(animation.parts.editItemAt(j));
737 if (leaf == "audio.wav") {744 } else {// 读取 frame后存储到 part 中
745 Animation::Frame frame;
746 frame.name = leaf;
747 frame.map = map;
748 frame.trimWidth = animation.width;
749 frame.trimHeight = animation.height;
750 frame.trimX = 0;
751 frame.trimY = 0;
752 part.frames.add(frame);
753 }
754 }
755 } else {
756 ALOGE("bootanimation.zip is compressed; must be only stored");
757 }
758 }
759 }
760 }
761 }
762 }
763 // .......// ......
789
790 mCallbacks->init(animation.parts);
791
792 zip->endIteration(cookie);
793
794 return true;
795}
preloadZip() 就是将 zip 文件中每个 part 都解析后存储出来。这样解析并加载了 zip 文件内容以后,movie() 方法后就调用了 playAnimation (*animation); 开始播放动画,看看是怎么播放的。
871bool BootAnimation::playAnimation(const Animation& animation)
872{ // 先获取到 part 一共有几部分
873 const size_t pcount = animation.parts.size();
874 nsecs_t frameDuration = s2ns(1) / animation.fps;
875 const int animationX = (mWidth - animation.width) / 2;
876 const int animationY = (mHeight - animation.height) / 2;
877
878 ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
879 elapsedRealtime());// 循环取每个 part 部分
880 for (size_t i=0 ; i<pcount ; i++) {
881 const Animation::Part& part(animation.parts[i]);
882 const size_t fcount = part.frames.size();
883 glBindTexture(GL_TEXTURE_2D, 0);
884
885 // Handle animation package
886 if (part.animation != NULL) {// 这里有个递归 播放 part 的 animation
887 playAnimation(*part.animation);
888 if (exitPending())
889 break;
890 continue; //to next part
891 }
892 // 这个是取part里面的内容 通过 OpenGL 播放,创建各种纹理,将所有的图片加载出来,并且判断是否需要退出。
893 for (int r=0 ; !part.count || r<part.count ; r++) {
894 // Exit any non playuntil complete parts immediately
895 if(exitPending() && !part.playUntilComplete)
896 break;
897
898 mCallbacks->playPart(i, part, r);
899
900 glClearColor(
901 part.backgroundColor[0],
902 part.backgroundColor[1],
903 part.backgroundColor[2],
904 1.0f);
905
906 for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
907 const Animation::Frame& frame(part.frames[j]);
908 nsecs_t lastFrame = systemTime();
909
910 if (r > 0) {
911 glBindTexture(GL_TEXTURE_2D, frame.tid);
912 } else {
913 if (part.count != 1) {
914 glGenTextures(1, &frame.tid);
915 glBindTexture(GL_TEXTURE_2D, frame.tid);
916 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
917 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
918 }
919 int w, h;
920 initTexture(frame.map, &w, &h);
921 }
922
923 const int xc = animationX + frame.trimX;
924 const int yc = animationY + frame.trimY;
925 Region clearReg(Rect(mWidth, mHeight));
926 clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
927 if (!clearReg.isEmpty()) {
928 Region::const_iterator head(clearReg.begin());
929 Region::const_iterator tail(clearReg.end());
930 glEnable(GL_SCISSOR_TEST);
931 while (head != tail) {
932 const Rect& r2(*head++);
933 glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
934 glClear(GL_COLOR_BUFFER_BIT);
935 }
936 glDisable(GL_SCISSOR_TEST);
937 }
938 // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
939 // which is equivalent to mHeight - (yc + frame.trimHeight)
940 glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
941 0, frame.trimWidth, frame.trimHeight);
942 if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
943 drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
944 }
945
946 eglSwapBuffers(mDisplay, mSurface);
947
948 nsecs_t now = systemTime();
949 nsecs_t delay = frameDuration - (now - lastFrame);
950 //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
951 lastFrame = now;
952
953 if (delay > 0) {
954 struct timespec spec;
955 spec.tv_sec = (now + delay) / 1000000000;
956 spec.tv_nsec = (now + delay) % 1000000000;
957 int err;
958 do {
959 err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
960 } while (err<0 && errno == EINTR);
961 }
962
963 checkExit();
964 }
965
966 usleep(part.pause * ns2us(frameDuration));
967
968 // For infinite parts, we've now played them at least once, so perhaps exit
969 if(exitPending() && !part.count)
970 break;
971 }
972
973 }
974
975 // Free textures created for looping parts now that the animation is done.
976 for (const Animation::Part& part : animation.parts) {
977 if (part.count != 1) {
978 const size_t fcount = part.frames.size();
979 for (size_t j = 0; j < fcount; j++) {
980 const Animation::Frame& frame(part.frames[j]);
981 glDeleteTextures(1, &frame.tid);
982 }
983 }
984 }
985
986 return true;
987}
开机动画的启动到播放的流程大概就是这样了~