开机动画流程分析
- 开机动画流程图
2.开机动画流程
2.1 开机动画进程启动
① Init进程启动以后会根据init.rc配置来启动surfaceflinger服务
② Surfaceflinger的init()方法中启动一个叫StartBootAnimThread的线程专门来启动开机动画进程
LOCAL_INIT_RC := surfaceflinger.rc ifeq ($(TARGET_USES_HWC2),true) LOCAL_CFLAGS += -DUSE_HWC2 endif LOCAL_SRC_FILES := main_surfaceflinger.cpp
mStartBootAnimThread = new StartBootAnimThread();
if (mStartBootAnimThread->Start() != NO_ERROR) {
ALOGE("Run StartBootAnimThread failed!");
}
③ StartBootAnimThread线程中是通过ctl.start命令的方式来启动BootAnimation。这里设置一个service.bootanim.exit系统变量,用来标记系统开始启动,为0表示还没有启动完成,为1表示系统启动完成,后面动画退出要靠检测这个变量来判断系统是否启动完成并且退出动画。
bool StartBootAnimThread::threadLoop() { property_set("service.bootanim.exit", "0"); property_set("ctl.start", "bootanim"); return false; }
④ 开机动画进程起来以后就会回调onFirstRef方法,并在这个方法中来创建播放动画的主线程(BootAnimation线程),到此开机动画进程就运行起来了。
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); } }
2.2 动画包获取
① BootAnimation构造函数中读取mboot中的parse_logo_path变量,这个变量决定动画包的存储路径。
if (mbootenv_get("parse_logo_path")) strncpy(variety_bootanim_path, mbootenv_get("parse_logo_path"), strlen(mbootenv_get("parse_logo_path"))); else { strncpy(variety_bootanim_path, "", strlen("")); }
② readyToRun()函数中确定动画包路径
if((strcmp(variety_bootanim_path,"") == 0)||(strcmp(variety_bootanim_path,"/tclconfig/mstar/tvconfig/") == 0) || (strcmp(variety_bootanim_path,"/tclconfig") == 0)){ memset(variety_bootanim_path,0,sizeof(variety_bootanim_path)); strncpy(variety_bootanim_path, VENDOR_BOOTANIMATION_FILE, strlen(VENDOR_BOOTANIMATION_FILE)); strncpy(variety_templet_path , VENDOR_TEMPLET_FILE , strlen(VENDOR_TEMPLET_FILE)); strncpy(variety_adanim_path,VENDOR_AD_BOOTANIMATION_FILE,strlen(VENDOR_AD_BOOTANIMATION_FILE)); } else { strncpy(variety_templet_path,variety_bootanim_path,strlen(variety_bootanim_path)); strncpy(variety_adanim_path,variety_bootanim_path,strlen(variety_bootanim_path)); strcat(variety_bootanim_path , "bootanimation.zip"); strcat(variety_templet_path , "animtemplet.zip"); strcat(variety_adanim_path , "adbootanimation.zip"); }
2.3 动画包解析
动画包路径获取到了之后,将在线程的主体函数threadLoop()中调movie方法加载资源,所有的资源都加载到animation对象中。
if (mZip == NULL) { r = android(); } else { r = movie(); }
bool BootAnimation::movie() { Animation animation; createPart(&animation); createFrame(&animation , mZip); createFrame(&animation , mTempletZip);
}
2.4 动画播放
① 第一层控制播放animation中的所有part
② 第二层控制当前播放的这一part是否循环播放
③ 第三层控制播放一个part的所有frame
size_t pcount = animation.parts.size();for (size_t i=0 ; i<pcount ; i++) {
for (int r=0 ; !part.count || r<part.count ; r++) { for (size_t j=0 ; j<fcount && (!exitPending()); j++) { // #endif /* VENDOR_EDIT */ const Animation::Frame& frame(part.frames[j]); nsecs_t lastFrame = systemTime();
2.5优化前后动画包
2.5.1 优化前的配置
1920 1080 24
p 1 0 part0
if (sscanf(desc_line, "%d %d %d", &width, &height, &fps) == 3) { ALOGE("width=%d, high=%d, fps=%d", width, height, fps); animation->width = width; animation->height = height; animation->fps = fps; }
else if (sscanf(line, " %c %d %d %s", &pathType, &count, &pause, path) == 4) {
ALOGE("type=%c, count=%d, pause=%d, path=%s", pathType, count, pause, path);
addPart(animation ,false , count , pause , path , NULL);
}
2.5.2 优化后的配置
1920 1080 24
b=bg.jpg
p 1 0 boot #710+0+500x736
p 0 0 loop #710+0+500x736
else if (sscanf(line, "%c=%s", &bgFlag, bgPath) == 2) { ALOGD("bgFlag=%c,bgPath=%s", bgFlag, bgPath); if ((bgFlag == 'b') && (strlen(bgPath) > 0)) { animation->bgPath = bgPath; } } else if (sscanf(line, "%c %d %d %s #%d+%d+%dx%d", &pathType, &count, &pause, path, &fx, &fy, &fw, &fh) == 8) { ALOGD("type=%c, count=%d, pause=%d, path=%s, fx=%d, fy=%d, fw=%d, fh=%d", pathType, count, pause, path, fx, fy, fw, fh); bool valid = (fx >= 0) && (fy >= 0) && (fw > 0) && (fh > 0); Animation::BootForeground* foreground = nullptr; if (valid) { foreground = new Animation::BootForeground; foreground->frontX = fx; foreground->frontY = fy; foreground->frontWidth = fw; foreground->frontHeight = fh; } addPart(animation ,false , count , pause , path , NULL, foreground); hasInfinitePart = (count == 0); }
2.6 动画退出
动画的退出是通过检测service.bootanim.exit变量来检测系统是否启动完成,如果这个变量为1表示系统启动完成,此时就可以退出动画进程了。在播放动画的循环中,播放没一帧都会检测一次,只要系统已启动完成就会被检测到并且退出动画进入系统。
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
property_get(EXIT_PROP_NAME, value, "0"); exitnow = atoi(value); if (exitnow) { requestExit(); }
if(checkExit()) { ALOGD("--- video over,can exit ---"); return false; }
2.7 确定是否进信源
property_get( "persist.sys.sbootoui", bootouiactivity, "" ); if( strcmp("", bootouiactivity)==0 ){ property_get( "persist.sys.bootfromui", bootactivity, "" ); }else{ ALOGE("Boot to ui forever!!!"); strcpy(bootactivity , "ui"); }
3.部署开机动画及视频播放流程
3.1 部署开机动画与视频的检测
由于动画和视频都可以通过云端部署,所以系统定义了部署开机动画的优先级高于开机视频,所以检测开机动画是否存在。如果在data/local目录下存在bootanimation.zip就优先播放部署的动画;
如果没有动画而存在bootvideo就播放开机视频,如果都没有则播放默认。
if( (access(DATA_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(DATA_BOOTANIMATION_FILE)) != NULL) ) {
if(access(SYSTEM_DATA_BOOT_VIDEO_ENCRYPT, R_OK) == 0) {if(strcmp(decrypt_md5 , checked_md5) == 0) { usrBootAnim = true; ret = 0; } } }
else if (access(DATA_BOOTVIDEO_FILE, R_OK) == 0) { bootanimForVideo = true; } else if( (access(variety_bootanim_path, R_OK) == 0) && ((zipFile = ZipFileRO::open(variety_bootanim_path)) != NULL) ){ mZip = zipFile; } else if( (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL) ) { mZip = zipFile; }
3.2 部署开机动画的播放
这里获取了部署的开机动画后,走的流程就会原生一样,同样去解析和播放动画包
3.3 部署开机视频的播放
开机视频是通过中间件来播放的。
4. 与中间件通信
开机动画过程中通过管道读写的方式来与中间件通信,主要是中间件需要通过管道写入信息,通知BootAnimation进程,包括是否已开始播放、是否已允许返回以及是否播放完成。
4.1 管道信息定义
#define FIFOMAX 1024 #define FIFONAME "/var/bootanimationAndBootvideo" int readfd = -1; #define VIDEOALLOW "logo_completed" #define ALLOWRETURN "allow_return" #define VIDEOOVER "video_completed"
4.2 创建打开管道
if(bootanimForVideo == true){ /* create FIFO; OK if they already exist */ if ((mkfifo(FIFONAME, 0666) < 0) && (errno != EEXIST)){ ALOGD("can't create FIFO"); } }
if(firstPlay == true){ readfd = open(FIFONAME,O_RDONLY); if(readfd == -1){ ALOGD("can't open FIFO"); }
}
4.3 倒计时
由于需要实现倒计时功能,主线程不能阻塞,所以在主线程中只读取第一个消息,如果是logo_completed表示视频校验成功且开始播放。这时我们开启一个ADFIFOThread线程专门去负责读取后面的管道消息。
read(readfd,buff,sizeof(buff));
if(strcmp(buff,VIDEOALLOW)==0){
if(mADFIFOThread == NULL){ mADFIFOThread = new ADFIFOThread(this); mADFIFOThread->setADInfoModule(mADInfoModule); mADFIFOThread->run("ADFIFOThread", PRIORITY_DISPLAY); } }
如果第一条消息是video_completed表示视频校验失败,直接加载默认动画,并走默认流程。
else if(strcmp(buff,VIDEOOVER)==0){ ZipFileRO* zipFile = NULL; if( (access(variety_bootanim_path, R_OK) == 0) && ((zipFile = ZipFileRO::open(variety_bootanim_path)) != NULL) ){ mZip = zipFile; }
else if( (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL) ) { mZip = zipFile; } animation.parts.clear(); createPart(&animation); createFrame(&animation , mZip); createFrame(&animation , mTempletZip); }
4.3.1 广告倒计时字体显示实现
如果视频校验成功,需要初始化视频广告信息显示模块,并且创建一个线程用来做广告倒计时,倒计时的信息更新就在这个线程中更新,主线程负责刷新画面,实现动态计时效果。
if(mADInfoModule == NULL) { mADInfoModule = new ADInfoModule(this); } if(mADTimer == NULL) { mADTimer = new ADTimer(this); mADTimer->setADInfoModule(mADInfoModule); mADTimer->run("ADTimer", PRIORITY_DISPLAY); }
在主线程中while循环来实时刷新画面,刷新率24/S
if(bootanimForVideo && i > LOGO_PART && firstPlay){ mADInfoModule->draw();
}
int AD_INFO_FPS = 1000000 / 24;
void BootAnimation:: ADInfoModule::draw(){ if(videoComplate){ return; } while(1){ onDraw(); if(videoComplate){ break; } usleep(AD_INFO_FPS); } }
读取管道的线程
bool BootAnimation::ADFIFOThread::threadLoop() { if ((mkfifo(FIFONAME, 0666) < 0) && (errno != EEXIST)){ ALOGD("--- can't create ADFIFOThread FIFO ---"); } int fifoID = open("/var/bootanimationAndBootvideo",O_RDONLY); if(fifoID == -1){ ALOGD("---ADFIFOThread can not open FIFO ---"); } int i = 0; int readCount = 3; for(i = 0;i < readCount;i++) { char buff[FIFOMAX] = {0}; read(fifoID,buff,sizeof(buff)); ALOGD("--- Read FIFO: %s ---",buff); if(strcmp(buff,VIDEOOVER)==0){ mADInfoModule->videoComplate = true; break; } } if(access(FIFONAME,R_OK)==0){ unlink(FIFONAME); } return false; }
倒计时线程
bool BootAnimation::ADTimer::threadLoop() { mADInfoModule->startTimer = true; for(mADInfoModule->playTime = 0;mADInfoModule->playTime < mADInfoModule->totalTime;mADInfoModule->playTime++) { if(mADInfoModule->videoComplate) // if video complate stop timr; { break; } ALOGD("--- ADTimer playTime=%d ---",mADInfoModule->playTime); mADInfoModule->surplusTime = mADInfoModule->totalTime - mADInfoModule->playTime; mADInfoModule->backSupTime = mADInfoModule->backTime - mADInfoModule->playTime; usleep(1000000); }return false; }
在ADFIFOThread线程中读取管道消息,一旦接收到video_complete消息就将videoComplete赋值true,在主线程检测到就会推出循环停止刷新,并继续往下执行,如果这时系统已经启动完成则退出动画,否则继续播放默认动画以等待系统启动完成。
4.3.2 可返回功能实现
可返回功能是中间件在指定倒计时可返回结束后,接收到用户按返回键,直接停止播放视频,并向管道写入video_complete消息,BootAnimation接收到消息直解退出广告计时。如果此时系统已经启动完成,直接进入系统,否则继续播放默认动画等待系统启动完成。