天天看点

Android开机动画流程—执行阶段

(1)动画文件的存在位置

static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip";
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";
           
static const std::vector<std::string> bootFiles = {
        APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,
       OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE
 };
static const std::vector<std::string> shutdownFiles = {
	   PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""
};
static const std::vector<std::string> userspaceRebootFiles = {
        PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE, OEM_USERSPACE_REBOOT_ANIMATION_FILE,
        SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE,
};
           

动画文件的读取是按顺序进行的,如果读取成功,则不再读取后续的文件,如果失败,则读取下一个文件。

加载动画包和配置文件(desc.txt)就不详细分析了。

加载动画执行完成之后,用来显示开机画面的线程的初始化工作就执行完成了,接下来就会执行这个线程的主体函数,即BootAnimation类的成员函数threadLoop。

(2)执行动画ThreadLoop

//frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::threadLoop()
{
    bool result;
    // We have no bootanimation file, so we use the stock android logo
    // animation.
    if (mZipFileName.isEmpty()) {
        result = android();
    } else {
        result = movie();
    }

    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
    eglDestroySurface(mDisplay, mSurface);
    mFlingerSurface.clear();
    mFlingerSurfaceControl.clear();
    eglTerminate(mDisplay);
    IPCThreadState::self()->stopProcess();
    return result;
}
           

首先判断自定义的开机动画文件mZipFileName是否存在,如果存在就调用movie()完成自定义开机画面的显示;如果不存在,调用android()完成系统默认开机画面的显示。

movie()和android()的返回值都是false,因此线程结束也会返回false。threadLoop()函数如果返回值为false,则该函数中的内容只会执行一次;如果返回true,则会不停的执行。这里返回false,因此只会执行一次。

接下来简单看一下android()和movie()函数。

bool BootAnimation::android() {
      SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
              elapsedRealtime());
              
    // 这两张图片保存在frameworks/base/core/res/assets/images目录中,它们最终会被编译
    // 在framework-res模块(frameworks/base/core/res)中,即编译在framework-res.apk文件中。
      initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
      initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

do {
		 //...	
          checkExit();
      } while (!exitPending());

	  return false;
}
           
bool BootAnimation::movie() {
      if (mAnimation == nullptr) {
          mAnimation = loadAnimation(mZipFileName);
      }
  
      if (mAnimation == nullptr)
          return false;
	 	}

		playAnimation(*mAnimation);

		releaseAnimation(mAnimation);
        mAnimation = nullptr;

        return false;
}


bool BootAnimation::playAnimation(const Animation& animation) {

		if (part.animation != nullptr) {
            playAnimation(*part.animation);
            if (exitPending())
                break;
            continue; //to next part
        }

		if(exitPending() && !part.playUntilComplete)
               break;

		//...
		checkExit();
		if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
                break;
}
           

在循环语句最后会执行checkExit()函数:

static const char EXIT_PROP_NAME[] = "service.bootanim.exit";

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 (exitnow) {
          requestExit();
          mCallbacks->shutdown();
      }
}
           

首先调用property_get获取属性EXIT_PROP_NAME的值,如果为1,则调用requestExit()要求退出当前线程,该函数是异步的。

//system/core/libutils/Thread.cpp

void Thread::requestExit()
{
    Mutex::Autolock _l(mLock);
    //这里将mExitPending 赋值为true
    mExitPending = true;
}

bool Thread::exitPending() const
{
    Mutex::Autolock _l(mLock);
    return mExitPending;
}
           

动画执行时通过调用exitPending()检测,该函数判断requestExit()是否被调用过,如果调用过则返回true,否则为false。

当属性"service.bootanim.exit"值被设为"1"时,android()就会调用requestExit(),exitPending()返回值为true。于是循环就会退出,开机动画就会结束。

至于什么时候是哪个服务将属性"service.bootanim.exit"的值设置为1的,我们后面讨论开机动画的停止的时候会提到。

(3)开机动画包bootanimation.zip

每个压缩文件都必须包含有一个名称为"desc.txt"的文件,这是用来描述用户自定义的开机动画是如何显示的。

480 640 20  
p 1 0 folder1  
p 2 20 folder2  
c 0 0 folder3  
c 1 0 folder4
           

第1行用来描述开机动画在屏幕显示的大小及帧率。具体为:开机动画的宽度为480个像素,高度为640个像素,显示频率为每秒20帧,即每帧显示1/20秒。

下面的每一行代表一个片段,显示的时候会按照顺序从上到下依次显示。

  • 第1个字符为片段类型,有’c’和’p’两种;
  • 第2个数字为该片段重复显示的次数,如果为’0’,表示会无限重复显示;
  • 第3个数字为两次显示之间的间隔,单位为第一行中定义的每帧显示的时间;
  • 第4个字符串为该片段所在的文件夹,一个片段可以由多个png图片组成,都存放在folder文件夹中;

解释一下:

  • “p 1 0 folder1”——代表该片段显示1次,与下一个片段间隔0s,该片段的显示图片路径为bootanimation.zip/folder1;
  • “p 2 20 folder2”——代表该片段显示2次,且两次之间显示的间隔为20(1/20)=1s,与下一个片段间隔20(1/20)=1s,该片段的显示图片路径为bootanimation.zip/folder2;
  • “c 0 0 folder3”——代表该片段无限循环显示,且两次显示的间隔为0s,与下一个片段间隔0s,该片段的显示图路径为bootanimation.zip/folder3;
  • “c 1 10 folder4”——代表该片段显示1次,显示后暂停10*(1/20)=0.5s,该片段的显示图路径为bootanimation.zip/folder4;

总结:

如果exitPending()返回值为true且part.playUntilComplete=false,则会break。即:当SurfaceFlinger服务要求bootanimation停止显示动画时,以’p’标识的片段会停止,而以’c’标识的片段会继续显示。这就是两者之间的主要区别。

'c’标识的意思应该是continue,即:即使SurfaceFlinger要求bootanimation停止动画,bootanimation也不会立刻停止动画,它会等c标识片段都显示完毕后,再停止。

继续阅读