Android14启动动画BootAnimation源码分析

环境参数:

  • android-14.0.0_r27
  • Ubuntu 22.04.5 LTS
  • aosp_cf_x86_64_phone-userdebug

在线源码网址:xrefandroid.com/

最近需要开发Android开机动画,所以想先来分析一下Android系统开机动画的流程。开机动画进程主要分为三个步骤:动画进程如何启动动画的绘制 以及动画进程如何结束

开机动画主要涉及三个进程,先把其在AOSP源码中的位置列出来:

init: system/core/init

bootanim: framework/base/cmds/bootanimation

surfaceflinger: framework/native/services/surfacceflinger

Linux用户进程的鼻祖 - init进程

系统开机,Linux内核从无到有的第一个进程为swapper(pid=0),进而孵化Linux系统的用户进程 - Init进程(pid=1),它是所有用户进程的鼻祖。

  • init进程能孵化出ueventd,adbd,logd,installd等用户守护进程
  • init进程能启动servicemanager(Binder管家)、bootanim等重要服务
  • init进程能孵化出Zygote进程

Init进程的从/system/core/init/main.cppmain()函数开始,调用/system/core/init/init.cppSecondStageMain()函数,最终会执行LoadBootScripts()函数:

scss 复制代码
// @/system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        // late_import is available only in Q and earlier release. As we don't
        // have system_ext in those versions, skip late_import for system_ext.
        parser.ParseConfig("/system_ext/etc/init");
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

init进程会去解析各种rc文件,包括/system/etc/init/hw/init.rc文件和system/etc/init目录下的rc文件

adb查看模拟器system/etc/init文件夹中的文件:

其中包含surfaceflinger.rc和bootanim.rc文件

开机动画绘制流程

先看bootanim.rc文件:

python 复制代码
#/frameworks/base/cmds/bootanimation/bootanim.rc
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    ioprio rt 0
    task_profiles MaxPerformance

系统会去启动/system/bin/目录下的bootanimation服务(进程),但是因为下面service的设置为disabled,表示此服务不会自动启动,而是需要显示调用服务名来启动。如何启动,后面会提到。

但此bootanimation服务来源于哪呢?

再查看bootanimation的bp编译配置文件frameworks/base/cmds/bootanimation/Android.bp

arduino 复制代码
# frameworks/base/cmds/bootanimation/Android.bp
cc_binary {
    name: "bootanimation",
    defaults: ["bootanimation_defaults"],

    header_libs: ["jni_headers"],

    shared_libs: [
        "libOpenSLES",
        "libbootanimation",
    ],

    srcs: [
        "BootAnimationUtil.cpp",

        "bootanimation_main.cpp",
        "audioplay.cpp",
    ],

    init_rc: ["bootanim.rc"],

    cflags: [
        "-Wno-deprecated-declarations",
    ],
}

此文件定义了一个可执行文件bootanimation, 也就是上面所说的bootanimation服务,入口文件为bootanimation_main.cpp, init_rc文件指定为当前目录的bootanim.rc文件,编译时,系统会将bootanimation执行文件放在/system/bin/目录下

bootanimation服务启动后会先进入bootanimation_main.cppmain函数

scss 复制代码
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();

        // create the boot animation object (may take up to 200ms for 2MB zip)
        sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());

        waitForSurfaceFlinger();

        boot->run("BootAnimation", PRIORITY_DISPLAY);

        ALOGV("Boot animation set up. Joining pool.");

        IPCThreadState::self()->joinThreadPool();
    }
    return 0;
}

通过bootAnimationDisabled()函数判断系统是否跳过开机动画。需要显示开机动画时,先去创建一个BootAnimation线程实例,同时需要等待surfaceflinger启动完成后,调用run方法启动线程

上面代码中boot变量为sp类型,为sp强智能指针,其在初始化的时候会触发onFirstRef()方法调用

scss 复制代码
#/frameworks/base/cmds/bootanimation/BootAnimation.cpp#onFirstRef
void BootAnimation::onFirstRef() {
    status_t err = mSession->linkToComposerDeath(this);
    SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
    if (err == NO_ERROR) {
        // Load the animation content -- this can be slow (eg 200ms)
        // called before waitForSurfaceFlinger() in main() to avoid wait
        ALOGD("%sAnimationPreloadTiming start time: %" PRId64 "ms",
                mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());
        preloadAnimation();
        ALOGD("%sAnimationPreloadStopTiming start time: %" PRId64 "ms",
                mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());
    }
}

preloadAnimation方法会去预加载动画资源。

因为BootAnimation继承于Thread,当线程被执行run()函数时,Thread会先调用readyToRun()方法做一些初始化工作

ini 复制代码
#/frameworks/base/cmds/bootanimation/BootAnimation.cpp#readyToRun
status_t BootAnimation::TimeCheckThread::readyToRun() {
    mInotifyFd = inotify_init();
    if (mInotifyFd < 0) {
        SLOGE("Could not initialize inotify fd");
        return NO_INIT;
    }

    mBootAnimWd = inotify_add_watch(mInotifyFd, BOOTANIM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB);
    if (mBootAnimWd < 0) {
        close(mInotifyFd);
        mInotifyFd = -1;
        SLOGE("Could not add watch for %s: %s", BOOTANIM_DATA_DIR_PATH, strerror(errno));
        return NO_INIT;
    }

    addTimeDirWatch();

    if (mBootAnimation->updateIsTimeAccurate()) {
        close(mInotifyFd);
        mInotifyFd = -1;
        return ALREADY_EXISTS;
    }

    return NO_ERROR;
}

在调用readyToRun()方法后,线程再执行threadLoop()方法:

scss 复制代码
#/frameworks/base/cmds/bootanimation/BootAnimation.cpp#threadLoop
bool BootAnimation::threadLoop() {
    bool result;
    initShaders();

    // We have no bootanimation file, so we use the stock android logo
    // animation.
    if (mZipFileName.isEmpty()) {
        ALOGD("No animation file");
        result = android();
    } else {
        result = movie();
    }

    mCallbacks->shutdown();
    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 result;
}

当配置了动画资源,会执行movie()方法进行播放,当没有配置动画资源,会执行android()方法去播放原生的logo图片

movie()方法中,会去加载动画,播放动画和释放动画。

ini 复制代码
#/frameworks/base/cmds/bootanimation/BootAnimation.cpp#movie
bool BootAnimation::movie() {
    if (mAnimation == nullptr) {
        mAnimation = loadAnimation(mZipFileName);
    }
    
    //opengl
    ...
    playAnimation(*mAnimation);
    ...
    releaseAnimation(mAnimation);
    return false;
}

在播放动画函数playAnimation()中,会通过循环一帧一帧的展示图片,同时每一帧都会通过checkExit()监测是否可以退出显示了

ini 复制代码
for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
    
    for (int r=0 ; !part.count || r<part.count ; r++) {
           ...
            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
                ...
                checkExit();
            }
        }
   ...
    return true;
}

android()方法中,会先去初始化需要显示的图片纹理,然后在一个循环中去绘制出来,同时每次都会通过checkExit()监测是否可以退出显示了

ini 复制代码
bool BootAnimation::android() {
    ...
    initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
    initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
    ...
    do {
        ...
        drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);

        ...

        checkExit();
    } while (!exitPending());

    return false;
}

监测退出函数checkExit()通过获取service.bootanim.exit属性值,来判断是否退出播放线程

scss 复制代码
    
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";

void BootAnimation::checkExit() {
    // Allow surface flinger to gracefully request shutdown
    char value[PROPERTY_VALUE_MAX];
    property_get(EXIT_PROP_NAME, value, "0");
    int exitnow = atoi(value);
    if (exitnow) {
        requestExit();
    }
}

因此,init进程会去加载各种rc文件,启动对应的各种服务,因为bootanim.rc的service设置为disabled而不会在init阶段去启动bootanimation服务(进程);而当bootanimation启动时,会先在创建的线程运行之前去加载配置的资源文件,从而在线程运行时显示对应的资源动画,同时在显示动画的时候去检查是否需要退出动画显示。

如何去启动开机动画

那bootanimation是如何被启动的呢?开机动画需要屏幕绘制,屏幕绘制都与SurfaceFlinger相关,先分析一下上文剩余的另一个rc文件surfaceflinger.rc

perl 复制代码
service surfaceflinger /system/bin/surfaceflinger
    class core animation
    user system
    group graphics drmrpc readproc
    capabilities SYS_NICE
    onrestart restart --only-if-running zygote
    task_profiles HighPerformance
    socket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
    socket pdx/system/vr/display/manager    stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0
     socket pdx/system/vr/display/vsync      stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0

在init阶段,init进程会去启动位于/system/bin/下的surfaceflinger服务

surfaceflinger服务在/frameworks/native/services/surfaceflinger/Android.bp文件下定义

arduino 复制代码
// @frameworks/native/services/surfaceflinger/Android.bp
filegroup {
    name: "surfaceflinger_binary_sources",
    srcs: [
        ":libsurfaceflinger_sources",
        "main_surfaceflinger.cpp",
    ],
}

cc_binary {
    name: "surfaceflinger",
    defaults: ["libsurfaceflinger_binary"],
    init_rc: ["surfaceflinger.rc"],
    srcs: [
        ":surfaceflinger_binary_sources",
        // Note: SurfaceFlingerFactory is not in the filegroup so that it
        // can be easily replaced.
        "SurfaceFlingerFactory.cpp",
    ],
    shared_libs: [
        "libSurfaceFlingerProp",
    ],

    logtags: ["EventLog/EventLogTags.logtags"],
}

定义了一个可执行文件surfaceflinger,可执行文件surfaceflingerinit_rc文件指定为当前目录的surfaceflinger.rc文件,在编译的时候系统会将surfaceflinger.rc文件安装到system/etc/init, surfaceflinger的入口文件为main_surfaceflinger.cpp

scss 复制代码
// @frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

int main(int char**){
    ...
    // instantiate surfaceflinger
    sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger(); 
    ... 
    // initialize before clients can connect
    flinger->init(); 
    
    // publish surface flinger
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
    ...
    // run surface flinger in this thread
    flinger->run();
    ...
}

主要是创建了一个surfaceflinger实例,然后先后调用了surfaceflinger的init方法和run方法:

再来看surfaceflinger中的init方法:主要是开启一个线程,类型为StartPropertySetThread

.h文件声明变量:

java 复制代码
@frameworks/native/services/surfaceflinger/surfaceflinger.h
sp<StartPropertySetThread> mStartPropertySetThread;
scss 复制代码
@frameworks/native/services/surfaceflinger/surfaceflinger.cpp
void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
    ...
    mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);

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

线程运行时会执行到threadLoop方法:

less 复制代码
@frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp#threadLoop
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");
    property_set("service.bootanim.progress", "0");
    // Start BootAnimation if not started
    property_set("ctl.start", "bootanim");
    // Exit immediately
    return false;
}

首先会设置service.bootanim.exit属性为0,同时将ctl.start属性设置为bootanim,表示需要启动bootanim。 ctl.start属性值改变时,init进程就会收到这个变化事件,init进程就会去启动bootanimation服务。

因此init进程在init阶段会去启动surfaceflinger进程,surfaceflinger启动的时候会先创建一个SurfaeFlinger对象,在其init()函数中通过创建StartPropertySetThread线程去修改系统属性ctl.start,通知init进程去启动bootanimation进程。具体为什么修改了系统属性就能通知init进程去启动bootanimation,可参看另外一篇文章:待定~

什么时候结束开机动画

bootanimation如何启动的已经分析,那bootanimation如何结束呢?上文提到checkExit()函数,bootanimation服务通过不断获取系统属性service.bootanim.exit的值来判断是否需要结束动画

framework源码全局搜索系统哪些地方对此属性赋值: 发现有两处对service.bootanim.exit设置为1,表示退出开机动画。

先来看SurfaceFlinger.cpp中是如何发起关闭动画行为的:

typescript 复制代码
@frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::bootFinished() {
    if (mBootFinished == true) {
        ALOGE("Extra call to bootFinished");
        return;
    }
    mBootFinished = true;
    ...

    // 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");
    ...
}

在SurfaceFlinger.cpp的bootFinished()函数中修改service.bootanim.exit属性,bootanimation服务会不断获取此属性的值从而达到关闭开机动画的目的,而SurfaceFlinger::bootFinished()函数会在SurfaceComposerAIDL::bootFinished()函数中被调用:

scss 复制代码
@frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
// gui::ISurfaceComposer
binder::Status SurfaceComposerAIDL::bootFinished() {
    status_t status = checkAccessPermission();
    if (status != OK) {
        return binderStatusFromStatusT(status);
    }
    mFlinger->bootFinished();
    return binder::Status::ok();
}

SurfaceComposerAIDL::bootFinished()函数是在SurfaceComposerAIDL类中声明的函数:

css 复制代码
@frameworks/native/services/surfaceflinger/SurfaceFlinger.h
class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
public:
    SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}

    binder::Status bootFinished() override;
}

通过在framework文件夹中搜索关键字bootFinished的结果来看:

SurfaceComposerAIDL::bootFinished()函数是在SurfaceComposerClient.cpp中被调用的:

scss 复制代码
@frameworks/native/libs/gui/SurfaceComposerClient.cpp
status_t SurfaceComposerClient::bootFinished() {
    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
    binder::Status status = sf->bootFinished();
    return statusTFromBinderStatus(status);
}

SurfaceComposerClient::bootFinished() 函数在android_view_SurfaceControl.cpp的nativeBootFinished函数被调用:

ini 复制代码
@frameworks/base/core/jni/android_view_SurfaceControl.cpp
static jboolean nativeBootFinished(JNIEnv* env, jclass clazz) {
    status_t error = SurfaceComposerClient::bootFinished();
    return error == OK ? JNI_TRUE : JNI_FALSE;
}

继续往上则是从SurfaceControl.java的bootFinished()方法,通过JNI与原生方法nativeBootFinished()交互:

java 复制代码
@frameworks/base/core/java/android/view/SurfaceControl.java
public static boolean bootFinished() {
    return nativeBootFinished();
}
private static native boolean nativeBootFinished();

SurfaceControl.java的静态方法bootFinished()明显是从WindowManagerService.java中调用起来的:

csharp 复制代码
private void performEnableScreen() {
    synchronized (mGlobalLock) {
        ...
        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 (!SurfaceControl.bootFinished()) {
            ProtoLog.w(WM_ERROR, "performEnableScreen: bootFinished() failed.");
            return;
        }
        ...
    }
}

发现在调用SurfaceControl.bootFinished()之前,还会通过SystemProperties.set("service.bootanim.exit", "1")设置系统属性来通知系统关闭开机动画

因此目前观察到开机动画的停止动作发起点在WMS:

  1. 先通过设置service.bootanim.exit系统属性为1,bootanimation服务检测到后则关闭开机动画
  2. 而后再次判断开机动画是否结束,如果没有,则通过静态函数,JNI接口等一系列操作,最后在SurfaceFlinger中,设置service.bootanim.exit系统属性为1来关闭开机动画

再深挖performEnableScreen方法前后调用的堆栈信息:

具体分析另起文章。

总结

相关推荐
安卓理事人3 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学5 小时前
Android M3U8视频播放器
android·音视频
q***57745 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober5 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿6 小时前
关于ObjectAnimator
android
zhangphil7 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我8 小时前
从头写一个自己的app
android·前端·flutter
lichong9519 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端
用户693717500138410 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
火柴就是我10 小时前
NekoBoxForAndroid 编译libcore.aar
android