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方法前后调用的堆栈信息:

具体分析另起文章。

总结

相关推荐
红米饭配南瓜汤2 小时前
Android显示系统(04)- OpenGL ES - Shader绘制三角形
android·音视频·媒体
码农老张Zy2 小时前
【PHP小课堂】学习PHP中的变量处理相关操作
android·开发语言·学习·php
尹中文2 小时前
Android ConstraintLayout 约束布局的使用手册
android
苗壮.2 小时前
Android 俩个主题的不同之处 “Theme.AppCompat vs android:Theme.Material.Light.NoActionBar”
android·gitee·appcompat
努力进修3 小时前
【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石
android·java·数据结构
闲暇部落5 小时前
OpenGL ES详解——文字渲染
android·freetype·文字渲染·位图字体
画个太阳作晴天10 小时前
Android10 设备死机的问题分析和解决
android·framework·anr
SHUIPING_YANG12 小时前
Typora设置自动上传图片到图床
android
Ai 编码助手12 小时前
php多进程那点事,用 swoole 如何去解决呢
android·php·swoole
alexhilton14 小时前
群星闪耀的大前端开发
android·kotlin·android jetpack