2012年4月18日 星期三

Android SurfaceFlinger Part 2-1 - Android View System

這裡要解釋的正式左邊的這張圖,一個app開起來之後不管是建立一般的物件或是使用skia這個2D的繪圖的library最後都是在放在surface上面。接下來要討論的就是在一個app建立之後view的建立、surface的建立和如何與surfaceflinger做溝通。


在Android的app來說,他的外表示靠Activity來建立的,因此我們就從Activity開始來看。當一個 app要被啟動是利用zygote去fork一個process來啟動。最先會跑到 main()@ActivityThread.java 然後直到handleLaunchActivity()@ActivityThread.java 來建立Activity。

handleLaunchActivity()@ActivityThread.java
//利用performLaunchActivity()回傳一個activity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
    r.createdConfig = new Configuration(mConfiguration);
    Bundle oldState = r.state;
    handleResumeActivity(r.token, false, r.isForward);
    ...
}
...

我們可以發現藉由performLaunchActivity()來建立一個activity之後呼叫handleResumeActivity() 先了解一下performLaunchActivity好了

performLaunchActivity()@ActivityThread.java
    ...
   
   //launch一個activity
   activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
    ...
   activity.mCalled = false;
  
   //在這裡呼叫activity裡頭的onCreate()
   mInstrumentation.callActivityOnCreate(activity, r.state);
   if (!activity.mCalled) {
       throw new SuperNotCalledException(
       "Activity " + r.intent.getComponent().toShortString() +
       " did not call through to super.onCreate()");
   }
   r.activity = activity;
   r.stopped = true;
   ...
return activity;
}

我們繼續看一下mInstrumentation.newActivity() 的部份。

newActivity()@Instrumentation.java
public Activity newActivity(Class clazz, Context context, 
            IBinder token, Application application, Intent intent, ActivityInfo info, 
            CharSequence title, Activity parent, String id,
            Object lastNonConfigurationInstance) throws InstantiationException, 
            IllegalAccessException {
        Activity activity = (Activity)clazz.newInstance();
        ActivityThread aThread = null;
        
        //設定一些activity的東西
        activity.attach(context, aThread, this, token, application, intent,
                info, title, parent, id,
                (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                new Configuration());
        return activity;
    }

我們看一下activity.attach()  

attach()@Activity.java
...

//利用PolicyManager.makeNewWindow來產生一個window
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
那PolicyManager.makeNewWindow()是什麼呢?

makeNewWindow()@PolicyManager.java
    ...
    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            //在run time的時候讀取
            //frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java
            //一個PhonePolicy
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            ...
        }
    }

    // The static methods to spawn new policy-specific objects
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
如果我們去看Policy.java就可以發現真正的makeNewWindow了

makeNewWindow()@Policy.java
public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
}
回到performLaunchActivity(),他會去呼叫Activity裡面的onCreate()而在這裡面我們會利用setContentView()來設定我們的UI,接著看一下handleResumeActivity()

handleResumeActivity()@ActivityThread.java
     ...
if (r.window == null && !a.mFinished && willBeVisible) {
    r.window = r.activity.getWindow();
    
    //取得decorView
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    
    //取得WindowManager
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;
    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    l.softInputMode |= forwardBit;
    if (a.mVisibleFromClient) {
        a.mWindowAdded = true;
        
        //將取得的 decor加入取得的WindowManager
        wm.addView(decor, l);
    }

    
} 
       ...

這裡有兩個地方可以看的。第一個這個decorView是什麼呢?第二個,這個WindowManager又是哪來的呢? 第一部份,r.window.getDecorView()的前面r.window是什麼呢?剛剛我們追過就是phoneWindow所以getDecorView()我們就直接看
PhoneWindow.getDecorView()

getDecorView()@PhoneWindow.java
  
@Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }
private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
            //generateLayout input是mDecor return 一個viewgroup
            mContentParent = generateLayout(mDecor);
      ...
}

protected ViewGroup generateLayout(DecorView decor) {
        ...
        //取得title 並加入decor中
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        
        //findViewById實際上是return getDecorView().findViewById(id);
        //他會去尋找{@link android.app.Activity#onCreate}所定義的xml
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        ...
        return contentParent;
}

因此我們可以發現,最剛開始windows manager裡面的view是一個PhoneWindow而PhoneWindow裡面是一個decorView裡面又包含了programmer定義的view 這是一個Decorator的design pattern 接著分析完了handleResumeActivity中的view, viewmanager之後我們繼續看 wm.addView(decor,l)

addView()@Window.java
public final void addView(View view, ViewGroup.LayoutParams params) {
            // Let this throw an exception on a bad params.
            WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
            CharSequence curTitle = wp.getTitle();
            
            ...
            super.addView(view, params);
        }
而LocalWindowManager 他是extends WindowManagerImpl.CompatModeWrapper, 因此我們去看一下WindowManagerImpl 的CompatModeWrapper才知道addView是寫些什麼

addView()@WindowManagerImpl.java
public void addView(View view, ViewGroup.LayoutParams params) {
        addView(view, params, null, false);
}

private void addView(View view, ViewGroup.LayoutParams params,
            CompatibilityInfoHolder cih, boolean nest) {
        
        ...
        //ViewRootImpl是我們接下來的重點
        ViewRootImpl root;
        View panelParentView = null;
        
        synchronized (this) {
            ...
            
            root = new ViewRootImpl(view.getContext());
            root.mAddNesting = 1;
            ...
            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
        // do this last because it fires off messages to start doing things
        //這裡的view是我們一開始addView(mDecor,l)中的mDecor
        root.setView(view, wparams, panelParentView);
    }
我們進入ViewRootImpl了解一下

ViewRootImpl.java
//ViewRootImpl繼承了Handler
public final class ViewRootImpl extends Handler implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    //這裡建立一個surface 的 object
    private final Surface mSurface = new Surface();
    //還有一個W 的object, W這個class 是繼承IWindow.stub
    final W mWindow;
    ....
    public ViewRootImpl(Context context) {
        ....
        //初始化對
        getWindowSession(context.getMainLooper());
        ....
        mWindow = new W(this);
        ....
    }
    ....
}
public static IWindowSession getWindowSession(Looper mainLooper) {
        synchronized (mStaticInit) {
            if (!mInitialized) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
                    //open session透過Binder與WindowManagerService做溝通做溝通
                    sWindowSession = Display.getWindowManager().openSession(
                            imm.getClient(), imm.getInputContext());
                    mInitialized = true;
                } catch (RemoteException e) {
                }
            }
            return sWindowSession;
        }
}
ok看完了ViewRootImpl的一些global variable及constructure之後我們回到剛剛的root.setView() setView()@ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
               ...

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();//實際上就是scheduleTraversals()
                ...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                }
                ...
    }
public void requestLayout() {
        checkThread();
        mLayoutRequested = true;
        
        scheduleTraversals();
}
public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;

            //noinspection ConstantConditions
            if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
                final long now = System.nanoTime();
                Log.d(TAG, "Latency: Scheduled traversal, it has been "
                        + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
                        + "ms since the last traversal finished.");
            }

            sendEmptyMessage(DO_TRAVERSAL);
        }
}
public void handleMessage(Message msg) {
        switch (msg.what) {
        ...
        case DO_TRAVERSAL:
            ...

            performTraversals();

            ...
}
private void performTraversals() {
   .... 
   //取得一個surface
   relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
   ....
   //從surface裡面取得一個canvas然後畫在上面再把他show到螢幕
   draw(fullRedrawNeeded);
   ....
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        ....
       //透過IWindowSession與WindowManagerService溝通取得一個surface 
       int relayoutResult = sWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
                mPendingConfiguration, mSurface);
        //Log.d(TAG, "<<<<<< BACK FROM relayout");
        
        ....
        return relayoutResult;
    }

private void draw(boolean fullRedrawNeeded) {
        //mSurface是剛剛ViewRootImpl的global variable
        Surface surface = mSurface;
        ...
        
        if (!dirty.isEmpty() || mIsAnimating) {
            Canvas canvas;
            try {
                int left = dirty.left;
                int top = dirty.top;
                int right = dirty.right;
                int bottom = dirty.bottom;

                ....
                //lock一塊canvas
                canvas = surface.lockCanvas(dirty);

                ....
                    try {
                        ....
                        //把東西畫到canvas上
                        mView.draw(canvas);
                    } finally {
                        ....
                    }

                    ....
                }

            } finally {
                //把canvas show到螢幕上
                surface.unlockCanvasAndPost(canvas);
            }
        }

        ....
        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
    }
總結一下,在這一篇我們說了
  1. 建立一個Activity之後會有一個PhoneWindow 這個PhoneWindow裡面包含了DecorView他是一個framelayout 我們的app所畫得ui會包含在Decorview裡頭 
  2. 建立一個Activity之後也會產生一個LocalWindowManager主要實做在WindowManagerImpl而他包含了ViewRootImpl
  3. ViewRootImpl包含了一個view指向DecorView及透過Binder從WindowServiceManager取得的surface
  4. 要畫出ui首先透過mSurface lock一塊canvas 然後透過mView.draw畫在上面 然後unlock 之後把他show在螢幕上

此篇是依照鄧凡平的Android系統原理深入解析 去trace 而成

2012年4月6日 星期五

Android SurfaceFlinger Part 1 - 啟動流程

SurfaceFlinger啟動方法有兩種

  • 方法一: 隨著zygote啟動(Default)

       啟動的流程是 main@SystemServer.java ->system_init@com_android_server_SystemServer.cpp
        ->system_init@system_init.cpp
       在system_init的function裡寫著

    property_get("system_init.startsurfaceflinger", propBuf, "1");
        if (strcmp(propBuf, "1") == 0) {
        // Start the SurfaceFlinger
        SurfaceFlinger::instantiate();
    }
     ...
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

        因此,假設系統中不存在"system_init.startsensorservice"就會執行SurfaceFlinger::instantiate();定義    在BinderService.h 最終執行
 sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
        將surfaceflinger加入系統的service.


  • 方法二:直接執行surfaceflinger

       我們可以在例如init.rc裡面直接去啟動surfaceflinger,程式碼位置是main_surfaceflinger.cpp 程式碼的內容是

  int main(int argc, char** argv) {
      SurfaceFlinger::publishAndJoinThreadPool();
      return 0;
  }
     SurfaceFlinger定義在SurfaceFlinger.h,而他是繼承BinderService 定義在BinderService.h 因此,我們可以在  BinderService.h看到 publishAndJoinThreadPool()的實做
    sp proc(ProcessState::self());
    sp sm(defaultServiceManager());
    sm->addService(String16(SERVICE::getServiceName()), new SERVICE());
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

兩種方法都是利用
 sm->addService(String16(SERVICE::getServiceName()), new SERVICE());   
將surfaceflinger加入service並利用
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
進入surfaceflinger裡的
SurfaceFlinger::readyToRun()
定義在Thread::_threadLoop@Threads.cpp

...
self->mStatus = self->readyToRun();
...
readyToRun()會做幾件事:

  • 建立GraphicPlane
  • GraphicPlane& plane(graphicPlane(dpy));
    DisplayHardware* const hw = new DisplayHardware(this, dpy);
    plane.setDisplayHardware(hw);

    在建立DisplayHardware的物件時init()@DisplayHareware,會new 一個FramebufferNativeWindow 的object FramebufferNativeWindow()@FramebufferNativeWindow 然後分別建立fbdev framebuffer_open()@gralloc.h及grdev gralloc_open()@gralloc.h 最後都會透過gralloc_device_open()@gralloc.cpp來建立,
    如果是framebuffer的話,會呼叫fb_device_open()@framebuffer.cpp 在裡面要注意的是他透過mapFrameBuffer()->mapFrameBufferLocked()嘗試開啟
    char const * const device_template[] = {
        "/dev/graphics/fb%u",
        "/dev/fb%u",
        0 };
    
    與kernel做溝通. 如果是graphic的話就要看平台實做了。


  • 初始化EGL
    • 取得Display資訊
    • EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
      eglInitialize(display, NULL, NULL);
      eglGetConfigs(display, NULL, 0, &numConfigs);
      err = selectConfigForPixelFormat(display, attribs, format, &config);
      

    • 建立main surface by call eglCreateWindowSurface()
    • surface = eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL);
      if (mFlags & PARTIAL_UPDATES) {
          // if we have partial updates, we definitely don't need to
          // preserve the backbuffer, which may be costly.
          eglSurfaceAttrib(display, surface,
                      EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
      }
      
    • 取得LCD 的density
    •     if (property_get("qemu.sf.lcd_density", property, NULL) <= 0) {
              if (property_get("ro.sf.lcd_density", property, NULL) <= 0) {
                  LOGW("ro.sf.lcd_density not defined, using 160 dpi by default.");
                  strcpy(property, "160");
              }
          } else {
              /* for the emulator case, reset the dpi values too */
              mDpiX = mDpiY = atoi(property);
          }
          mDensity = atoi(property) * (1.0f/160.0f);
      
    • 建立的Context
    • context = eglCreateContext(display, config, NULL, contextAttributes);
      
      context存放著當前的 顏色,texture 等等的資訊
    • 暫時 unBind context
    • eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

  • 初始化hw composer
  • mHwc = new HWComposer(mFlinger);
    if (mHwc->initCheck() == NO_ERROR) {
        mHwc->setFrameBuffer(mDisplay, mSurface);
    }
    
  • Bind context
  • 最後回到readyToRun()透過hw.makeCurrent();來bind context接著就可以開始畫圖了
  • 播放動畫
  • property_set("ctl.start", "bootanim");
在readyToRun()跑完之後,就會進入SurfaceFlinger::threadLoop()在下一篇會討論