本文主要讲解Android ROM开发,如何定制开关机动画,以及Android开机系统启动过程。
Android开机动画总共有三个过程。第一个开机动画是在Kenel启动时显示的,第二个开机动画是在init进程启动时显示的,这两个都是静态图片。第三个动画是在系统服务启动过程中显示的,他是一个动态图片,也是我们关注比较多的动画。 关于动画的播放,主要涉及frame buffer的知识,可以参考老罗的分析http://blog.csdn.net/luoshengyang/article/details/7691321/,从底层的角度,理清了Android的开机动画播放过程。 现在主要关注与开机动画相关的几个类、文件。
一、Kenel动画
Kenel logo是开机显示的第一个动画,用于显示Android内核正在启动过程中。播放的图片位于 vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo,系统读取目录下的文件夹里面的logo图片(fwvga_kernel.bmp)显示到手机上的 。那么问题来了,系统默认读的是哪个文件夹? 如何客制化开机Kenel logo?我一修改这个文件夹不是所有的其他国家的logo都要变?
1.1 系统默认使用哪个文件夹?
其实系统是有一套机制控制Kenel logo的,这也是客制化开机动画的基础,该目录下,有一个mk文件—rules.mk,他会决定当前系统使用哪一套开机kenel logo。
LOCAL_DIR := $(GET_LOCAL_DIR)
BOOT_LOGO_DIR := $(LOCAL_DIR)
#fix no boot_logo config
#LOCAL_CFLAGS += -DBOOT_LOGO=wvga
ifeq ($(strip $(BOOT_LOGO)),)
BOOT_LOGO = fwvga
endif
ifeq ($(strip $(MTK_LK_CAMERA_SUPPORT)), yes)
BOOT_LOGO = fhd
endif
$(info BOOT_LOGO = $(BOOT_LOGO))
$(info lk/logo/dir=$(LOCAL_DIR),builddir=$(BUILDDIR))
由上段代码可以看出来,在BOOT_LOGO没有赋值的情况下,默认使用fwvga文件夹下的Kenel logo。
1.2 如何客制化开机Kenel logo?
看到这里,我们可以发现,如果要客制化开机Kenel logo图片,可以在rules.mk里面添加相关判断,以达到目的。以海外版本为例,我需要在发货海外的版本中,客制化kenel logo,就需要
1、在rules.mk中添加一下代码
#added by guohongcheng for kenel logo start
ifeq ($(strip $(KENEL_LOGO_PROC)), yes)
BOOT_LOGO = hd720
Endif
#added by guohongcheng for kenel logo end
这样当系统属性KENEL_LOGO_PROC是yes的时候,BOOT_LOGO会加载hd720下面的图片,
2、在ProjectConfig.mk中设置KENEL_LOGO_PROC值
而KENEL_LOGO_PROC这个属性值是在项目的ProjectConfig.mk设置的,添加一下语句
#added by guohongcheng for kenel logo start
KENEL_LOGO_PROC=yes
#added by guohongcheng for kenel logo end
3、配置完成过后我们只需在自己的自拟定文件夹hd720 中,( vendor/mediatek/proprietary/bootable/bootloader/lk/dev/logo
中将logo替换即可。
注意:
(1) 替换时所有图片的命名(不只是2张静态logo,其他的图片也需要拷贝到自拟定目录下进行改名)必须和默认文件夹保持一致 例如 xxxxxxxx_kernel.bmp 和 xxxxxxxx_uboot.bm
(2) 一般情况下要改61处文件名 这里给大家提供一个批量修改文件名的命令,防止人为造成的编译’find -type f | grep cmcc | xargs rename s/cmcc_lte_hd720/tl_lte_hd720_Italian/’参数为筛选选条件 第二个参数为需要替换的部分 第三个参数为替换成的部分 |
二、init动画
2.1 第二个开机动画的播放过程
第二个开机动画是在init进程开启时显示的图片,也就是在开启一系列系统进程的过程中显示。 init进程的入口函数main实现在文件system/core/init/init.cpp
中,最终会调用console_init_action方法,代码如下:
static int console_init_action(const std::vector<std::string>& args)
{
std::string console = property_get("ro.boot.console");
if (!console.empty()) {
console_name = "/dev/" + console;
}
int fd = open(console_name.c_str(), O_RDWR | O_CLOEXEC);
if (fd >= 0)
have_console = 1;
#ifdef MTK_INIT
else
ERROR("console_init: can't open %s\n", console_name.c_str());
#endif
close(fd);
fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n" // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
显示第二个开机画面是通过调用函数load_565rle_image来实现的。在调用函数load_565rle_image的时候,指定的开机画面文件为INIT_IMAGE_FILE。INIT_IMAGE_FILE是一个宏,定义在system/core/init/init.h
文件中,如下所示:
#define INIT_IMAGE_FILE "/initlogo.rle"
, 即第二个开机画面的内容是由文件/initlogo.rle来指定的。如果文件/initlogo.rle不存在,或者在显示它的过程中出现异常,那么函数load_565rle_image的返回值就会等于-1,这时候函数console_init_action就以文本的方式来显示第二个开机画面,即向编号为0的控制台(/dev/tty0)输出“ANDROID”这7个字符。
2.2 init动画客制化
init logo的客制化与kenel logo的客制化相同,他对应logo文件夹下的fwvga_uboot.bmp。
三、第三个开机动画
3.1 制作开关机动画
1、开机动画的位置
system/media/bootanimation.zip
,要修改开机动画就是修改bootanimation这个压缩文件。如果不存在该压缩包,使用原生自带的资源,其路径在system/framework/framework-res.apk/assets/images
(android-logo-mask.png,android-logo-shine.png),但是比较难看,比较常见的就是“android”。所以要定制自己的开关机动画一般都是在system/media/目录下放置bootanimation.zip和shutanimation.zip.这里以开机动画为例,关机动画和开机动画其原理一样。
2、bootanimation.zip文件结构
bootanimation里面主要包含一个desc.txt以及N个文件夹。而文件夹里面放着的就是开机动画的图片资源。decs.txt的作用就是指导系统如何去执行开机动画。 desc.txt编写规范,例如开机动画需要用到2个文件夹,分别是folder1和folder2,开机的时候,先把folder1里面的图片都播放一遍,然后再循环播放folder2里面的文件,直到进入系统,decs.txt文档的内容如下:
320 480 12
p 1 0 folder1
p 0 0 folder2
320 480是代表屏幕的分辨率,12表示12帧每秒,简单地说12代表一秒钟播放12张图片;
p 1 0 part1:p就是play。1是播放一次,0是无限次。0代表阶段间隔帧数为0。folder1就是说,这条指令是针对folder1这个文件夹的;
p 0 0 part2:第一个0这里是代表循环播放,第二个0和上面第二条指令一样。folder2就是第二个文件夹。
总结规则如下: 第一条指令:[屏幕的分辨率] [播放频率]
第二条指令:[p] [播放次数] [间隔帧数] [文件夹]
第N条指令: 同上
3、压缩包
把需要用到的folder文件夹跟decs.txt打包成zip格式,必须是zip,不能是rar,且打包的时压缩方式选择“存储”模式。然后改名成为bootanimation.zip,最后将制作好的zip包push到/system/media目录下。
注意:bootanimation不能太大,一般最好不要超过3M。
3.2 bootanimation的启动过程
第三个开机画面是由应用程序bootanimation来负责显示的。应用程序bootanimation在启动脚本system/core/rootdir/init.rc
中被配置成了一个服务,如下所示:
service bootanim /system/bin/bootanimation
class core
user graphics
group graphics audio cw_access
disabled
oneshot
应用程序bootanimation的用户和用户组名称分别被设置为graphics。注意, 用来启动应用程序bootanimation的服务是disable的,即init进程在启动的时候,不会主动将应用程序bootanimation启动起来。当SurfaceFlinger服务启动的时候,它会通过修改系统属性ctl.start的值来通知init进程启动应用程序bootanimation,以便可以显示第三个开机画面,而当System进程将系统中的关键服务都启动起来之后,ActivityManagerService服务就会通知SurfaceFlinger服务来修改系统属性ctl.stop的值,以便可以通知init进程停止执行应用程序bootanimation,即停止显示第三个开机画面。接下来我们就分别分析第三个开机画面的显示过程和停止过程。
Zygote进程在启动的过程中,会将System进程启动起来,System进程在启动的过程中,会调用SurfaceFlinger类的静态成员函数instantiate来启动SurfaceFlinger服务。System进程在启动SurfaceFlinger服务的过程中,首先会创建一个SurfaceFlinger实例,然后再将这个实例注册到Service Manager中去。在注册的过程,前面创建的SurfaceFlinger实例会被一个sp指针引用。当一个对象第一次被智能指针引用的时候,这个对象的成员函数onFirstRef就会被调用。由于SurfaceFlinger重写了父类RefBase的成员函数onFirstRef,因此,在注册SurfaceFlinger服务的过程中,将会调用SurfaceFlinger类的成员函数onFirstRef。在调用的过程,就会创建一个线程来启动第三个开机画面。
SurfaceFlinger是在framework/native/services/surfaceflinger/Main_surfaceflinger.cpp
被启动的,首先会执行init方法,然后自行run方法,代码如下:
// initialize before clients can connect
flinger->init();
// publish surface flinger
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
// publish GpuService
sp<GpuService> gpuservice = new GpuService();
sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
// run surface flinger in this thread
flinger->run();
在framework/native/services/surfaceflinger/SurfaceFlinger.cpp下,init()会方法的最后执行startBootAnim()
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
HAL_PIXEL_FORMAT_RGBA_8888);
......
// set initial conditions (e.g. unblank default device)
initializeDisplays();
// start boot animation
startBootAnim();
ALOGV("Done initializing");
}
startBootAnim()方法代码如下,它调用函数property_set来将系统属性“ctl.start”的值设置为“bootanim”,表示要将应用程序bootanimation启动起来,以便可以显示第三个开机画面。
void SurfaceFlinger::startBootAnim() {
#ifdef MTK_AOSP_ENHANCEMENT
// dynamic disable/enable boot animation
checkEnableBootAnim();
#else
// start boot animation
property_set("service.bootanim.exit", "0");
property_set("ctl.start", "bootanim");
#endif
}
当系统属性发生改变时,init进程就会接收到一个系统属性变化通知,这个通知最终是由在init进程中的函数handle_property_set_fd来处理的。 函数handle_property_set_fd实现在文件system/core/init/property_service.c
中,如下所示:
static void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
char * source_ctx = NULL;
struct pollfd ufds[1];
const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */
int nr;
......
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
#ifdef MTK_INIT
INFO("[PropSet]: pid:%u uid:%u gid:%u %s %s\n", cr.pid, cr.uid, cr.gid, msg.name, msg.value);
#endif
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
......
}
}