Android源码解析之——3、开机动画

814 阅读6分钟

一、Linux开机动画

二、Android开机动画

目前Android开机动画的实现方式主要是:逐帧动画和OpenGL直接绘制。

  • 1、逐帧动画

顾名思义,动画都是按帧(Frame)进行播放,每一帧都一张静态的图片,最典型的例子就是电影。它的优点是可以播放很复杂的动画,灵活性强,缺点是数据量比较大。

通过逐帧动画实现开机动画的方法很简单:将一系列图片打包成bootanimation.zip,放到/system/media/目录下,系统将图片一帧一帧播放从而形成动面效果。理论上这种方法可以实现一切动画需求,但是实践后发现当bootanimation.zip大于5M的时候,动画将有明显卡顿,文件越大动面画越不流畅。所以一般手机的开机动画,只有很小的区域展示动画,因为他们的动画帧分辨率很小(如100*50),这样的好处是在较小的文件中,容纳更多的帧。

  • 2、OpenGL动画

OpenGL(Open Graphics Library)是个定义了一个跨编程语言、跨平台的应用程序接口(API)的规范,它用于生成二维、三维图像。这个接口由近350个不同的函数调用组成,从简单二维图形绘制到复杂三维图形渲染都能实现。

2.1、开机动画源码分析

涉及代码:

framework/base/cmds/bootanimation/
framework/native/services/surfacefligner/
system/core/init/

启动流程: 内核起来后会启动第一个进程,即init进程。

init进程根据init.rc配置启动surfaceflinger进程。

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

surfaceflinger进程启动后,开始调用main()函数。

//frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

int main(int argc, char** argv) {
....

    // instantiate surfaceflinger
    sp<SurfaceFlinger> flinger = new SurfaceFlinger(); //创建surfaceflinger服务实例

....
    flinger->init();

    // publish surface flinger
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false); //注册到service manager里

    // run in this thread
    flinger->run(); //开始运行

    return 0;
}

首先new一个SurfaceFlinger实例,然后init,然后run

//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

// Do not call property_set on main thread which will be blocked by init
// Use StartPropertySetThread instead.
void SurfaceFlinger::init() {
    ALOGI(  "SurfaceFlinger's main thread ready to run. "
            "Initializing graphics H/W...");


    // 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) { //真正启动设置bootanimation的属性线程
        ALOGE("Run StartPropertySetThread failed!");
    }

    ALOGV("Done initializing");
}

初始化graphics之后,mStartPropertySetThread()播放开机动画。//注意已经不是以前的startBootAnim方法

StartPropertySetThread如下定义:

namespace android {

StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
        Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}

status_t StartPropertySetThread::Start() {
    return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}

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;
}

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

bootanim由于设置为disabled,所以它不会自动执行。

这样bootanim进程就会启动?凭什么设置了一个属性就启动了?那么下面我们来看,

/system/core/init/init.cpp,在看init进程的init.cpp的main函数中:

int main(int argc, char** argv) {
......
 
    property_load_boot_defaults();
    export_oem_lock_status();
    start_property_service(); //start_property_service
    set_usb_controller();
}

main函数中调用了start_property_service()方法,该方法声明在/system/core/init/property_service.cpp中:

void start_property_service() {
    property_set("ro.property_service.version", "2");

    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr, sehandle);
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

在这个函数中创建了一个socket,然后通过epoll机制,去注册了一个监听处理函数。

epoll机制多路复用

init进程会使用epoll机制来轮询事件,其中一个事件是系统属性值被修改。得到该事件后,会执行handle_property_set_fd(),最终调用到handle_property_set()

static void handle_property_set(SocketConnection& socket,
                                const std::string& name,
                                const std::string& value,
                                bool legacy_protocol) {
  //......
  if (android::base::StartsWith(name, "ctl.")) {
    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
      handle_control_message(name.c_str() + 4, value.c_str());
      if (!legacy_protocol) {
        socket.SendUint32(PROP_SUCCESS);
      }
    } else {
      //......
  } else {
    //......
  }
  //......
}

该函数会进一步执行handle_control_message(),传入的参数msg.name=ctl.start,msg.value=bootanim

//system/core/init/init.cpp
void handle_control_message(const std::string& msg, const std::string& name) {
    Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
    if (svc == nullptr) {
        LOG(ERROR) << "no such service '" << name << "'";
        return;
    }

    if (msg == "start") {
        svc->Start();
    } else if (msg == "stop") {
        svc->Stop();
    } else if (msg == "restart") {
        svc->Restart();
    } else {
        LOG(ERROR) << "unknown control msg '" << msg << "'";
    }
}

该函数首先调用FindServiceByName,从service_list中查询要启动的服务是否有存在,若存在,返回服务的相关信息。

因为init.rc中有bootanimation的定义,因此在init进程执行parse_config()时,会将该服务添加到service_list中,所以bootanimation应用是存在的。然后,如果找到了该服务,就调用service_start启动服务。

service.bootanim.exit属性设为0,这个属性bootanimation进程里会周期检查,=1时就退出动画,这里=0表示要播放动画。

后面通过ctl.start的命令启动bootanimation进程,动画就开始播放了。

下面来到bootanimation的实现

frameworks/base/cmds/bootanimation/bootanimation_main.cpp

int main(int argc, char** argv)
{
    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();

        waitForSurfaceFlinger();

        // create the boot animation object
        ALOGV("Boot animation set up. Joining pool.");

        sp<BootAnimation> boot;
        //创建BootAnimation实例
        if(argc > 1){
            if(strcmp(argv[1], "shutdown") == 0){
                boot = new BootAnimation(new AudioAnimationCallbacks(),true); 
                mShutdown=true;
            }
        }else{
            boot = new BootAnimation(new AudioAnimationCallbacks(),false);
        }

        IPCThreadState::self()->joinThreadPool();//binder线程池,与surfaceflinger通信用的。
    }
    ALOGV("Boot animation exit");
    return 0;
}

bootanimation启动流程.png

void BootAnimation::onFirstRef() {
    status_t err = mSession->linkToComposerDeath(this);
    ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
    if (err == NO_ERROR) {
        run("BootAnimation", PRIORITY_DISPLAY);
    }
}
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
    int curWidth = dinfo.w;
    int curHeight = dinfo.h;
    if(mShutdown && mReverseAxis){
        curWidth = dinfo.h;
        curHeight = dinfo.w;
    }
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            /*dinfo.w*/curWidth, /*dinfo.h*/curHeight, PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::openGlobalTransaction();
    control->setLayer(0x40000000);
    SurfaceComposerClient::closeGlobalTransaction();

    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_DEPTH_SIZE, 0,
            EGL_NONE
    };
    EGLint w, h;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglInitialize(display, 0, 0);
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    context = eglCreateContext(display, config, 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 &&
        (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) {
        mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE;
        return NO_ERROR;
    }
    static const char* bootFiles[] = {OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};
    static const char* shutdownFiles[] =
        {OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE};

    for (const char* f : (/*!mShuttingDown*/!mShutdown ? bootFiles : shutdownFiles)) {
        if (access(f, R_OK) == 0) {
            mZipFileName = f;
            return NO_ERROR;
         }
     }
       //add for boot video
       mVideoAnimation = false;
       if (access(SYSTEM_BOOTVIDEO_FILE, R_OK) == 0){
          mVideoFile = (char*)SYSTEM_BOOTVIDEO_FILE;
       } else if (access(DATA_BOOTVIDEO_FILE, R_OK) == 0){
          mVideoFile = (char*)DATA_BOOTVIDEO_FILE;
       }
       property_get("persist.sys.bootvideo.enable",decrypt, "false");
       char value[PROPERTY_VALUE_MAX];
       property_get("persist.sys.bootvideo.showtime", value, "-1");
       if(mVideoFile != NULL && !strcmp(decrypt, "true") &&(atoi(value)!=0)) {
            mVideoAnimation = true;
       }else{
            ALOGD("bootvideo:No boot video animation,EXIT_VIDEO_NAME:%s,bootvideo.showtime:%s\n",decrypt,value);
       }
       //add end
    return NO_ERROR;
}
bool BootAnimation::threadLoop()
{
    bool r;
    //add for boot video function
    mStartbootanimaTime = 0;
    mBootVideoTime = -1;

    if (mVideoAnimation){
        r = video();
    } else {
	    // 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;
}

Firefly官方提供的 开机log动画修改