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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正大国际期货:期货交易需要根除的几点习惯

1、交易过多品种。对于期货交易而言&#xff0c;最不靠谱的就是同时操作多个品种。我认为的较合适的品种上限在3-4个&#xff0c;甚至更少为好。我们经常看到的截图&#xff0c;动辄十个甚至数十个品种&#xff0c;而且都盈利&#xff0c;这种截图看看就罢&#xff0c;不必认真…

Python处理时间和日期库之pytime使用详解

概要 在Python编程中,时间和日期处理是一个常见的需求。虽然Python标准库提供了强大的时间和日期处理模块,但对于一些常见的任务,例如自然语言解析时间、简单的日期计算等,标准库的使用相对复杂。pytime库提供了一种简单而直观的方法来处理时间和日期,使得这些任务变得更…

240.搜索二维矩阵

题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,…

宏基因组分箱(binning)|1.Metabat实战了解binning

Introduction 宏基因组学是直接从环境样本&#xff08;如土壤、水、肠道内容物等&#xff09;中回收遗传物质并进行研究的学科&#xff0c;无需对个体生物进行分离或培养。这一领域的研究为我们提供了对微生物群落多样性及其功能的深入理解。可以查看我之前写的宏基因组分析流…

汇总 |国内外医疗器械网络安全法规与标准

国内外关于医疗器械网络安全的法规和标准日益完善&#xff0c;旨在确保医疗器械在全生命周期内的网络安全&#xff0c;保障患者信息的安全和隐私&#xff0c;以及医疗器械的正常运行。不同国家和地区的法规和标准各有侧重&#xff0c;但都强调了医疗器械制造商、开发者、经营者…

Python - 深度学习系列38 重塑实体识别5-预测并行化改造

说明 在重塑实体识别4中梳理了数据流&#xff0c;然后我发现pipeline的串行效率太低了&#xff0c;所以做了并行化改造。里面还是有不少坑的&#xff0c;记录一下。 内容 1 pipeline 官方的pipeline看起来的确是比较好用的&#xff0c;主要是实现了比较好的数据预处理。因为…

Solidwokrs钣金拆图之移动面命令使用技巧

Solidwokrs钣金拆图之移动面命令使用技巧 Chapter1 Solidwokrs钣金拆图之移动面命令使用技巧Chapter2 solidworks如何删除外部参考 Chapter1 Solidwokrs钣金拆图之移动面命令使用技巧 原文链接&#xff1a;https://www.sohu.com/a/441562400_728492 今天给大家介绍一个SolidW…

IO进程线程(六)进程

文章目录 一、进程状态&#xff08;二&#xff09;进程状态切换实例1. 实例1 二、进程的创建&#xff08;一&#xff09;原理&#xff08;二&#xff09;fork函数--创建进程1. 定义2. 不关注返回值3. 关注返回值 &#xff08;三&#xff09; 父子进程的执行顺序&#xff08;四&…

【Redis数据库百万字详解】数据持久化

文章目录 一、持久化1.1、什么是持久化1.2、持久化方式1.3、RDB优缺点1.4、AOF优缺点 二、RDB持久化触发机制2.1、手动触发2.2、自动触发 三、RDB持久化配置3.1、配置文件3.2、配置查询/设置3.3、禁用持久化3.4、RDB文件恢复 四、RDB持久化案例4.1、手动持久化4.2、自动持久化案…

2024第26届大湾区国际电机博览会暨发展论坛

2024第二十六届大湾区国际电机博览会 暨发展论坛 2024第26届大湾区国际电机博览会暨发展论坛 The 26th Greater Bay Area International Motor Expo and Development Forum 时间&#xff1a;2024年12月4-6日 地址&#xff1a;深圳国际会展中心&#xff08;宝安新馆&#x…

安全生产新篇章:可燃气体报警器检验周期的国家标准解读

随着工业化进程的加快&#xff0c;安全生产成为了重中之重。 可燃气体报警器作为预防火灾和爆炸事故的重要设备&#xff0c;其准确性和可靠性直接关系到企业的生产安全和员工的生命财产安全。 因此&#xff0c;国家对可燃气体报警器的检验周期有着明确的规定&#xff0c;以确…

美洽工作台3.0,全新发布!

美洽工作台3.0&#xff0c;全新发布 想要效率翻倍&#xff0c;就要一步到位&#xff01; 工作台 3.0&#xff0c;为效率而生 1. 更丰富的外观选择&#xff0c;让界面焕然一新&#xff0c;新增导航主题色选择&#xff0c;深色 Dark、浅色 Light 随意切换 2. 自定义你的专属导…

Python 识别图片形式pdf的尝试(未解决)

想识别出pdf页面右下角某处的编号。pdf是图片形式页面。查了下方法&#xff0c;有源码是先将页面提取成jpg&#xff0c;再用pytesseract提取图片文件中的内容。 直接用图片来识别。纯数字的图片&#xff0c;如条形码&#xff0c;可识别。带中文的不可以&#xff0c;很乱。 识别…

吴恩达深度学习笔记:机器学习(ML)策略(1)(ML strategy(1))1.3-1.4

目录 第三门课 结构化机器学习项目&#xff08;Structuring Machine Learning Projects&#xff09;第一周 机器学习&#xff08;ML&#xff09;策略&#xff08;1&#xff09;&#xff08;ML strategy&#xff08;1&#xff09;&#xff09;1.3 单一数字评估指标&#xff08;S…

Linux|如何安装 Java

引言 Java是最受欢迎的编程语言之一&#xff0c;JVM&#xff08;Java的虚拟机&#xff09;是运行Java应用程序的运行时环境。这两个平台是许多流行软件所需的&#xff0c;包括Tomcat&#xff0c;Jetty&#xff0c;Cassandra&#xff0c;Glassfish和Jenkins。 本教程[1]将指导您…

2024年应用经济学、管理科学与社会国际学术会议(ICAEMSS 2024)

2024年应用经济学、管理科学与社会国际学术会议&#xff08;ICAEMSS 2024&#xff09; 会议简介 2024年应用经济学、管理科学与社会国际学术会议将聚焦应用经济学和管理科学的前沿问题&#xff0c;深入探讨社会变革中的经济管理与科学应用。参会者将分享最新研究成果&#xf…

短剧小程序App系统源码:打造个性化追剧体验

随着数字媒体的迅猛发展&#xff0c;短剧作为一种新兴的娱乐形式&#xff0c;越来越受到广大观众的喜爱。为了满足用户对短剧内容的个性化需求&#xff0c;短剧小程序App系统应运而生。本文将深入探讨短剧App源码的核心功能&#xff0c;以及如何通过多语言支持和国际支付等技术…

超声波洗眼镜机是智商税吗?四款不能错过的超声波清洗机实力种草

在日常生活中&#xff0c;眼镜成为了我们不可或缺的伙伴&#xff0c;无论是阅读书籍、工作还是享受自然风光&#xff0c;清晰的视野总是至关重要。然而&#xff0c;眼镜上不可避免地会沾染灰尘、油脂甚至细菌&#xff0c;影响我们的视觉体验。传统的眼镜清洗方法虽然简单&#…

雷池WAF《动态防护》功能体验

一、雷池简介&#xff08;官方&#xff09; 自 2016 年起&#xff0c;长亭就开源了雷池的语义分析算法自动机引擎&#xff0c;随后又陆续开源了雷池相关风控插件和引擎通信协议。雷池的商业版本自发布以来&#xff0c;得到了各大咨询机构和众多顶级企业的认可。然而&#xff0…

MT3050 区间最小值

思路&#xff1a; 使用ST表 ST模板可参考MT3024 maxmin 代码&#xff1a; 1.暴力9/10&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e5 10; int n, m; int a[N]; int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin …