android
开机画面是由应用程序bootanimation来负责显示的,主要类BootAnimation.cpp直接继承于android中定义的一个Thread,
接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。
当应用程序启动时,是先从main开始,具体代码如下:
sp boot = new BootAnimation();
//add video boot
memset(value,0,sizeof(value));
property_get("service.bootvideo", value, "0");
gUseBootVideo = (atoi(value) == 1 ? true : false);
if(gUseBootVideo){
// boot->setBootVolume();
/* modify,bootanimation path */
/*before modify code(property_set("service.bootvideo", "2");)*/
if (access(USER_BOOTVIDEO_FILE, R_OK) == 0) {
property_set("service.bootvideo", "2");
}else{
gUseBootVideo = false;
}
/*modify end */
}
如果access(USER_BOOTVIDEO_FILE, R_OK) == 0),即bootvideo文件存在则会执行property_set("service.bootvideo", "2"),则启动系统内的视频播放应用程序LibPlayer播放开机视频,同时gUseBootVideo =true代表bootanimation播放的是开机视频。否则播放开机动画。
所以当开机视频文件bootvideo存在时,应用程序bootanimation会做2件事情:
一:执行到sp boot = new BootAnimation();时会调用BootAnimation对象的成员函数onFirstRe,在onFirstRe里会执行线程的run方法,然后会调用动画的android或movie方法来显示动画(既然是为了播放开机视频,开机动画就没必要播放,后续在播放动画的方法中必须跳过GLES的绘图逻辑以免出现动画和视频同时出现的BUG)。
二:执行 到property_set("service.bootvideo", "2");时,则启动系统内的视频播放应用程序LibPlayer播放开机视频。
之前说到BootAnimation对象的成员函数onFirstRe执行后会经过一系列的调用最终执行到播放开机动画,具体流程如下:
1.在onFirstRe中调用 run("BootAnimation", PRIORITY_DISPLAY);创建线程
2.这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些初始化工作,这里我们需要注意mAndroidAnimation这个bool,它是来决定后面的动画走的是原生android还是我们自定义的movie
3.调用BootAnimation类的成员函数htreadLoop来显示第开机动画,这里我们走的是movie。
- bool BootAnimation::threadLoop()
- {
- bool r;
- if (mAndroidAnimation) {
- r = android();
- } else {
- r = movie();
- }
- .............
- return r;
- }
4.调用movie来解压bootanimation.zip,并通过解析
desc.txt
显示里面一张张的图片。
desc.txt
是
用来描述用户自定义的开机动画是如何显示的,格式如下:
- 600 480 24
- p 1 0 part1
- p 0 10 part2
具体的书写格式j及含义我就不多说了自行谷歌,但是需要大家注意的一点就是无论是什么样的
desc.txt
它的最后一行应该基本都是
- p 0 xx xxxx
第二个参数0代表的就是part文件夹中的图片是无限循环显示的,我们看到的情况就是屏幕上显示了一张静态的图片。为什么要无限循环显示?
答案就是
bootanimation在等一个消息,它在等launcher是否已经准备好了,如果launcher准备好了,那么它就停止循环,让launcher显示出来,否则就一直循环播放,直到launcher准备好。而launcher准备好的消息最终是由
SurfaceFlinger::bootFinished()
中的一个prop属性来发出的,而动画在循环播放中会不停的通过BootAnimation的checkExit来读取这个属性值,如果为真则调用其父类
Thread的函数requestExit退出线程,并且break退出for循环,最后返回false结束播放。
-----------------------------------------------------分割线--------针对开机动画卡在logo-----------------------------------------------------------------------
movie中我们只关心动画的能否正常显示(至于其他的绘图呀什么的太难了,表示不懂),以及动画能否正常退出。好了,假如是执行的是原生的android源码并且
bootanimation.zip是正确的,那么一切都好说,显示肯定是没有问题的。但是,万能的ROM厂商不答应啊,开机动画太单调了,还是换个广告植入下吧~于是动不动就推送一个bootanimation.zip给你,所以你每次开机就会看到不同的开机动画。于是问题就来了,广告发的太多万一哪天不小心推送了一个错误的bootanimation.zip给用户,则很有可能导致卡在开机动画那里了,从而会导致launcher起不来。所以客户提出的需求就是:不管bootanimation.zip对不对,你得让launcher起来,动画播不播就别管了。
下面分析下错误的bootanimation.zip的几种情况:
①没有
desc.txt
文件,源码中已经进行了判断,直接返回
ZipFileRO& zip(mZip);
size_t numEntries = zip.getNumEntries();
ZipEntryRO desc = zip.findEntryByName("desc.txt");
FileMap* descMap = zip.createEntryFileMap(desc);
ALOGE_IF(!descMap, "descMap is null");
if (!descMap) {
return false;
}
②
desc.txt
文件中定义的图片包名和实际的不匹配(如
定义的是part1
,但是实际的是part),而movie的for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息,这些信息都保存在Animation对象animation中,而后面的绘图就是根据这些信息来完成的。而错误的bootanimation.zip会导致
const
size_t
pcount = animation.parts.size() 及const size_t fcount = part.frames.size();
均为0,而源码中并没有对这种情况进行判断所以会造成movie中的流程不正确导致屏幕一直卡在logo。
movie中最后有一个三层的for循环,主要工作就是根据之前拿到的animation来进行绘图工作:
当
pcount =0时,会直接跳过for循环,直接返回false
当
fcount =0时,无法进入第三个for循环,会在第二个for那里进行无限循环!
由于我手上的代码并不是原生的android源码,而是项目code,所以关于画面显示的原理可能不太一样。我们的开机视频及开机动画是在Video层播放的,而launcher的显示是在OSD层,所以在视频和动画播放结束后需要将display_mode从Video层切换到OSD层,否则就算launcher正常启动了我们还是会看到屏幕一直卡在开机logo。
所以之前说的那2种出错的情况不能直接返回,而是需要按照正常流程那样在
SurfaceFlinger::bootFinished()
调用后进行一个setDisPlayMode操作,再返回。这样就可以避免屏幕一直卡在开机logo。
针对这些情况方法都是一样的:先调用用setDisPlayMode然后通过checkExit循环判断是否已接收到
bootFinished ,若接收到直接返回退出播放。例如:
int switch_logo_flag = 0;
while(!descMap&&!gUseBootVideo){
if(switch_logo_flag == 0){
property_set(RUNNING_PROP_NAME,"running");
switch_logo_flag=1;
}
checkExit();
if(exitPending()){
ALOGD("======tzr=====descMap is null ,return \n");
return false;
}
}
------------------------------------------------------------开机视频播放过程中出现黑屏------------------------------------------------------------------------
如果视频长度超过10s,
在播放开机视频的10后就会出现屏幕一直黑屏,直到launcher显示出来。
根据log及之前的开机动画分析可知,开机视频的流程与开机动画相比就是增加了用libPlayer播放指定文件夹中的视频文件,而当开机的准备工作完成后会执行
SurfaceFlinger::bootFinished()
会设置service.bootanim.exit=1来通知应用程序bootanimation可以停止播放动画了,然后setDisplayMode以便让launcher显示出来。但是如果播放的是长度较长的视频,在接到
SurfaceFlinger
的通知后不能停止播放,必须在播放结束后才能执行setDisplayMode。
而播放10s后的黑屏问题就是因为恰好执行了执行
SurfaceFlinger::bootFinished()
然后bootanimation立即调用了setDisplayMode。既然问题的原因已经查明,修改的方法很简单,就是当接收到service.bootanim.exit=1时,不要立即setDisplayMode,而是要等到接收到开机视频播放完成的消息后再去设置。
接收和设置的代码全部在函数checkExit中:
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(gUseBootVideo){
//
下面这个while循环就是为了判断是否已经执行了SurfaceFlinger::bootFinished(),如果是则退出循环执行下面的代码,否则就一直循环等待
do{
usleep(10*1000);
memset(value,0,sizeof(value));
property_get(EXIT_PROP_NAME, value, "0");
exitnow = atoi(value);
}while(!exitnow);
}
if (exitnow) {
if(gUseBootVideo){
-
property_set("service.bootvideo.exit", "1");
/
/这里是修改前的代码,通过这个属性设置会执行set_display_mode.sh
do{
memset(value,0,sizeof(value));
property_get("init.svc.bootvideo", value, "NULL");
/
/这个属性init.svc.bootvideo=stopped代表视频播放结束了
}while(!strcmp(value,"running"));
+
property_set("service.bootvideo.exit", "1");
}
requestExit();
}
}
这篇文章是之前很早写的基于android4.4版本,现在项目都已经基于android8.1了,代码架构也更改非常大,但是一些基础的地方还是相同的,还是发出来作为参考