1. 程式人生 > >Android 開機動畫客製化

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 開機動畫的位置
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 {
......
    }
}