Android BootAnimation 模块分析

前言

本篇文章主要分析 Android 原生模块 BootAnimation 开关机动画的相关内容。

正文

摘要

在 BootAnimation 模块中,通过 debug.sf.nobootanimation 属性去控制跳过开机动画,可以通过配置属性 debug.sf.nobootanimation = 1 将开机动画跳过。在编译前也可以通过配置属性 ro.boot.quiescent = 1 将开机动画默认跳过。

BootAnimation 不仅是控制开机动画,关机动画也在其中,在创建 BootAnimation 时通过判断 sys.powerctl 属性值获取 Power 模块的状态,以此来判断是应该播放开机动画还是关机动画。

BootAnimation 函数执行流程描述:BootAnimation 继承自 Thread 类,并且 BootAnimation 重写了 readyToRun() 和 threadLoop() 函数。readyToRun() 和 threadLoop() 函数是 Thread 类的函数,BootAnimation 重写会在线程的启动中自动调用。那么执行顺序就是

BootAnimation() 构造函数 -> readyToRun() -> threadLoop() -> android()/movie() 两种情况,显示原生的图片调用 android(),使用开机动画调用 movie() 函数。

movie() -> loadAnimation() -> playAnimation() -> releaseAnimation()

1、BootAnimation 模块

1.1 介绍

Android 系统在开机过程中会播放一段动画,表示系统在启动中,这一功能实现是由 BootAnimation 模块实现。

BootAnimation 的代码路径在 frameworks/base/cmds/bootanimation 下

路径文件结构如下所示:

ubuntu:frameworks/base/cmds/bootanimation$ tree
.
├── Android.mk    // 编译配置文件
├── audioplay.cpp
├── audioplay.h
├── BootAnimation.cpp  // bootanimation 功能文件
├── BootAnimation.h
├── bootanimation_main.cpp  // bootanimation 入口文件
├── BootAnimationUtil.cpp  // 工具类
├── BootAnimationUtil.h
├── bootanim.rc  // rc 配置文件
├── FORMAT.md
└── iot
    ├── BootAction.cpp
    ├── BootAction.h
    ├── bootanim_iot.rc
    ├── BootParameters.cpp
    ├── BootParameters.h
    └── iotbootanimation_main.cpp

1 directory, 16 files

1.2 BootAnimation 服务

1.2.1 rc 文件

BootAnimation 模块的 rc 文件如下所示:

service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

在 rc 文件中,定义了一个 /system/bin/ 路径下的 bootanimation 服务,用户定义为 graphics,组定义为 graphics 和 audio,启动状态为 disabled 表示 init 解析 init.rc 的过程中不会自动启动该服务。

1.2.2 Android.mk 文件

再来看下 BootAnimation 服务的 Android.mk 文件,下面是 Android.mk 的部分内容:

# bootanimation executable
# =========================================================
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SHARED_LIBRARIES := \
    libbootanimation \
LOCAL_SRC_FILES:= \
    BootAnimationUtil.cpp \
LOCAL_SRC_FILES += \
    bootanimation_main.cpp \
    audioplay.cpp \
LOCAL_MODULE:= bootanimation
LOCAL_INIT_RC := bootanim.rc
include $(BUILD_EXECUTABLE)

# libbootanimation
# ===========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libbootanimation
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SRC_FILES:= \
    BootAnimation.cpp
LOCAL_C_INCLUDES += \
    external/tinyalsa/include \
    frameworks/wilhelm/include
include ${BUILD_SHARED_LIBRARY}

从上述 Android.mk 文件中可以发现,定义了一个可执行文件 bootanimation 和一个动态库 libbootanimation 文件。

可执行文件 bootanimation 的 rc 指定为当前目录下的 bootanim.rc 文件,在编译时,系统会将 bootanim.rc 安装到 /system/etc/init 目录,init 进程在启动时会加载所有的 rc 文件,完成服务的声明。bootanimation 的入口文件为 bootanimation_main.cpp。

动态库 libbootanimation 是 bootanimation 所需的依赖文件,内容为 BootAnimation.cpp。

1.2.3 bootanimation_main.cpp

下面是 bootanimation 入口文件 bootanimation_main.cpp 的内容:

int main()
{
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

    bool noBootAnimation = bootAnimationDisabled();
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

        //等待SurfaceFlinger初始化完成并加入到 ServiceManager。
        //因为 bootanim 服务是在 SurfaceFlinger.init() 里面启动的,但在 SurfaceFlinger 服务是在执行完 SurfaceFlinger.init() 之后才加入到 ServiceManager 的,所以这里需要等一下。
        waitForSurfaceFlinger();

        // create the boot animation object
        ALOGD("DEBUG: create BootAnimation boot");
        sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks(), 0);
        ALOGV("Boot animation set up. Joining pool.");

        IPCThreadState::self()->joinThreadPool();
    }
    ALOGV("Boot animation exit");
    return 0;
}

变量 noBootAnimation 配置是在 BootAnimation 模块中,通过 debug.sf.nobootanimation 属性去控制跳过开机动画,可以通过配置属性 debug.sf.nobootanimation = 1 将开机动画跳过。在编译前也可以通过配置属性 ro.boot.quiescent = 1 将开机动画默认跳过。

这里等待 SurfaceFlinger,开机动画的播放是建立在系统图像服务就绪的基础上的,如果图像服务还没有初始化完成,那么 BootAnimation 也就无法播放动画了,这也涉及到 bootanimation 的启动,后续在 bootanimation 是如何启动的会介绍。

创建 BootAnimation 准备播放开机动画。

1.2.4 BootAnimation.cpp

初始化 BootAnimation 对象如下所示:

BootAnimation::BootAnimation(sp<Callbacks> callbacks, int dispalyId)
        : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
        mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) , mDisplayId(dispalyId){
    mSession = new SurfaceComposerClient();

    std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
    if (powerCtl.empty()) {
        mShuttingDown = false;
    } else {
        mShuttingDown = true;
    }
}

BootAnimation 不仅是控制开机动画,关机动画也在其中,在创建 BootAnimation 时通过判断 sys.powerctl 属性值获取 Power 模块的状态,以此来判断是应该播放开机动画还是关机动画。

初始化中并没有进行预载资源及动画播放的任务,那么 BootAnimation 是如何进行动画播放的呢?在 BootAnimation 类的定义中可以发现,BootAnimation 是继承自 Thread 类的,线程重写 threadLoop() 函数表明线程所需执行的任务,

这里对文件内函数做一些简单解释:

  • onFirstRef()—— 属于其父类 RefBase,该函数在强引用 sp 新增引用计数時调用,就是当有 sp 包装的类初始化的时候调用;
  • binderDied() ——当对象死掉或者其他情况导致该 Binder 结束时,就会回调 binderDied() 方法;
  • readyToRun() ——Thread 执行前的初始化工作;
  • threadLoop() ——每个线程类都要实现的,在这里定义 thread 的执行内容。这个函数如果返回 true,且没有调用 requestExit(),则该函数会再次执行;如果返回 false,则 threadloop 中的内容仅仅执行一次,线程就会退出。

其他内部函数简介:

  • android() —— 显示系统默认的开机画面;
  • movie() —— 显示用户自定义的开机动画;
  • loadAnimation(const String8&) —— 加载动画;
  • playAnimation(const Animation&) —— 播放动画;
  • checkExit() —— 检查是否退出动画;
1.2.4.1 readyToRun()

当线程被执行 run() 函数时,Threads 基类中代码会先进入到 readyToRun() 函数,再调用 threadLoop() 函数。

status_t BootAnimation::readyToRun() {
    mAssets.addDefaultAssets();

    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
            ISurfaceComposer::eDisplayIdMain));
    DisplayInfo dinfo;
    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
    if (status)
        return -1;

    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::Transaction t;
    if (mDisplayId == 1) {
        t.setLayerStack(control, 1);
    } else {
        t.setLayerStack(control, 0);
    }
    t.setLayer(control, 0x40000000)
        .apply();

    sp<Surface> s = control->getSurface();

    // initialize opengl and egl
    const EGLint attribs[] = {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    };

    EGLint w, h;
    EGLint numConfigs;
    EGLConfig config[10];
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);
    eglChooseConfig(display, attribs, config, 10, &numConfigs);
    int index = 0;
    for (int i = 0; i < numConfigs; ++i) {
        EGLint a = 0;
        EGLint r, g, b;
        r = g = b = 0;
        eglGetConfigAttrib(display, config[i], EGL_RED_SIZE, &r);
        eglGetConfigAttrib(display, config[i], EGL_GREEN_SIZE, &g);
        eglGetConfigAttrib(display, config[i], EGL_BLUE_SIZE, &b);
        eglGetConfigAttrib(display, config[i], EGL_ALPHA_SIZE, &a);
        if (r == 8 && g == 8 && b == 8 && a == 8) {
            index = i;
            break;
        }
    }
    surface = eglCreateWindowSurface(display, config[index], s.get(), NULL);
    context = eglCreateContext(display, config[index], NULL, NULL);

    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;

    mDisplay = display;
    mContext = context;
    mSurface = surface;
    mWidth = w;
    mHeight = h;
    mFlingerSurfaceControl = control;
    mFlingerSurface = s;

    // If the device has encryption turned on or is in process
    // of being encrypted we show the encrypted boot animation.
    char decrypt[PROPERTY_VALUE_MAX];
    property_get("vold.decrypt", decrypt, "");

    bool encryptedAnimation = atoi(decrypt) != 0 ||
        !strcmp("trigger_restart_min_framework", decrypt);

    if (!mShuttingDown && encryptedAnimation) {
        static const char* encryptedBootFiles[] =
            {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE};
        for (const char* f : encryptedBootFiles) {
            if (access(f, R_OK) == 0) {
                mZipFileName = f;
                return NO_ERROR;
            }
        }
    }
    static const char* bootFiles[] =
        {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};
    static const char* shutdownFiles[] =
        {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE};

    for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) {
        if (access(f, R_OK) == 0) {
            mZipFileName = f;
            return NO_ERROR;
        }
    }
    return NO_ERROR;
}

在 readyToRun() 函数中,主要完成了 surface 功能的使用初始化,准备后续的动画播放;完成了开关机动画资源文件的配置。

资源文件配置目录

开机动画目录:

​ “/product/media/bootanimation.zip”

​ “/oem/media/bootanimation.zip”

​ “/system/media/bootanimation.zip”

关机动画目录:

​ “/product/media/shutdownanimation.zip”

​ “/oem/media/shutdownanimation.zip”

​ “/system/media/shutdownanimation.zip”

通过循环遍历资源文件路径,配置开机或者关机动画资源位置。

1.2.4.2 threadLoop()

threadLoop() 函数执行如下:

bool BootAnimation::threadLoop()
{
    bool r;
    // We have no bootanimation file, so we use the stock android logo
    // animation.
    if (mZipFileName.isEmpty()) {
        r = android();
    } else {
        r = movie();
    }

    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
    eglDestroySurface(mDisplay, mSurface);
    mFlingerSurface.clear();
    mFlingerSurfaceControl.clear();
    eglTerminate(mDisplay);
    eglReleaseThread();
    IPCThreadState::self()->stopProcess();
    return r;
}

threadLoop() 函数中主要有两种执行,android() 和 movie(),当配置了动画资源,那么会执行 movie() 进行播放;如果没有执行动画资源,那么会执行 android(),播放原生的 logo 图片,logo 资源路径为【“images/android-logo-mask.png”、“images/android-logo-shine.png”】

1.2.4.3 movie()

播放动画会执行到 movie() 函数中。部分函数如下所示:

bool BootAnimation::movie()
{
    Animation* animation = loadAnimation(mZipFileName);
    ...
    playAnimation(*animation);
    ...
    releaseAnimation(animation);

    return false;
}

在 movie() 中包含加载动画、播放动画、释放动画等逻辑。这部分代码功能主要是通过 surface 和 opengl 环境去播放动画的。

这里我们来分析下动画是如何结束播放的。

在播放动画 playAnimation() 函数中,会通过循环去显示每一帧图片,每一帧图片显示后都会通过 checkExit() 函数判断是否可以退出显示了:

bool BootAnimation::playAnimation(const Animation& animation)
{
        for (int r=0 ; !part.count || r<part.count ; r++) {
           ...
            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
                ...
                checkExit();
            }
        }
   ...
    return true;
}
1.2.4.4 checkExit()

检测是否退出播放动画逻辑如下:

void BootAnimation::checkExit() {
    // Allow surface flinger to gracefully request shutdown
    char value[PROPERTY_VALUE_MAX];
    // static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
    property_get(EXIT_PROP_NAME, value, "0");
    int exitnow = atoi(value);
    if (exitnow) {
        requestExit();
        if (mCallbacks != NULL)
            mCallbacks->shutdown();
    }
}

checkExit() 函数里面通过获取 service.bootanim.exit 这个属性值来判断是否要退出动画。如果这个属性被设置为 “1”, 则调用 requestExit() 请求结束线程, exitPending()函数的返回值就会变成 true。就退出播放线程。BootAnimation 的使命就完成了,可以退出了。

那么就是说 BootAnimation 的动画结束播放是通过 service.bootanim.exit 属性值控制的,至于是什么模块在什么时候通知的,需要再进行分析。

Android系统开发进阶-BootAnimation启动与结束流程 | 一叶知秋 (qiushao.net)

2、BootAnimation 是如何启动的

上一章节中,我们在 bootanim.rc 中发现启动状态是 disabled,表明在 init 阶段不会将服务启动。那么本章节我们主要来跟踪下,bootanimation 是如何启动的。前面提到开机动画实际是播放图片,它肯定是建立在 Surface 服务的基础上的,这里启动 bootanimation 是由 Surface 服务端 SurfaceFlinger 拉起的,我们来跟踪下如何拉起的。

SurfaceFlinger 的代码路径在 frameworks/native/services/surfaceflinger 下

cc_binary {
    name: "surfaceflinger",
    defaults: ["surfaceflinger_defaults"],
    init_rc: ["surfaceflinger.rc"],
    srcs: ["main_surfaceflinger.cpp"],
    whole_static_libs: [
        "libsigchain",
    ],
    shared_libs: [
        "android.frameworks.displayservice@1.0",
        "android.hardware.configstore-utils",
        "android.hardware.configstore@1.0",
        "android.hardware.graphics.allocator@2.0",
        "libbinder",
        "libcutils",
        "libdisplayservicehidl",
        "libhidlbase",
        "libhidltransport",
        "liblayers_proto",
        "liblog",
        "libsurfaceflinger",
        "libtimestats_proto",
        "libutils",
    ],
    static_libs: [
        "libserviceutils",
        "libtrace_proto",
    ],
    ldflags: ["-Wl,--export-dynamic"],

    // TODO(b/71715793): These version-scripts are required due to the use of
    // whole_static_libs to pull in libsigchain. To work, the files had to be
    // locally duplicated from their original location
    // $ANDROID_ROOT/art/sigchainlib/
    multilib: {
        lib32: {
            version_script: "version-script32.txt",
        },
        lib64: {
            version_script: "version-script64.txt",
        },
    },
}

上面是 SurfaceFlinger 下 Android.bp 的二进制可执行文件 surfaceflinger 的配置内容,配置 surfaceflinger.rc 是服务的启动配置文件,入口文件是 main_surfaceflinger.cpp。

surfaceflinger.rc 文件如下:

service surfaceflinger /system/bin/surfaceflinger
    class core animation
    user system
    group graphics drmrpc readproc
    onrestart restart zygote
    writepid /dev/stune/foreground/tasks
    socket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
    socket pdx/system/vr/display/manager    stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0
    socket pdx/system/vr/display/vsync      stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0

main_surfaceflinger.cpp 如下:

int main(int, char**) {
    signal(SIGPIPE, SIG_IGN);

    hardware::configureRpcThreadpool(1 /* maxThreads */,
            false /* callerWillJoin */);

    startGraphicsAllocatorService();

    // When SF is launched in its own process, limit the number of
    // binder threads to 4.
    ProcessState::self()->setThreadPoolMaxThreadCount(4);

    // start the thread pool
    sp<ProcessState> ps(ProcessState::self());
    ps->startThreadPool();

    // instantiate surfaceflinger
    sp<SurfaceFlinger> flinger = new SurfaceFlinger();

    setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);

    set_sched_policy(0, SP_FOREGROUND);

    // Put most SurfaceFlinger threads in the system-background cpuset
    // Keeps us from unnecessarily using big cores
    // Do this after the binder thread pool init
    if (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);

    // initialize before clients can connect
    flinger->init();

    // publish surface flinger
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);

    // publish GpuService
    sp<GpuService> gpuservice = new GpuService();
    sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);

    startDisplayService(); // dependency on SF getting registered above

    struct sched_param param = {0};
    param.sched_priority = 2;
    if (sched_setscheduler(0, SCHED_FIFO, &param) != 0) {
        ALOGE("Couldn't set SCHED_FIFO");
    }

    // run surface flinger in this thread
    flinger->run();

    return 0;
}

surfaceflinger 的入口函数中,初始化了一系列服务,GraphicsAllocator、SurfaceFlinger、GpuService、

DisplayService。并且注册 SurfaceFlinger 服务,最后开启 surface flinger 线程。

这里进入 SurfaceFlinger 的初始化函数 init() 中:

void SurfaceFlinger::init() {
    ALOGI(  "SurfaceFlinger's main thread ready to run. "
            "Initializing graphics H/W...");

    ALOGI("Phase offest NS: %" PRId64 "", vsyncPhaseOffsetNs);

    Mutex::Autolock _l(mStateLock);

    // start the EventThread
    mEventThreadSource =
            std::make_unique<DispSyncSource>(&mPrimaryDispSync, SurfaceFlinger::vsyncPhaseOffsetNs,
                                             true, "app");
    mEventThread = std::make_unique<impl::EventThread>(mEventThreadSource.get(),
                                                       [this]() { resyncWithRateLimit(); },
                                                       impl::EventThread::InterceptVSyncsCallback(),
                                                       "appEventThread");
    mSfEventThreadSource =
            std::make_unique<DispSyncSource>(&mPrimaryDispSync,
                                             SurfaceFlinger::sfVsyncPhaseOffsetNs, true, "sf");

    mSFEventThread =
            std::make_unique<impl::EventThread>(mSfEventThreadSource.get(),
                                                [this]() { resyncWithRateLimit(); },
                                                [this](nsecs_t timestamp) {
                                                    mInterceptor->saveVSyncEvent(timestamp);
                                                },
                                                "sfEventThread");
    mEventQueue->setEventThread(mSFEventThread.get());
    mVsyncModulator.setEventThread(mSFEventThread.get());

    // Get a RenderEngine for the given display / config (can't fail)
    getBE().mRenderEngine =
            RE::impl::RenderEngine::create(HAL_PIXEL_FORMAT_RGBA_8888,
                                           hasWideColorDisplay
                                                   ? RE::RenderEngine::WIDE_COLOR_SUPPORT
                                                   : 0);
    LOG_ALWAYS_FATAL_IF(getBE().mRenderEngine == nullptr, "couldn't create RenderEngine");

    LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay,
            "Starting with vr flinger active is not currently supported.");
    getBE().mHwc.reset(
            new HWComposer(std::make_unique<Hwc2::impl::Composer>(getBE().mHwcServiceName)));
    getBE().mHwc->registerCallback(this, getBE().mComposerSequenceId);
    // Process any initial hotplug and resulting display changes.
    processDisplayHotplugEventsLocked();
    LOG_ALWAYS_FATAL_IF(!getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY),
            "Registered composer callback but didn't create the default primary display");

    // make the default display GLContext current so that we can create textures
    // when creating Layers (which may happens before we render something)
    getDefaultDisplayDeviceLocked()->makeCurrent();

    if (useVrFlinger) {
        auto vrFlingerRequestDisplayCallback = [this] (bool requestDisplay) {
            // This callback is called from the vr flinger dispatch thread. We
            // need to call signalTransaction(), which requires holding
            // mStateLock when we're not on the main thread. Acquiring
            // mStateLock from the vr flinger dispatch thread might trigger a
            // deadlock in surface flinger (see b/66916578), so post a message
            // to be handled on the main thread instead.
            sp<LambdaMessage> message = new LambdaMessage([=]() {
                ALOGI("VR request display mode: requestDisplay=%d", requestDisplay);
                mVrFlingerRequestsDisplay = requestDisplay;
                signalTransaction();
            });
            postMessageAsync(message);
        };
        mVrFlinger = dvr::VrFlinger::Create(getBE().mHwc->getComposer(),
                getBE().mHwc->getHwcDisplayId(HWC_DISPLAY_PRIMARY).value_or(0),
                vrFlingerRequestDisplayCallback);
        if (!mVrFlinger) {
            ALOGE("Failed to start vrflinger");
        }
    }

    mEventControlThread = std::make_unique<impl::EventControlThread>(
            [this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });

    // initialize our drawing state
    mDrawingState = mCurrentState;

    // set initial conditions (e.g. unblank default device)
    initializeDisplays();

    getBE().mRenderEngine->primeCache();

    // Inform native graphics APIs whether the present timestamp is supported:
    if (getHwComposer().hasCapability(
            HWC2::Capability::PresentFenceIsNotReliable)) {
        mStartPropertySetThread = new StartPropertySetThread(false);
    } else {
        mStartPropertySetThread = new StartPropertySetThread(true);
    }

    if (mStartPropertySetThread->Start() != NO_ERROR) {
        ALOGE("Run StartPropertySetThread failed!");
    }

    mLegacySrgbSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(HWC_DISPLAY_PRIMARY,
            Dataspace::SRGB_LINEAR);

    ALOGV("Done initializing");
}

在 SurfaceFlinger.cpp 初始化结束时创建了 mStartPropertySetThread 对象,在 StartPropertySetThread 类中设置了开机动画的相关属性。

bool StartPropertySetThread::threadLoop() {
    // Set property service.sf.present_timestamp, consumer need check its readiness
    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
    // Clear BootAnimation exit flag
    property_set("service.bootanim.exit", "0");
    // Start BootAnimation if not started
    property_set("ctl.start", "bootanim");
    // Exit immediately
    return false;
}

这里设置了两个属性 service.bootanim.exit = 0 和 ctl.start = bootanim。

当 clt.start 属性发生改变时,init 进程就会接收到一个系统属性变化通知,这个通知在 property_service.cpp 中完成处理。当接收到 ctl.start = bootanim 时,init 就会启动 bootanimation 服务。

3、BootAnimation 是如何结束的

第一章节中我们提到开机动画的播放是循环播放的,在循环播放中会检测是否需要退出,而退出的条件是 service.bootanim.exit = 1。是通过 service.bootanim.exit 属性来控制的,也就是说,在启动过程结束时,系统会设置 service.bootanim.exit 属性来结束开机动画。这里我们来跟踪下是在什么时候设置的属性。

通过搜索 service.bootanim.exit,发现在源码中有两处对此属性设置为 1:

SurfaceFlinger.cpp	477 property_set("service.bootanim.exit", "1");
WindowManagerService.java	3506 SystemProperties.set("service.bootanim.exit", "1");

这里我们来看下 SurfaceFlinger.cpp 中的设置是如何调用的。

void SurfaceFlinger::bootFinished()
{
    if (mStartPropertySetThread->join() != NO_ERROR) {
        ALOGE("Join StartPropertySetThread failed!");
    }
    const nsecs_t now = systemTime();
    const nsecs_t duration = now - mBootTime;
    ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );

    // wait patiently for the window manager death
    const String16 name("window");
    sp<IBinder> window(defaultServiceManager()->getService(name));
    if (window != 0) {
        window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
    }

    if (mVrFlinger) {
      mVrFlinger->OnBootFinished();
    }

    // stop boot animation
    // formerly we would just kill the process, but we now ask it to exit so it
    // can choose where to stop the animation.
    property_set("service.bootanim.exit", "1");

    const int LOGTAG_SF_STOP_BOOTANIM = 60110;
    LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,
                   ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));

    sp<LambdaMessage> readProperties = new LambdaMessage([&]() {
        readPersistentProperties();
    });
    postMessageAsync(readProperties);
}

在 SurfaceFlinger 的 bootFinished() 函数中有关闭开机动画。bootFinished 在 SurfaceFlinger.h 中定义的是虚函数,是 ISurfaceComposer 的接口。

ISurfaceComposer 接口代码路径在 frameworks/native/libs/gui 下。搜索 bootFinished 函数:

ubuntu:/frameworks/native/libs/gui$ grep -irn "bootFinished"
include/gui/ISurfaceComposer.h:131:    virtual void bootFinished() = 0;
tests/Surface_test.cpp:554:    void bootFinished() override {}
ISurfaceComposer.cpp:98:    virtual void bootFinished()
ISurfaceComposer.cpp:603:            bootFinished();

ISurfaceComposer.cpp 中 bootFinished() 函数调用

status_t BnSurfaceComposer::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        ...
        case BOOT_FINISHED: {
            CHECK_INTERFACE(ISurfaceComposer, data, reply);
            bootFinished();
            return NO_ERROR;
        }
        ...
    }
}

这里 ISurfaceComposer 通过 Binder 提供 BOOT_FINISHED 服务,那么是谁通过 Binder 调用 BOOT_FINISHED 的呢?

WindowManagerService.java	3530 surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED

发现在 WindowManagerService.java 中调用了 BOOT_FINISHED 服务。

    private void performEnableScreen() {
        synchronized(mWindowMap) {
            if (DEBUG_BOOT) Slog.v(TAG_WM, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
                    + " mShowingBootMessages=" + mShowingBootMessages
                    + " mSystemBooted=" + mSystemBooted
                    + " mOnlyCore=" + mOnlyCore,
                    new RuntimeException("here").fillInStackTrace());
            if (mDisplayEnabled) {
                return;
            }
            if (!mSystemBooted && !mShowingBootMessages) {
                return;
            }

            if (!mShowingBootMessages && !mPolicy.canDismissBootAnimation()) {
                return;
            }

            // Don't enable the screen until all existing windows have been drawn.
            if (!mForceDisplayEnabled
                    // TODO(multidisplay): Expand to all displays?
                    && getDefaultDisplayContentLocked().checkWaitingForWindows()) {
                return;
            }

            if (!mBootAnimationStopped) {
                Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
                // stop boot animation
                // formerly we would just kill the process, but we now ask it to exit so it
                // can choose where to stop the animation.
                SystemProperties.set("service.bootanim.exit", "1");
                mBootAnimationStopped = true;
            }

            if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
                if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
                return;
            }

            // Bolt S: direct stop the bootanim service.
            if (android.os.Bolt.BOLT_BOOTANIM) {
                try {
                    Slog.i(android.os.Bolt.TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED by setting system property!");
                    SystemProperties.set("ctl.stop", "bootanim");
                } catch (Exception e) {
                    Slog.d(android.os.Bolt.TAG, "Try 'setprop ctl.stop bootanim' failed. Check SELinux policy.");
                }
            } else {
                try {
                    IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
                    if (surfaceFlinger != null) {
                        Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
                        Parcel data = Parcel.obtain();
                        data.writeInterfaceToken("android.ui.ISurfaceComposer");
                        surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
                                data, null, 0);
                        data.recycle();
                    }
                } catch (RemoteException ex) {
                    Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
                }
            }

            EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
            Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
            mDisplayEnabled = true;
            if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");

            // Enable input dispatch.
            mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
        }

        try {
            Slog.i(TAG_WM, "Process check[PHASE_BOOT_COMPLETED] step-3 performEnableScreen {am-start -> bootAnimationComplete}");
            mActivityManager.bootAnimationComplete();
        } catch (RemoteException e) {
        }

        mPolicy.enableScreenAfterBoot();

        // Make sure the last requested orientation has been applied.
        updateRotationUnchecked(false, false);
    }

在 WindowManagerService.java 文件的 performEnableScreen() 函数中,不仅调用了 BOOT_FINISHED 接口,还直接设置了 service.bootanim.exit = 1,这里对应了搜索 service.bootanim.exit = 1 的两处调用。

这里我们继续搜索 performEnableScreen() 函数的调用,这里通过加堆栈日志发现,在 ActivityThread 中通过内部类 Idler 调用了am.activityIdle(a.token, a.createdConfig, stopProfiling),最终设置了结束开机动画标识。

Idler Handler –>
ActivityTaskManagerService.java:1675 activityIdle –>
ActivityStackSupervisor.java:1310 activityIdleInternalLocked –>
ActivityStackSupervisor.java:1253 checkFinishBootingLocked –>
ActivityStackSupervisor.java:1261 mService.postFinishBooting(booting, enableScreen); –>
ActivityTaskManagerService.java:5678 postFinishBooting –>
ActivityTaskManagerService.java:6482 enableScreenAfterBoot –>
WindowManagerService.java:3235 enableScreenAfterBoot –>
WindowManagerService.java:3282 performEnableScreen –>
SystemProperties.set(“service.bootanim.exit”, “1”);

这里的 ActivityThread 是应用的 UI 线程,应用主线程加载完毕之后,会结束开机动画。在系统启动的第一个应用是 Launcher。

下图引用资源描述了开关机动画的加载结束过程:
在这里插入图片描述

4、如何替换开关机动画

对于自定义开关机动画,可以在开关机路径下放入自定义的资源文件,采用原生机制实现替换。

#开机动画路径
static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] =
"/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] =
"/system/media/bootanimation.zip";
#关机动画路径
static const char OEM_SHUTDOWNANIMATION_FILE[] =
"/oem/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] =
"/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] =
"/system/media/shutdownanimation.zip";

开关机动画资源路径和文件名是固定的:

路径:/oem/media/、/product/media/、/system/media/
命名:bootanimation.zip(开机动画)、shutdownanimation.zip(关机动画)

若以上三个路径下均无动画素材,则会使用system/framework/framework-res.apk中assets目录下的 两张图片做动画

/assets/images/android-logo-shine.png
/assets/images/android-logo-mask.png

以 bootanimation.zip 为例:

资源结构为:

bootanimation.zip : tree
|-- desc.tdxt
|-- part0
|-- part1

desc.txt 是配置文件,描述了播放的规则,bootanimation 播放前会解析播放设置,内容解释如下所示:

1280 768 30  //1280、720 是图片的宽高 24 是每帧播放 24 张图片
c 1 0 part0
p 0 0 part1

第一行:
	1280 720 表示图片的分辨率,这个可以根据系统屏幕分辨率和想要的动画效果自己定义
	24 表示帧率,即每s播放几帧图片。

第二、三行:
	c:为标识符,早期版本值都是p,即part的意思,代表这一行为一个动画片段。
		【现在新增了c 标识符(具体什么版本加入未做深究),表示该动画片段至少会执行一次,如果系统发出终止动画指令,p标识的part会立刻停止执行,而c标识的part会继续播放完所有帧后停止,之后的part也是一样,如果标识为p,则不执行,为c,则执行一次,直到所有part都执行完。】
	1:表示当前part循环次数,如果为0表示无限循环。
	0:表示延时n帧的时间后播放下一个part,
		【比如当前例子里,帧率是24,那一帧时长为1/24s,所以延时n帧的时间就是n*(1/24)s。】
	part0:是指图片文件夹的名字。

图片的宽高最好保持一致,避免动画播放的效果不理想。 客户提供的素材根据黑屏分出每一个类似动画的素材,每份素材存放一个目录。根据需求调整帧率、动画执行的频率。

压缩包中不能存在二级目录 在 windows 上压缩时需要选择“仅存储”模式压缩。

mac、Linux下使用命令压缩:zip -r -0 bootanimation.zip *

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值