android开机动画启动流程

android的Surface Flinger服务启动分析知道,开机动画是在SurfaceFlinger实例通过调用startBootAnim()启动的。

下面我们就一起学习BootAnim是如何启动和结束的,我精读代码前都喜欢先描出框架图,以此图为基础再去研读会达到事半功倍的效果。好吧,直接上图。


技术分享



内核起来后会启动第一个进程,即init进程。

init进程会根据init.rc配置启动surfaceflinger进程。


[cpp]  view plain  copy
  1. service surfaceflinger /system/bin/surfaceflinger  
  2.     class main  
  3.     user system  
  4.     group graphics drmrpc  
  5.     onrestart restart zygote  

surfaceflinger进程便启动了,跟着就会跑进程的main()函数。


frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

扫描二维码关注公众号,回复: 1705581 查看本文章
[cpp]  view plain  copy
  1. int main(int argc, char** argv) {  
  2. ....  
  3.   
  4.     // instantiate surfaceflinger  
  5.     sp<SurfaceFlinger> flinger = new SurfaceFlinger();//创建surfaceflinger服务实例  
  6.   
  7. ....  
  8.     flinger->init();  
  9.   
  10.     // publish surface flinger  
  11.     sp<IServiceManager> sm(defaultServiceManager());  
  12.     sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);//注册到service manager里  
  13.   
  14.     // run in this thread  
  15.     flinger->run();//开跑  
  16.   
  17.     return 0;  
  18. }  

首先new一个SurfaceFlinger实例,然后init,然后run


frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

[cpp]  view plain  copy
  1. void SurfaceFlinger::init() {  
  2.     ALOGI(  "SurfaceFlinger's main thread ready to run. "  
  3.             "Initializing graphics H/W...");  
  4.   
  5.  .....  
  6.     // start boot animation  
  7.     startBootAnim();//开始播放动画  
  8. }  

初始化graphics之后,就调用startBootAnim()播放开机动画。


[cpp]  view plain  copy
  1. void SurfaceFlinger::startBootAnim() {  
  2.     // start boot animation  
  3.     mBootFinished = false;  
  4.     property_set("service.bootanim.exit""0");//这个会有bootanimation进程周期检测,=1退出动画  
  5.     property_set("ctl.start""bootanim");//通过ctl.start命令启动bootanim  
  6. }  

把service.bootanim.exit属性设为0,这个属性bootanimation进程里会周期检查,=1时就退出动画,这里=0表示要播放动画。

后面通过ctl.start的命令启动bootanim进程,动画就开始播放了。


下面来到bootanimation的实现


frameworks/base/cmds/bootanimation/bootanimation_main.cpp

[cpp]  view plain  copy
  1. int main(int argc, char** argv)  
  2. {  
  3.   
  4.   
  5.         sp<ProcessState> proc(ProcessState::self());  
  6.         ProcessState::self()->startThreadPool();  
  7.   
  8.         // create the boot animation object  
  9.         sp<BootAnimation> boot = new BootAnimation();//创建BootAnimation实例  
  10.   
  11.         IPCThreadState::self()->joinThreadPool();//binder线程池,与surfaceflinger通信用的。  
  12.   
  13.     }  
  14.     return 0;  
  15. }  

new一个BootAnimation实例,然后建个binder线程池,因为BootAnimation在显示动画时要与SurfaceFlinger服务进程通信,所以要启个binder线程池。


frameworks/base/cmds/bootanimation/BootAnimation.cpp

[cpp]  view plain  copy
  1. BootAnimation::BootAnimation() : Thread(false)  
  2. {  
  3.     mSession = new SurfaceComposerClient();//创建一个对象  
  4. }  

创建实例时,构造函数就会被调用,new一个SurfaceComposerClient实例,他是用来与surfaceflinger通信的


[cpp]  view plain  copy
  1. void BootAnimation::onFirstRef() {  
  2.     status_t err = mSession->linkToComposerDeath(this);//注册surfaceflinger死亡消息的通知书  
  3.     ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));  
  4.     if (err == NO_ERROR) {  
  5.         run("BootAnimation", PRIORITY_DISPLAY);//开跑  
  6.     }  
  7. }  


linkTocomposerDeath的作用是当surfaceflinger死掉是,BootAnimation就会得到通知。


如下,收到通知后就退出动画了,因为surfaceflinger都挂掉了,播放不了了。

[cpp]  view plain  copy
  1. void BootAnimation::binderDied(const wp<IBinder>& who)  
  2. {  
  3.     // woah, surfaceflinger died!  
  4.     ALOGD("SurfaceFlinger died, exiting...");  
  5.   
  6.     // calling requestExit() is not enough here because the Surface code  
  7.     // might be blocked on a condition variable that will never be updated.  
  8.     kill( getpid(), SIGKILL );//收到surfaceflinger死亡的消息,好吧自己也跟着去了。  
  9.     requestExit();  
  10. }  

另一个函数run()在BootAnimation的父类Thead里,用来创建一个线程并跑起来。


父类

system/core/libutils/Threads.cpp

[cpp]  view plain  copy
  1. status_t Thread::run(const char* name, int32_t priority, size_t stack)  
  2. {  
  3.     ...  
  4.       
  5.     if (mCanCallJava) {  
  6.         res = createThreadEtc(_threadLoop,//创建线程  
  7.                 this, name, priority, stack, &mThread);  
  8.     } else {  
  9.         res = androidCreateRawThreadEtc(_threadLoop,  
  10.                 this, name, priority, stack, &mThread);  
  11.     }  
  12.     ....  
  13. }  

创建_threadLoop线程

[cpp]  view plain  copy
  1. int Thread::_threadLoop(void* user)  
  2. {  
  3. ....  
  4.     do {  
  5.         bool result;  
  6.         if (first) {  
  7.             first = false;  
  8.             self->mStatus = self->readyToRun();//这个函数被bootanimation重写了  
  9.             result = (self->mStatus == NO_ERROR);  
  10.   
  11.             if (result && !self->exitPending()) {  
  12.                 ...  
  13.                 result = self->threadLoop();//这个函数被bootanimation重写了  
  14.             }  
  15.         } else {  
  16.             result = self->threadLoop();  
  17.         }  
  18.   
  19.         ...  
  20.       
  21.     return 0;  
  22. }  

 readyToRun函数实现

[cpp]  view plain  copy
  1. status_t BootAnimation::readyToRun() {  
  2.     mAssets.addDefaultAssets();  
  3.   
  4.     sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(  
  5.             ISurfaceComposer::eDisplayIdMain));  
  6.     DisplayInfo dinfo;  
  7.     status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);  
  8.     if (status)  
  9.         return -1;  
  10.     char value[PROPERTY_VALUE_MAX];  
  11.     property_get("persist.panel.orientation", value, "0");  
  12.     int orient = atoi(value) / 90;  
  13.   
  14.     if(orient == eOrientation90 || orient == eOrientation270) {  
  15.         int temp = dinfo.h;  
  16.         dinfo.h = dinfo.w;  
  17.         dinfo.w = temp;  
  18.     }  
  19.   
  20.     Rect destRect(dinfo.w, dinfo.h);  
  21.     mSession->setDisplayProjection(dtoken, orient, destRect, destRect);  
  22.   
  23.     // create the native surface  
  24.     sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),  
  25.             dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);  
  26.   
  27.     SurfaceComposerClient::openGlobalTransaction();  
  28.     control->setLayer(0x40000000);  
  29.     SurfaceComposerClient::closeGlobalTransaction();  
  30.   
  31.     sp<Surface> s = control->getSurface();  
  32.   
  33.     // initialize opengl and egl  
  34.     const EGLint attribs[] = {  
  35.             EGL_RED_SIZE,   8,  
  36.             EGL_GREEN_SIZE, 8,  
  37.             EGL_BLUE_SIZE,  8,  
  38.             EGL_DEPTH_SIZE, 0,  
  39.             EGL_NONE  
  40.     };  
  41.     EGLint w, h, dummy;  
  42.     EGLint numConfigs;  
  43.     EGLConfig config;  
  44.     EGLSurface surface;  
  45.     EGLContext context;  
  46.   
  47.     EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);  
  48.   
  49.     eglInitialize(display, 0, 0);  
  50.     eglChooseConfig(display, attribs, &config, 1, &numConfigs);  
  51.     surface = eglCreateWindowSurface(display, config, s.get(), NULL);  
  52.     context = eglCreateContext(display, config, NULL, NULL);  
  53.     eglQuerySurface(display, surface, EGL_WIDTH, &w);  
  54.     eglQuerySurface(display, surface, EGL_HEIGHT, &h);  
  55.   
  56.     if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)  
  57.         return NO_INIT;  
  58.   
  59.     mDisplay = display;  
  60.     mContext = context;  
  61.     mSurface = surface;  
  62.     mWidth = w;  
  63.     mHeight = h;  
  64.     mFlingerSurfaceControl = control;  
  65.     mFlingerSurface = s;  
  66.   
  67.     mAndroidAnimation = true;  
  68.   
  69.     // If the device has encryption turned on or is in process   
  70.     // of being encrypted we show the encrypted boot animation.  
  71.     char decrypt[PROPERTY_VALUE_MAX];  
  72.     property_get("vold.decrypt", decrypt, "");  
  73.   
  74.     bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);  
  75.   
  76.     if ((encryptedAnimation &&  
  77.             (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&  
  78.             (mZip.open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE) == NO_ERROR)) ||  
  79.   
  80.             ((access(USER_BOOTANIMATION_FILE, R_OK) == 0) &&  
  81.             (mZip.open(USER_BOOTANIMATION_FILE) == NO_ERROR)) ||  
  82.   
  83.             ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&  
  84.             (mZip.open(SYSTEM_BOOTANIMATION_FILE) == NO_ERROR))) {  
  85.         mAndroidAnimation = false;  
  86.     }  
  87.   
  88.     return NO_ERROR;  
  89. }  

threadloop实现
[cpp]  view plain  copy
  1. bool BootAnimation::threadLoop()  
  2. {  
  3.     bool r;  
  4.     if (mAndroidAnimation) {  
  5.         r = android();//显示android默认动画  
  6.     } else {  
  7.         r = movie();//显示自定义的动画  
  8.     }  
  9.   
  10.     // No need to force exit anymore  
  11.     property_set(EXIT_PROP_NAME, "0");  
  12.   
  13.     eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);  
  14.     eglDestroyContext(mDisplay, mContext);  
  15.     eglDestroySurface(mDisplay, mSurface);  
  16.     mFlingerSurface.clear();  
  17.     mFlingerSurfaceControl.clear();  
  18.     eglTerminate(mDisplay);  
  19.     IPCThreadState::self()->stopProcess();  
  20.     return r;  
  21. }  

movie()的实现
[cpp]  view plain  copy
  1. bool BootAnimation::movie()  
  2. {  
  3.     //读取bootanimation.zip文件并解释  
  4.   
  5.       // clear screen  
  6.    //下面是循环显示   
  7.        for (int i=0 ; i<pcount ; i++) {  
  8.         const Animation::Part& part(animation.parts[i]);  
  9.         const size_t fcount = part.frames.size();  
  10.         glBindTexture(GL_TEXTURE_2D, 0);  
  11.   
  12.         for (int r=0 ; !part.count || r<part.count ; r++) {  
  13.             // Exit any non playuntil complete parts immediately  
  14.             if(exitPending() && !part.playUntilComplete)  
  15.                 break;  
  16.   
  17.             for (int j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {  
  18.                 const Animation::Frame& frame(part.frames[j]);  
  19.                 nsecs_t lastFrame = systemTime();  
  20.   
  21.                 if (r > 0) {  
  22.                     glBindTexture(GL_TEXTURE_2D, frame.tid);  
  23.                 } else {  
  24.                     if (part.count != 1) {  
  25.                         glGenTextures(1, &frame.tid);  
  26.                         glBindTexture(GL_TEXTURE_2D, frame.tid);  
  27.                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  28.                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  29.                     }  
  30.                     initTexture(  
  31.                             frame.map->getDataPtr(),  
  32.                             frame.map->getDataLength());  
  33.                 }  
  34.   
  35.                 if (!clearReg.isEmpty()) {  
  36.                     Region::const_iterator head(clearReg.begin());  
  37.                     Region::const_iterator tail(clearReg.end());  
  38.                     glEnable(GL_SCISSOR_TEST);  
  39.                     while (head != tail) {  
  40.                         const Rect& r(*head++);  
  41.                         glScissor(r.left, mHeight - r.bottom,  
  42.                                 r.width(), r.height());  
  43.                         glClear(GL_COLOR_BUFFER_BIT);  
  44.                     }  
  45.                     glDisable(GL_SCISSOR_TEST);  
  46.                 }  
  47.                 glDrawTexiOES(xc, yc, 0, animation.width, animation.height);  
  48.                 eglSwapBuffers(mDisplay, mSurface);  
  49.   
  50.                 nsecs_t now = systemTime();  
  51.                 nsecs_t delay = frameDuration - (now - lastFrame);  
  52.                 //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));  
  53.                 lastFrame = now;  
  54.   
  55.                 if (delay > 0) {  
  56.                     struct timespec spec;  
  57.                     spec.tv_sec  = (now + delay) / 1000000000;  
  58.                     spec.tv_nsec = (now + delay) % 1000000000;  
  59.                     int err;  
  60.                     do {  
  61.                         err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);  
  62.                     } while (err<0 && errno == EINTR);  
  63.                 }  
  64.   
  65.                 checkExit();//检测是否退出动画  
  66.             }  
  67.   
  68.             usleep(part.pause * ns2us(frameDuration));  
  69.   
  70.             // For infinite parts, we've now played them at least once, so perhaps exit  
  71.             if(exitPending() && !part.count)  
  72.                 break;  
  73.         }  
  74.   
  75.         // free the textures for this part  
  76.         if (part.count != 1) {  
  77.             for (int j=0 ; j<fcount ; j++) {  
  78.                 const Animation::Frame& frame(part.frames[j]);  
  79.                 glDeleteTextures(1, &frame.tid);  
  80.             }  
  81.         }  
  82.     }  
  83.   
  84.     return false;  
  85. }  

那么到movie为止,动画是在播放了,而且还在循环检测是否退出,即checkExit()


checkExit()的实现

[cpp]  view plain  copy
  1. void BootAnimation::checkExit() {  
  2.     // Allow surface flinger to gracefully request shutdown  
  3.     char value[PROPERTY_VALUE_MAX];  
  4.     property_get(EXIT_PROP_NAME, value, "0");//属性为1,说明要退出了  
  5.     int exitnow = atoi(value);  
  6.     if (exitnow) {  
  7.         requestExit();  
  8.     }  
  9. }  

[cpp]  view plain  copy
  1. property_get(EXIT_PROP_NAME, value, "0");检测这个属性,=1就退出动画  
[cpp]  view plain  copy
  1. #define EXIT_PROP_NAME "service.bootanim.exit"  
这个属性就是上面讲到的,等到launcher跑起来后就会置1

那动画是什么时候退出的?

当launcher应用程序主线程跑起来后,如果主线程处于空闲,就会向ActivityManagerService发送一个activityIdle的消息。

应用程序主线程是ActivityThread.java来描述的,activityIdle是这个类来实现的

[cpp]  view plain  copy
  1. private class Idler implements MessageQueue.IdleHandler {  
  2. ...  
  3.                 IActivityManager am = ActivityManagerNative.getDefault();  
  4.     ...  
  5.                         try {  
  6.                             am.activityIdle(a.token, a.createdConfig, stopProfiling);  
  7.                             a.createdConfig = null;  
  8.                         } catch (RemoteException ex) {  
  9.                             // Ignore  
  10.                         }  
  11.                     ....  
  12.     }  

上面的ActivityManagerNavtive.getDefault()得到am

来到frameworks/base/core/java/android/app/ActivityManagerNative.java

[cpp]  view plain  copy
  1. static public IActivityManager getDefault() {  
  2.     return gDefault.get();//getDefault的实现  
  3. }  

[cpp]  view plain  copy
  1. private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {  
  2.         protected IActivityManager create() {  
  3.             IBinder b = ServiceManager.getService("activity");  
  4.             if (false) {  
  5.                 Log.v("ActivityManager""default service binder = " + b);  
  6.             }  
  7.             IActivityManager am = asInterface(b);  
  8.             if (false) {  
  9.                 Log.v("ActivityManager""default service = " + am);  
  10.             }  
  11.             return am;  
  12.         }  
  13.     };  

gDefault实际上是IActivityManager,往下看

[cpp]  view plain  copy
  1. class ActivityManagerProxy implements IActivityManager  
  2. {  


ActivityManagerProxy实现了IActivityManager

那么am.activityIdle()就是ActivityManagerProxy里的函数,如下

[cpp]  view plain  copy
  1. public void activityIdle(IBinder token, Configuration config, boolean stopProfiling)  
  2.             throws RemoteException  
  3.     {  
  4.         ...  
  5.         mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);//发送ACTIVITY_IDLE_TRANSACTION  
  6.   
  7.   ....  
  8.     }  

发送了ACTIVITY_IDLE_TRANSACTION的进程间通信,这个消息被 ActivityManagerNative 接收处理了。

[cpp]  view plain  copy
  1. case ACTIVITY_IDLE_TRANSACTION: {//收到消息  
  2.             data.enforceInterface(IActivityManager.descriptor);  
  3.             IBinder token = data.readStrongBinder();  
  4.             Configuration config = null;  
  5.             if (data.readInt() != 0) {  
  6.                 config = Configuration.CREATOR.createFromParcel(data);  
  7.             }  
  8.             boolean stopProfiling = data.readInt() != 0;  
  9.             if (token != null) {  
  10.                 activityIdle(token, config, stopProfiling);//这个函数在ActivityManagerService被重写  
  11.             }  
  12.             reply.writeNoException();  
  13.             return true;  
  14.         }  

收到消息后就调用了activityIdle函数,这个函数被ActivityManagerService重写了,如下

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

[cpp]  view plain  copy
  1. @Override  
  2.     public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {  
  3.         final long origId = Binder.clearCallingIdentity();  
  4.         synchronized (this) {  
  5.             ActivityStack stack = ActivityRecord.getStackLocked(token);  
  6.             if (stack != null) {  
  7.                 ActivityRecord r =  
  8.                         mStackSupervisor.activityIdleInternalLocked(token, false, config);  
  9.                 if (stopProfiling) {  
  10.                     if ((mProfileProc == r.app) && (mProfileFd != null)) {  
  11.                         try {  
  12.                             mProfileFd.close();  
  13.                         } catch (IOException e) {  
  14.                         }  
  15.                         clearProfilerLocked();  
  16.                     }  
  17.                 }  
  18.             }  
  19.         }  
  20.         Binder.restoreCallingIdentity(origId);  
  21.     }  

调用activityIdleInternalLocked函数,在下面实现

frameworks/base/services/java/com/android/server/am/ActivityStackSupervisor.java

[cpp]  view plain  copy
  1.     final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,  
  2.             Configuration config) {  
  3.         ....          
  4.   
  5.         if (enableScreen) {  
  6.             mService.enableScreenAfterBoot();//调ActivityManagerService类的enableScreenAfterBoot()函数  
  7.          }  
  8. ....  
  9.         if (activityRemoved) {  
  10.             resumeTopActivitiesLocked();  
  11.         }  
  12.   
  13.         return r;  
  14.     }  


来到frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

[cpp]  view plain  copy
  1. void enableScreenAfterBoot() {  
  2.         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,  
  3.                 SystemClock.uptimeMillis());  
  4.         mWindowManager.enableScreenAfterBoot();//调WindowManagerService类里的enableScreenAfterBoot()函数  
  5.   
  6.         synchronized (this) {  
  7.             updateEventDispatchingLocked();  
  8.         }  
  9.     }  

来到frameworks/base/services/java/com/android/server/wm/WindowManagerService.java


[cpp]  view plain  copy
  1. public void enableScreenAfterBoot() {  
  2.  ....  
  3.   
  4.     performEnableScreen();  
  5. }  

performEnableScreen()实现


[cpp]  view plain  copy
  1.  public void performEnableScreen() {  
  2.        
  3.   
  4. ..  
  5.                  surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED  
  6.                                          data, null, 0);  
  7. ....  
  8.  }  

发送了FIRST_CALL_TRANSACTION的请求



因为从下面知道FIRST_CALL_TRANSACTION = BOOT_FINISHED

所以BnSurfaceComposer收到消息

frameworks/native/include/gui/ISurfaceComposer.h

[cpp]  view plain  copy
  1. class BnSurfaceComposer: public BnInterface<ISurfaceComposer> {  
  2. public:  
  3.     enum {  
  4.         // Note: BOOT_FINISHED must remain this value, it is called from  
  5.         // Java by ActivityManagerService.  
  6.         BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,  
  7.         ...  
  8.     };  
  9.   
  10.     virtual status_t onTransact(uint32_t code, const Parcel& data,  
  11.             Parcel* reply, uint32_t flags = 0);  
  12. };  


[cpp]  view plain  copy
  1. </pre></p><p><span style="color:#333333;">frameworks/native/libs/gui/ISurfaceComposer.cpp</span></p><p><span style="color:#333333;"></span><pre name="code" class="cpp">status_t BnSurfaceComposer::onTransact(  
  2.     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)  
  3. {  
  4.     switch(code) {  
  5.           
  6.         ....  
  7.         case BOOT_FINISHED: {  
  8.             CHECK_INTERFACE(ISurfaceComposer, data, reply);  
  9.             bootFinished();//调用 bootFinished()  
  10.             return NO_ERROR;  
  11.         }  
  12.           
  13.         ....  
  14.     }  
  15.     // should be unreachable  
  16.     return NO_ERROR;  
  17. }  

bootFinished()函数BpSurfaceComposer里实现,但发现没有,他又发了一个BOOT_FINISHED,死循环了,其实没有。bootFinished()被 SurfaceFlinger类重写了

[cpp]  view plain  copy
  1. class BpSurfaceComposer : public BpInterface<ISurfaceComposer>  
  2. {  
  3.   
  4.     virtual void bootFinished()  
  5.     {  
  6.         Parcel data, reply;  
  7.         data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());  
  8.         remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply);  
  9.     }  

 重写

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

[cpp]  view plain  copy
  1. void SurfaceFlinger::bootFinished()  
  2. {  
  3. ...  
  4.     property_set("service.bootanim.exit""1");  
  5. }  

把service.bootanim.exit写成1,然后bootanimation进程的checkExit()检测到就退出进程,停止播放。

猜你喜欢

转载自blog.csdn.net/wd229047557/article/details/80771595