android7.0代码没有关机动画的实现,默认是使用关机对话弹框完成。如果需要定制化关机动画,则需要重新设计。
    思路分为两种:
    一是新建一个shutdown animation服务,完全仿照bootanimation重新拷贝一份代码然后进行相关的修改,因为动画的显示逻辑都是一样的,不同的只是文件读取路径不一样,然后就是触发地点不一样。正因为差异很小,这样做的代价很大,而且涉及到很多sepolicy的修改要添加,得不偿失。
    二是利用bootanimation的代码,通过标志位,走不同的文件读取路径。这种也是目前网上提供的最多的解决思路。
    目前主要对第二种方式的探索。
    实现不同的关机动画逻辑,这里如何进入bootanimation也有三种方法:
    1.通过新建一个shutdown animation的服务,在关机序列那里如同在surfaceflinger里面一样启动开机动画一样启动关机动画。
    2.在关机序列里面启动开机动画,但同时传入shutdown参数,让main函数通过参数选择不同的执行路径。
    3.设置一个系统属性,作为路径开关,在关机序列里面打开开关,在bootanimation里面去读取这个开关属性去决定路径。
    三个方法从思想上都是可行的,但是在实际操作中,确是有差异的。
    下面讲一下第一种方案:
    每一次关机都会执行到
    frameworks/base / services/core/java/com/android/server/power/ShutdownThread.java

private static void beginShutdownSequence(Context context) {
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            sIsStarted = true;
        }
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        ProgressDialog pd = new ProgressDialog(context);
        // Path 1: Reboot to recovery for update
        //   Condition: mReason == REBOOT_RECOVERY_UPDATE
        //
        //  Path 1a: uncrypt needed
        //   Condition: if /cache/recovery/uncrypt_file exists but
        //              /cache/recovery/block.map doesn't.
        //   UI: determinate progress bar (mRebootHasProgressBar == True)
        //
        // * Path 1a is expected to be removed once the GmsCore shipped on
        //   device always calls uncrypt prior to reboot.
        //
        //  Path 1b: uncrypt already done
        //   UI: spinning circle only (no progress bar)
        //
        // Path 2: Reboot to recovery for factory reset
        //   Condition: mReason == REBOOT_RECOVERY
        //   UI: spinning circle only (no progress bar)
        //
        // Path 3: Regular reboot / shutdown
        //   Condition: Otherwise
        //   UI: spinning circle only (no progress bar)
        if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(mReason)) {
            // We need the progress bar if uncrypt will be invoked during the
            // reboot, which might be time-consuming.
            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
            if (mRebootHasProgressBar) {
                pd.setMax(100);
                pd.setProgress(0);
                pd.setIndeterminate(false);
                pd.setProgressNumberFormat(null);
                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_prepare));
            } else {
                pd.setIndeterminate(true);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_reboot));
            }
        } else if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
            // Factory reset path. Set the dialog message accordingly.
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
            pd.setMessage(context.getText(
                        com.android.internal.R.string.reboot_to_reset_message));
            pd.setIndeterminate(true);
        } else {
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        pd.show();
        sInstance.mProgressDialog = pd;
        sInstance.mContext = context;
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        // make sure we never fall asleep again
        sInstance.mCpuWakeLock = null;
        try {
            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
            sInstance.mCpuWakeLock.setReferenceCounted(false);
            sInstance.mCpuWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mCpuWakeLock = null;
        }
        // also make sure the screen stays on for better user experience
        sInstance.mScreenWakeLock = null;
        if (sInstance.mPowerManager.isScreenOn()) {
            try {
                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
                sInstance.mScreenWakeLock.setReferenceCounted(false);
                sInstance.mScreenWakeLock.acquire();
            } catch (SecurityException e) {
                Log.w(TAG, "No permission to acquire wake lock", e);
                sInstance.mScreenWakeLock = null;
            }
        }
        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

    这里注释说明了关机序列函数里面各个通路是在什么情况下面进行的,我们关心的是Path 3: Regular reboot / shutdown,也就是下面这一部分,这里是设置关机对话框的标题和信息等。

else {
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
        }

    这里注释掉else内容并添加如下属性设置,这里就完成了服务入口的添加,这里如同开机动画一样,添加了一个service.shutdownanim.exit系统属性,来标示这里是开始画关机动画,实际上这一句比较多余,纯属对齐格式。系统在检测到该属性为1的时候就会退出动画。

SystemProperties.set("service.shutdownanim.exit", "0");
SystemProperties.set("ctl.start", "shutdownanim");

    添加完入口就要去frameworks/base / cmds/bootanimation/bootanim.rc中添加服务,如下,新建shutdownanim服务,代码路径同bootanimation一样,并带有-shutdown参数,方便进行路径选择。

service bootanim /system/bin/bootanimation
    class core
    user graphics
    group graphics audio
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

service shutdownanim /system/bin/bootanimation -shutdown
    class core
    user graphics
    group graphics
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

    接着到主程序frameworks/base / cmds/bootanimation/bootanimation_main.cpp中改入口逻辑

int main(int argc, char** argv)//带参数主程序
{
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

    char value[PROPERTY_VALUE_MAX];
    property_get("debug.sf.nobootanimation", value, "0");
    int noBootAnimation = atoi(value);
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

        // create the boot animation object
        sp<BootAnimation> boot = new BootAnimation();

        //According to the parameters to decide isshutdown or not
        if (argc > 1) {//判断是否有参数
            if (strcmp(argv[1], "-shutdown")==0) {//读取参数是否为-shutdown
                boot->isShutdown(true);//设置关机标示
            }

        }
        IPCThreadState::self()->joinThreadPool();

    }
    return 0;
}

    添加了isShutdown方法后,需要去frameworks/base / cmds/bootanimation/BootAnimation.h声明该方法。

public:
                BootAnimation();
    virtual     ~BootAnimation();

    sp<SurfaceComposerClient> session() const;
    void isShutdown(bool shutdown);//申明isShutdown方法
...

    在尾部添加mShutdown成员变量

bool        mShutdown;

    现在需要去frameworks/base / cmds/bootanimation/BootAnimation.cpp进行关机动画的相关定制,首先添加宏定义,这里是储存关机动画shutdownanimation.zip的位置,这里当然需要提前准备shutdownanimation.zip,这里准备zip包有一些要求,不是随便打包的zip都可以,Windows下面需要选择储存模式,linux系统下需要命令进行特殊压缩。同样,内部文件和desc.txt的编写规则,网上也有很多。

static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"

    在onFirstRef初始化关机标示

void BootAnimation::onFirstRef() {
    status_t err = mSession->linkToComposerDeath(this);
    mShutdown = false;//初始化mShutdown关机标示为false
    ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
    if (err == NO_ERROR) {
        run("BootAnimation", PRIORITY_DISPLAY);
    }
}
void BootAnimation::isShutdown(bool shutdown)
{
    mShutdown = shutdown;//isShutdown方法实现
}
if(!mShutdown){
       if (encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,     
   R_OK) == 0)) {
           mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE;
       }
       else if (access(OEM_BOOTANIMATION_FILE, R_OK) == 0) {
           mZipFileName = OEM_BOOTANIMATION_FILE;
       }
       else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) {
        mZipFileName = SYSTEM_BOOTANIMATION_FILE;
       }
    }else{
        if(access(getAnimationFileName(IMG_STD), R_OK) == 0){
            mZipFileName = SYSTEM_SHUTDOWNANIMATION_FILE;
        }
        mShutdown = false;
    }
    return NO_ERROR;

    关机更多的操作都可以通过mShutdown标示符进行区分,一上便是第一种方法,通过情况下这种方式是可用的,但实际项目中有时候是无法打到目的的。
    这里就介绍第二种方法,这种方法不需要新建shutdown services,即不需要修改bootanim.rc,直接使用bootanima的服务,通过对服务传参的方式进行处理。在关机序列处,添加如下代码,这里的参数可以传入多个,空格隔开,在主程序里面通过command的指针逐个读取,这里可以通过传入多个参数实现,是否需要播放关机音乐的参数。例如"shutdownanim:-shutdown shutmusic",其他修改类似。但是这种方法出现过参数怎么也传不到bootanima服务这边,bootanimation启动不了的问题,没有任何相关log。

SystemProperties.set("service.shutdownanim.exit", "0");
SystemProperties.set("ctl.start", "shutdownanim:-shutdown");

    从而引入到第三种方法,第三种方法同样不需要新建关机服务,只需要在启动开机动画的时候读取设置的系统属性标识符,来确定改启动关机流程还是开机流程。在关机序列处,添加一个service.bootanim.mode的属性。

SystemProperties.set("service.bootanim.exit", "0");
SystemProperties.set("service.bootanim.mode", "1");
String mode = SystemProperties.get("service.bootanim.mode");
Log.d(TAG, "Set bootanim mode " + mode + ", go to start shutdownanim now");
SystemProperties.set("ctl.start", "bootanim");

    在主程序处添加属性查询,其他部分一致。

int main()
{
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

    char value[PROPERTY_VALUE_MAX];
    property_get("debug.sf.nobootanimation", value, "0");
    int noBootAnimation = atoi(value);
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

        // create the boot animation object
        sp<BootAnimation> boot = new BootAnimation();

        //Set shutdown process according to parameters
        char mode[1];
        property_get("service.bootanim.mode", mode, "0");//获取属性
        if((int)(mode[0]-'0')==1){//查看是否是关机流程
            boot->isShutdown(true);
        }

        IPCThreadState::self()->joinThreadPool();

    }
    return 0;
}

方法四:

通过对关机进行监听实现,最新在项目中是通过这种方式实现的。