android 开机动画执行流程

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}
开机动画的启动到播放的流程大概就是这样了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/702472.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【代码随想录】【算法训练营】【第36天】 [860]柠檬水找零 [406]根据身高重建队列 [452]用最少数量的箭引爆气球

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 36&#xff0c;周三&#xff0c;最难坚持的一天~ 题目详情 [860] 柠檬水找零 题目描述 860 柠檬水找零 解题思路 前提&#xff1a; 思路&#xff1a;维护5&#xff0c;10&#xff0c;20三种…

【教程】DGL单机多卡分布式GCN训练

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ PyTorch中的DDP会将模型复制到每个GPU中。 梯度同步默认使用Ring-AllReduce进行&#xff0c;重叠了通信和计算。 示例代码&#xff1a; 视频&#xff1…

【免费Web系列】大家好 ,今天是Web课程的第十九天点赞收藏关注,持续更新作品 !

1. Vue工程化 前面我们在介绍Vue的时候&#xff0c;我们讲到Vue是一款用于构建用户界面的渐进式JavaScript框架 。&#xff08;官方&#xff1a;Vue.js - 渐进式 JavaScript 框架 | Vue.js&#xff09; 那在前面的课程中&#xff0c;我们已经学习了Vue的基本语法、表达式、指令…

MapperStruct拷贝数据的介绍和使用

1、前言 在java 编程中&#xff0c;对象直接拷贝是很常用的方法&#xff0c;最初我们常用spring提供的拷贝工具BeanUtils的copyProperties方法完成对象之间属性的拷贝。但是它有几个明显的如下缺点 1、属性类型不一致导致摸一个属性值拷贝失败 2、通一个字段使用基本类型和包…

Mybatis plus join 一对多对象语法

1. 实体类环境 题目 package co.yixiang.exam.entity;import co.yixiang.domain.BaseDomain; import co.yixiang.exam.config.CustomStringListDeserializer; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonCreator;…

使用Python爬取temu商品与评论信息

【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作&#xff01; 【&…

Pytorch--Convolution Layers

文章目录 1.nn.Conv1d2.torch.nn.Conv2d()3.torch.nn.ConvTranspose1d()3.torch.nn.ConvTranspose2d() 1.nn.Conv1d torch.nn.Conv1d() 是 PyTorch 中用于定义一维卷积层的类。一维卷积层常用于处理时间序列数据或具有一维结构的数据。 构造函数 torch.nn.Conv1d() 的语法如…

【运维自动化-配置平台】如何使用云资源同步功能(腾讯云为例)

云资源同步是通过apikey去单向同步云上的主机资源和云区域信息&#xff0c;目前支持腾讯云和亚马逊云。主要特性 1、蓝鲸配置平台周期性的单向只读同步云主机和vpc&#xff08;对应蓝鲸云区域&#xff09;信息&#xff0c;第一次全量&#xff0c;后面增量 2、默认同步到主机池…

kotlin 中的数字

以下均来自官方文档&#xff1a; 一、整数类型 1、kotlin中内置的整数类型&#xff0c;有四种不同大小的类型&#xff1a; 类型存储大小&#xff08;比特数&#xff09;最小值最大值Byte8-128127Short16-3276832767Int32-2,147,483,648 (-231)2,147,483,647 (231 - 1)Long64…

图片导入AutoCAD建立草图—CAD图像导入插件

插件介绍 CAD图像导入插件可将PNG&#xff0c;JPG等格式图片导入到AutoCAD软件内建立图像边缘的二维线条模型。插件可以提取图像黑色或白色区域的边界&#xff0c;并可绘制原状边界或平滑边界两种样式。 模型说明 边界提取&#xff0c;黑色或白色边界的提取根据原图类型选择…

c#调用c++dll方法

添加dll文件到debug目录&#xff0c;c#生成的exe的相同目录 就可以直接使用了&#xff0c;放在构造函数里面测试

排序的时间复杂度、空间复杂度和稳定性等的比较

时间复杂度和空间复杂度我们比较熟悉&#xff0c;重点来看一下稳定性。 稳定性是指假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&#xff0c;这些记录的相对次序保持不变&#xff0c;即在原序列中&#xff0c;a[i] a[j] &…

Golang 百题(实战快速掌握语法)_1

整形转字符串类型 实验介绍 本实验将展示三种方法来实现整形类型转字符串类型。 知识点 strconvfmt Itoa 函数 代码实例 Go 语言中 strconv 包的 itoa 函数输入一个 int 类型&#xff0c;返回转换后的字符串。下面是一个例子。 package mainimport ("fmt"&qu…

跟TED演讲学英文:Toward a new understanding of mental illness by Thomas Insel

Toward a new understanding of mental illness Link: https://www.ted.com/talks/thomas_insel_toward_a_new_understanding_of_mental_illness Speaker: Thomas Insel Date: January 2013 文章目录 Toward a new understanding of mental illnessIntroductionVocabularySum…

【C语言】联合(共用体)

目录 一、什么是联合体 二、联合类型的声明 三、联合变量的创建 四、联合的特点 五、联合体大小的计算 六、联合的应用&#xff08;判断大小端&#xff09; 七、联合体的优缺点 7.1 优点 7.2 缺点 一、什么是联合体 联合也是一种特殊的自定义类型。由多个不同类型的数…

测长仪的发展历程!

测长仪的发展历程可以大致分为以下几个阶段&#xff1a; 早期发展&#xff1a; 最早的测量工具主要是一些机械式测量工具&#xff0c;如角尺、卡钳等。 16世纪&#xff0c;在火炮制造中已开始使用光滑量规。 1772年和1805年&#xff0c;英国的J.瓦特和H.莫兹利等先后制造出利用…

Win快速删除node_modules

在Windows系统上删除 node_modules 文件夹通常是一个缓慢且耗时的过程。这主要是由于几个关键因素导致的&#xff1a; 主要原因 文件数量多且嵌套深&#xff1a; node_modules 文件夹通常包含成千上万的子文件夹和文件。由于其结构复杂&#xff0c;文件和文件夹往往嵌套得非常…

XXL-JOB分布式任务调度快速入门

文章目录 概念快速启动XXL-JOB调度初始化执行器项目配置执行器新增GLUE模式(Java)的任务新增BEAN模式&#xff08;类形式&#xff09;的任务BEAN模式&#xff08;方法形式&#xff09;的任务参考来源 概念 XXL-JOB是一个开源的分布式任务调度平台&#xff0c;它是一个轻量级、…

使用B树实现员工(人事)管理系统

1. 前言 使用B树来表示人事管理系统&#xff0c;其中每个节点代表一个人员&#xff0c;树的根节点为董事长&#xff0c;每个节点可以有多个子节点&#xff0c;表示下属。每一层代表一个等级分布。 addPerson: 添加人员功能通过查找指定上司节点&#xff0c;然后将新的人员作…

程序员/码农创业有多少种可能?

程序员创业&#xff0c;无疑是当下科技浪潮中的一股强大力量。凭借扎实的技术功底和敏锐的市场洞察力&#xff0c;在创业道路上展现出了无限的活力和创造力。那么&#xff0c;程序员创业究竟有哪些事情可以做呢&#xff1f;可以从技术产品的研发入手。 可以利用自己的专业知识…