Android系统在启动过程中,最多可以出现三个画面。第一个开机画面是在内核启动的过程中出现,是一个静态的画面;第二个画面是在init进程启动的过程中出现的,也是一个静态的画面;第三个画面是在系统服务启动的过程中出现 ,是一个动态的画面。这三个画面都是在一个被称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。本文主要分析第三个启动画面的流程。
在Android层动画的流程图:

先由init启动SurfaceFlinger进程,对图形系统进行初始化,再由SurfaceFlinger进程启动BootAnimation 进程显示动画。动画由一个 while 循环播放,在播放循环里调用 checkExit ()检查一个系统标志,判断是否应该结束动画。由此可见,动画的显示流程较为简单,但动画的结束流程相对复杂,其中经历了Launcher、ActivityManagerService、SurfaceFlinger……等层层调用传递消息,最终在SurfaceFlinger.bootFinished()设置动画的结束标志。下面来具体分析整个从显示到结束的流程。
首先从init进程开始,init进程是linux内核启动后启动的第一个进程,init 会解析init.rc 文件,启动其中声明的服务。关于动画部分,在init.rc文件中有这样的配置:

service surfaceflinger /system/bin/surfaceflingerclass mainuser systemgroup graphics drmrpconrestart restart zygote

init进程会启动surfaceflinger服务,入口源文件为/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp:

/** Copyright (C) 2010 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#include <sys/resource.h>#include <cutils/sched_policy.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include "GpuService.h"
#include "SurfaceFlinger.h"using namespace android;int main(int, char**) {signal(SIGPIPE, SIG_IGN);// When SF is launched in its own process, limit the number of// binder threads to 4.ProcessState::self()->setThreadPoolMaxThreadCount(4);// start the thread poolsp<ProcessState> ps(ProcessState::self());ps->startThreadPool();// instantiate surfaceflinger//创建了一个SurfaceFlinger对象flingersp<SurfaceFlinger> flinger = new SurfaceFlinger();setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);set_sched_policy(0, SP_FOREGROUND);#ifdef ENABLE_CPUSETS// Put most SurfaceFlinger threads in the system-background cpuset// Keeps us from unnecessarily using big cores// Do this after the binder thread pool initset_cpuset_policy(0, SP_SYSTEM);
#endif// initialize before clients can connect //初始化flinger->init();// publish surface flinger //加入ServiceManager中进行管理,最后启动服务sp<IServiceManager> sm(defaultServiceManager());sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);// publish GpuServicesp<GpuService> gpuservice = new GpuService();sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);// run surface flinger in this threadflinger->run();return 0;
}

在上面的源文件中创建了一个SurfaceFlinger对象flinger,然后调用flinger->init()进行初始化,再将其加入ServiceManager中进行管理,最后启动服务。
我们来看一下frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp中的init()初始化函数:

void SurfaceFlinger::init() {ALOGI(  "SurfaceFlinger's main thread ready to run. ""Initializing graphics H/W...");{ // Autolock scopeMutex::Autolock _l(mStateLock);// initialize EGL for the default displaymEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);eglInitialize(mEGLDisplay, NULL, NULL);// start the EventThreadsp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,vsyncPhaseOffsetNs, true, "app");mEventThread = new EventThread(vsyncSrc, *this);sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,sfVsyncPhaseOffsetNs, true, "sf");mSFEventThread = new EventThread(sfVsyncSrc, *this);mEventQueue.setEventThread(mSFEventThread);// Get a RenderEngine for the given display / config (can't fail)mRenderEngine = RenderEngine::create(mEGLDisplay,HAL_PIXEL_FORMAT_RGBA_8888);}// Drop the state lock while we initialize the hardware composer. We drop// the lock because on creation, it will call back into SurfaceFlinger to// initialize the primary display.mHwc = new HWComposer(this);mHwc->setEventHandler(static_cast<HWComposer::EventHandler*>(this));Mutex::Autolock _l(mStateLock);// retrieve the EGL context that was selected/createdmEGLContext = mRenderEngine->getEGLContext();LOG_ALWAYS_FATAL_IF(mEGLContext == EGL_NO_CONTEXT,"couldn't create EGLContext");// make the GLContext current so that we can create textures when creating// Layers (which may happens before we render something)getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);mEventControlThread = new EventControlThread(this);mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);// initialize our drawing statemDrawingState = mCurrentState;// set initial conditions (e.g. unblank default device)initializeDisplays();// start boot animation//调用startBootAnim()开始动画显示startBootAnim();ALOGV("Done initializing");
}

init()函数主要是对屏幕显示窗口进行初始化工作。在最后调用了startBootAnim(),我们来看一下SurfaceFlinger.cpp中的startBootAnim()函数:

void SurfaceFlinger::startBootAnim() {// start boot animationproperty_set("service.bootanim.exit", "0");property_set("ctl.start", "bootanim");
}

其中先将”service.bootanim.exit”标志为 0,这个标志用于在bootanim进程中检测开机动画是否结束,然后通过发”ctl.start”请求给init进程,启动”bootanim”服务。
这里需要注意的是,开机动画进程在init.rc中注册过了,但是在解析init.rc文件时并没有直接启动,而是由SurfaceFlinger服务来启动。这是因为init.rc 虽然注册了bootanimation服务,但是被disabled了。所以在解析init.rc文件时,bootanimation并没有被直接启动。

service bootanim /system/bin/bootanimationclass mainuser rootgroup graphicsdisabledoneshot

由SurfaceFlinger启动的原因是开机动画必须得有显示窗口,而这个显示窗口则是由SurfaceFlinger提供的,所以必须得在SurfaceFlinger初始化完成之后,才开始显示开机动画。
下面转到bootanimation,入口文件为 frameworks/base/cmds/bootanimation/bootanimation_main.cpp :

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 objectsp<BootAnimation> boot = new BootAnimation();IPCThreadState::self()->joinThreadPool();}return 0;
}

启动了一个线程池。BootAnimation对象在显示第三个开机画面的过程中,需要与SurfaceFlinger服务通信,因此bootanimation就需要启动一个Binder线程池。
这里的BootAnimation类(frameworks\base\cmds\bootanimation\BootAnimation.cpp)间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef():

void BootAnimation::onFirstRef() {status_t err = mSession->linkToComposerDeath(this);ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));if (err == NO_ERROR) {run("BootAnimation", PRIORITY_DISPLAY);}
}

因此,当一个BootAnimation对象boot第一次被智能指针引用的时,对象的成员函数onFirstRef()会被调用。
此外BootAnimation类继承了Thread类(\system\core\libutils\Threads.cpp):

BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) {mSession = new SurfaceComposerClient();
}

Thread 类有个run() 函数,在run()函数中会调用_threadLoop()函数,而_threadLoop函数又调用了readyToRun和threadLoop两个函数:

do {bool result;if (first) {first = false;self->mStatus = self->readyToRun();result = (self->mStatus == NO_ERROR);if (result && !self->exitPending()) {// Binder threads (and maybe others) rely on threadLoop// running at least once after a successful ::readyToRun()// (unless, of course, the thread has already been asked to exit// at that point).// This is because threads are essentially used like this://   (new ThreadSubclass())->run();// The caller therefore does not retain a strong reference to// the thread and the thread would simply disappear after the// successful ::readyToRun() call instead of entering the// threadLoop at least once.result = self->threadLoop();}} else {result = self->threadLoop();}

BootAnimation类重写了readyToRun和threadLoop这两个函数:

status_t BootAnimation::readyToRun() {mAssets.addDefaultAssets();sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));DisplayInfo dinfo;status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);if (status)return -1;// create the native surface// 初始化opengl和绘图表面surfacesp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);SurfaceComposerClient::openGlobalTransaction();control->setLayer(0x40000000);SurfaceComposerClient::closeGlobalTransaction();sp<Surface> s = control->getSurface();// initialize opengl and eglconst EGLint attribs[] = {EGL_RED_SIZE,   8,EGL_GREEN_SIZE, 8,EGL_BLUE_SIZE,  8,EGL_DEPTH_SIZE, 0,EGL_NONE};EGLint w, h;EGLint numConfigs;EGLConfig config;EGLSurface surface;EGLContext context;EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);eglInitialize(display, 0, 0);eglChooseConfig(display, attribs, &config, 1, &numConfigs);surface = eglCreateWindowSurface(display, config, s.get(), NULL);context = eglCreateContext(display, config, NULL, NULL);eglQuerySurface(display, surface, EGL_WIDTH, &w);eglQuerySurface(display, surface, EGL_HEIGHT, &h);if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)return NO_INIT;mDisplay = display;mContext = context;mSurface = surface;mWidth = w;mHeight = h;mFlingerSurfaceControl = control;mFlingerSurface = s;// If the device has encryption turned on or is in process// of being encrypted we show the encrypted boot animation.char decrypt[PROPERTY_VALUE_MAX];property_get("vold.decrypt", decrypt, "");bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);// 判断是否有开机动画文件 bootanimation.zip 存在if (encryptedAnimation && (access(getAnimationFileName(IMG_ENC), R_OK) == 0)) {mZipFileName = getAnimationFileName(IMG_ENC);}else if (access(getAnimationFileName(IMG_OEM), R_OK) == 0) {mZipFileName = getAnimationFileName(IMG_OEM);}else if (access(getAnimationFileName(IMG_SYS), R_OK) == 0) {mZipFileName = getAnimationFileName(IMG_SYS);}return NO_ERROR;
}bool BootAnimation::threadLoop()
{bool r;// We have no bootanimation file, so we use the stock android logo// animation.if (mZipFileName.isEmpty()) {r = android();} else {r = 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 r;
}

readyToRun() 主要是对opengl工作环境进行初始化,判断用户自定义的开机动画文件是否存在,保存结果到mZipFileName 成员变量中。threadLoop 就开始真正的播放动画了,当mZipFileName是空时,则播放Android系统默认的开机动画,否则播放用户自定义的开机动画 。自定义的开机动画是由文件USER_BOOTANIMATION_FILE或者文件SYSTEM_BOOTANIMATION_FILE来描述的。只要其中的一个文件存在,那么开机画面就会使用用户自定义的开机动画。
android() 播放的是系统原生动画,“android”字样加上不断移动的光影效果。movie() 则是读取bootanimation.zip 中的帧动画,一张一张的轮播,形成动画效果。下面来分析下这Android系统动画函数android(),它还是存在于BootAnimation.cpp文件中:

//播放系统原生动画
bool BootAnimation::android()
{initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");// clear screenglShadeModel(GL_FLAT);glDisable(GL_DITHER);glDisable(GL_SCISSOR_TEST);glClearColor(0,0,0,1);glClear(GL_COLOR_BUFFER_BIT);eglSwapBuffers(mDisplay, mSurface);glEnable(GL_TEXTURE_2D);glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);const GLint xc = (mWidth  - mAndroid[0].w) / 2;const GLint yc = (mHeight - mAndroid[0].h) / 2;const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),updateRect.height());// Blend stateglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);const nsecs_t startTime = systemTime();do {nsecs_t now = systemTime();double time = now - startTime;float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;GLint x = xc - offset;glDisable(GL_SCISSOR_TEST);glClear(GL_COLOR_BUFFER_BIT);glEnable(GL_SCISSOR_TEST);glDisable(GL_BLEND);glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);glEnable(GL_BLEND);glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);if (res == EGL_FALSE)break;// 12fps: don't animate too fast to preserve CPUconst nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);if (sleepTime > 0)usleep(sleepTime);checkExit();} while (!exitPending());glDeleteTextures(1, &mAndroid[0].name);glDeleteTextures(1, &mAndroid[1].name);return false;
}

使用一个while循环去绘图,并在循环中调用 checkExit()判断是否应该结束动画。
checkExit()也存在于BootAnimation.cpp文件中:

void BootAnimation::checkExit() {// Allow surface flinger to gracefully request shutdownchar value[PROPERTY_VALUE_MAX];property_get(EXIT_PROP_NAME, value, "0");int exitnow = atoi(value);if (exitnow) {requestExit();if (mAudioPlayer != NULL) {mAudioPlayer->requestExit();}}
}

检测到 “service.bootanim.exit” 的值被修改成非 0 之后,就调用 requestExit() 结束动画。
requestExit() 函数是父类 Thread 中定义的,在Threads.cpp文件中:

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

设置了一个标志位 mExitPending = true,表明准备退出线程。while (!exitPending()) 循环条件就变为 false了,退出动画线程。movie() 的流程也是类似。
下面我们来看看”service.bootanim.exit”。经过分析可知在\frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp文件中将这个标志设置为了 “1”:

void SurfaceFlinger::bootFinished()
{const nsecs_t now = systemTime();const nsecs_t duration = now - mBootTime;ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );mBootFinished = true;// wait patiently for the window manager deathconst String16 name("window");sp<IBinder> window(defaultServiceManager()->getService(name));if (window != 0) {window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));}// stop boot animation// formerly we would just kill the process, but we now ask it to exit so it// can choose where to stop the animation.//将service.bootanim.exit的值设置为1property_set("service.bootanim.exit", "1");const int LOGTAG_SF_STOP_BOOTANIM = 60110;LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}

在frameworks\native\libs\gui\ISurfaceComposer.cpp中调用了bootFinished():

status_t BnSurfaceComposer::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{switch(code) {……case BOOT_FINISHED: {CHECK_INTERFACE(ISurfaceComposer, data, reply);bootFinished();return NO_ERROR;}……}
}

现在来探究是谁发送了BOOT_FINISHED 消息给SurfaceFlinger 服务,查看代码发现,在\frameworks\native\include\gui\ISurfaceComposer.h中有定义BOOT_FINISHED:

class BnSurfaceComposer: public BnInterface<ISurfaceComposer> {
……
public:enum {// Note: BOOT_FINISHED must remain this value, it is called from// Java by ActivityManagerService.BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,
……
}
……
}

发现在\services\core\java\com\android\server\wm\WindowManagerService.java中有发送 IBinder::FIRST_CALL_TRANSACTION 消息,并注释说是 BOOT_FINISHED :

public void performEnableScreen() {
……
try {IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");if (surfaceFlinger != null) {//Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");Parcel data = Parcel.obtain();data.writeInterfaceToken("android.ui.ISurfaceComposer");surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHEDdata, null, 0);data.recycle();}……
}

获取 SurfaceFlinger 服务,发送了 IBinder.FIRST_CALL_TRANSACTION 消息。
在WindowManagerService.java中的enableScreenAfterBoot()是开机流程中的服务:

    public void enableScreenAfterBoot() {synchronized(mWindowMap) {if (DEBUG_BOOT) {RuntimeException here = new RuntimeException("here");here.fillInStackTrace();Slog.i(TAG_WM, "enableScreenAfterBoot: mDisplayEnabled=" + mDisplayEnabled+ " mForceDisplayEnabled=" + mForceDisplayEnabled+ " mShowingBootMessages=" + mShowingBootMessages+ " mSystemBooted=" + mSystemBooted, here);}if (mSystemBooted) {return;}mSystemBooted = true;hideBootMessagesLocked();// If the screen still doesn't come up after 30 seconds, give// up and turn it on.mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30*1000);}mPolicy.systemBooted();performEnableScreen();}

在frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java又调用了 ActivityManagerService.enableScreenAfterBoot() :

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,Configuration config) {...if (enableScreen) {mService.enableScreenAfterBoot();}   ...
}

在WindowManagerService.java中调用了ActivityStackSupervisor.activityIdleInternalLocked():

@Override
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {final long origId = Binder.clearCallingIdentity();synchronized (this) {ActivityStack stack = ActivityRecord.getStackLocked(token);if (stack != null) {ActivityRecord r = mStackSupervisor.activityIdleInternalLocked(token, false, config);if (stopProfiling) {if ((mProfileProc == r.app) && (mProfileFd != null)) {try {mProfileFd.close();} catch (IOException e) {}   clearProfilerLocked();}   }   }   }   Binder.restoreCallingIdentity(origId);
}

而在ActivityThread 中又调用了ActivityManagerService.activityIdle():

private class Idler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {...if (a.activity != null && !a.activity.mFinished) {try {am.activityIdle(a.token, a.createdConfig, stopProfiling);a.createdConfig = null;} catch (RemoteException ex) {// Ignore}}...
}

ActivityThread 类就是android应用程序的主线程。这个 Idler 类是在 handleResumeActivity 被注册到主线程 ActivityThread 中的:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,boolean reallyResume) {
...if (!r.onlyLocalRequest) {r.nextIdle = mNewActivities;mNewActivities = r;if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler());}   r.onlyLocalRequest = false;// Tell the activity manager we have resumed.if (reallyResume) {try {ActivityManagerNative.getDefault().activityResumed(token);} catch (RemoteException ex) {}   }
...
}

也就是说一个Activity 在Resume的时候会注册一个空闲处理函数到主线程,主线程空闲的时候就会调用这个函数。
那么开机动画结束和Activity的启动有什么关系呢?我们可以理解为Android系统启动完成后,就会启动Launcher中的主Activity,一旦Launcher的Activity启动完成之后,如果没有用户操作,就会进入空闲状态,ActivityThread就会调用注册的IdleHandler。然后层层转发调用,最终调用SurfaceFlinger去终止开机动画。
上面的分析是从下往上分析的,下面再从顶向下理一遍:

  1. Launcher启动,注册一个Idler空闲处理器到ActivityThread中;
  2. Launcher主线程空闲的时候就会调用Idler。queueIdle()方法;
  3. Idler.queueIdle() 通过Binder通信调用了ActivityManagerService.activityIdle()方法
  4. 调用ActivityStackSupervisor.activityIdleInternalLocked()方法;
  5. 调用ActivityManagerService.enableScreenAfterBoot()方法;
  6. 调用WindowManagerService.enableScreenAfterBoot()方法;
  7. 调用WindowManagerService.performEnableScreen()方法;
  8. performEnableScreen 通过Binder通信发送 BOOT_FINISHED 消息给 ISurfaceComposer
  9. 调用ISurfaceComposer 收到 BOOT_FINISHED 消息后调用SurfaceFlinger::bootFinished 函数设置动画结束标志。

我们可以在各个方法调用点加Log来进行调试。

Android开机动画流程相关推荐

  1. Android 开机动画流程介绍

    前言 简单介绍了安卓开机动画流程 流程 [开机动画启动流程]: // Bootanim.rc (frameworks\base\cmds\bootanimation)service bootanim ...

  2. Android开机动画流程(一)——启动阶段

    (1)前述 Android系统在启动的过程中,最多可以出现四个画面,每一个画面都用来描述一个不同的启动阶段. Linux uboot显示(静态) Linux 内核的启动画面(静态) init 进程的启 ...

  3. Android 开机动画启动、播放、退出流程(android 10)

    Android 开机动画启动流程 (android 10) 1 开机动画启动流程 我们先来看一下开机动画是如何启动,并开始播放的. 通过系统启动流程分析可以得知,在系统内核启动后,会启动第一个init ...

  4. Android 开机动画(bootanimation)启动

    Android 开机动画(bootanimation)启动 Android 开机动画启动 前言 一.简单的对话 二.过程 总结 前言 开机动画应该算是我接触的第一个AOSP的Native程序,网上讲解 ...

  5. Android:Android开机动画

    Android系统的开机画面显示过程分析 三个开机画面修改方式:目录 DIY固件系列教程--实现开机LOGO三屏动画的完全替换 一.第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面 二.第 ...

  6. android 开机动画动态替换

    客户有需求apk 可以动态修改开机动画,按照android 系统原生逻辑是没有办法做到的 代码位置frameworks/base/cmds/bootanimation static const cha ...

  7. android logo:内核、android开机动画

    android logo:内核.android开机动画 关键词:android 开机logo  开机动画 initlogo.rle   bootanimation  desc.txt 平台信息: 内核 ...

  8. Android开机启动流程

    Android开机启动流程 一.APPS PBL(Application primary boot loader:主引导加载程序) 二.XBL(Extensible boot loader:可扩展引导 ...

  9. Android开机动画bootanimation

    android开机动画详细分析可以参见http://blog.csdn.net/luoshengyang/article/details/7691321 引用老罗的文章,写的太好了. 以下介绍一些相关 ...

最新文章

  1. Get/POST方法提交的长度限制
  2. java 顺序栈_java用顺序栈实现数制转换 求完整程序!!
  3. 企业项目学习准备阶段——Rhel6.5版本无图形虚拟机封装过程及相关配置
  4. c语言读取exe的pe标记,PE文件信息读取程序(1.关键函数部分)
  5. Codeforces#363 Div2
  6. Linux设置默认Python版本
  7. python之路day10-命名空间和作用域、函数嵌套,作用域链、闭包
  8. python网络模块_Python的pyroute2网络模块-阿里云开发者社区
  9. [ExtJS6]ResponsiveColumn-自适应列布局
  10. Codeup_575I_剪刀石头布
  11. Atitit refact art 重构的艺术 目录 1. Concept 1 1.1. Bp 1 2. Prob 2 3. Tool 2 1.Concept 1. legacy code遗留代
  12. 新路嘉机器人_嘉懿学子在2019年上海市中小学机器人竞赛中喜获佳绩
  13. myeclipse+JDK10+tomcat9 配置
  14. 用计算机如何判断闰年,php判断/计算闰年的方法小结【三种方法】
  15. python 12306查询不到车次_(经典!!!详细解析!!!)python实现12306余票查询
  16. 重置CAD 或者Civil 3d的所有环境
  17. 数据结构(一)--ArrayList and LinkerList
  18. 从图片到涂鸦:高品质涂鸦的自动生成
  19. Word2013插入分隔符(分节符)实现任意页插入页码
  20. Redis安装(Windows 最新版本redis 5.0.9,以及redis 4.0)

热门文章

  1. H5唤起android app,启动关联应用
  2. c++win32项目 如何显示后再删除一个绘图_CAD 100个快捷键+20个实用绘图技巧
  3. 前端小白入职前的准备
  4. Python 求解 XTXh=XTY ¶
  5. HX711称重传感器的功能实现
  6. MySQL黑马笔记三
  7. win10,vs2015深度学习目标检测YOLOV5+deepsort C++多目标跟踪代码实现,源码注释,拿来即用。
  8. Neodynamic ZPLPrinter Web API for Docker
  9. laravel模型关联方法总结
  10. java 解析修改dex_Android dex文件解析