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、
#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 開機動畫的位置
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.這裡以開機動畫為例,關機動畫和開機動畫其原理一樣。
3.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.3 壓縮包
把需要用到的folder資料夾跟decs.txt打包成zip格式,必須是zip,不能是rar,且打包的時壓縮方式選擇“儲存”模式。然後改名成為bootanimation.zip,最後將製作好的zip包push到/system/media目錄下。
注意:bootanimation不能太大,一般最好不要超過3M。
@3.1 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服務。Sytem程序在啟動SurfaceFlinger服務的過程中,首先會建立一個SurfaceFlinger例項,然後再將這個例項註冊到Service Manager中去。在註冊的過程,前面建立的SurfaceFlinger例項會被一個sp指標引用。從前面Android系統的智慧指標(輕量級指標、強指標和弱指標)的實現原理分析一文可以知道,當一個物件第一次被智慧指標引用的時候,這個物件的成員函式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 {
......
}
}