【Android原始碼】WindowManagerService 淺析

指間沙似流年發表於2017-12-23

所有需要顯示在螢幕上的內容,都是通過WindowManager來操作的,這就是我們通常所說的WMS(WindowManagerService),本篇我們一起來分析一下WindowManagerService、Surface、SurfaceFlinger、WindowManager之間是如何建立關係並互動的。我們通過Dialog來分析它們之間如何建立聯絡並互動。

首先當我們想要獲取WindowManager的時候,發現它是一個抽象類,所以我們需要找到它的實現。 我們可以通過Context的getSystemService方法獲取到對應的服務例項,而WindowManager就是在ContextImpl中註冊的服務之一。

registerService(Context.WINDOW_SERVICE, WindowManager.class,
      new CachedServiceFetcher<WindowManager>() {
  @Override
  public WindowManager createService(ContextImpl ctx) {
      return new WindowManagerImpl(ctx);
  }});
複製程式碼

原來WIndowManager的例項是WindowManagerImpl。 那麼Dialog是如何獲取到WindowManager的?

首先我們先分析下Dialog的建構函式:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
   mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

   final Window w = new PhoneWindow(mContext);
   mWindow = w;
   w.setCallback(this);
   w.setOnWindowDismissedCallback(this);
   w.setWindowManager(mWindowManager, null, null);
   w.setGravity(Gravity.CENTER);

   mListenersHandler = new ListenersHandler(this);
}
複製程式碼

可以發現,在建構函式中獲取到WindowManager物件,並通過Window的setWindowManager方法將Window和WindowManager聯絡起來。

public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
     mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
   return new WindowManagerImpl(mContext, parentWindow);
}
複製程式碼

最終通過new WindowManagerImpl(mContext, parentWindow)將WindowManager和Window聯絡起來,此時可以發現這個構建的WindowManager方法中多出一個parentWindow,表示和具體的WIndow相關聯。

現在我們跳轉到WindowManagerImpl中:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
        return new WindowManagerImpl(displayContext, mParentWindow);
    }

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}
複製程式碼

可以發現WindowManagerImpl並沒有做什麼操作,新增View、移除View、更新View的操作最終都交給了WindowManagerGlobal類,並呼叫addView方法。

public void addView(View view, ViewGroup.LayoutParams params,
       Display display, Window parentWindow) {
   ViewRootImpl root;
   View panelParentView = null;

   synchronized (mLock) {
       // 構建ViewRootImpl
       root = new ViewRootImpl(view.getContext(), display);
		 // 設定佈局
       view.setLayoutParams(wparams);
		 // 將View新增到View列表中
       mViews.add(view);
       // 將root新增到root列表中
       mRoots.add(root);
       mParams.add(wparams);
   }

   // do this last because it fires off messages to start doing things
   try {
   		// 呼叫ViewRootImpl的addView方法將View顯示到介面上
       root.setView(view, wparams, panelParentView);
   } catch (RuntimeException e) {
     throw e;
   }
}
複製程式碼

通過分析WindowManagerGlobal,可以發現主要操作是:

  1. 構建ViewRootImpl
  2. 將佈局設定給View
  3. 將View、ViewRootImpl儲存起來
  4. 通過setView將view顯示到介面上

到此時,我們分析到了framework層,而WMS是在native層,那麼framework層是如何與native層建立關係和互動的呢?

這個時候我們就需要看ViewRootImpl類了,這個類繼承自Handler,是native與java層View系統通訊的橋樑,比如performTraversals函式就是收到繪製View的訊息之後,通過呼叫measure、layout、draw來繪製整個檢視。

首先我們先看建構函式:

public ViewRootImpl(Context context, Display display) {
   mContext = context;
   // 獲取Window Session,與WindowManagerService之間建立聯絡
   mWindowSession = WindowManagerGlobal.getWindowSession();
   // 儲存當前執行緒,更新UI只能在建立了ViewRootImpl的執行緒
   // 所以通常情況下,我們只能在UI執行緒更新UI,但並不是不能在子執行緒中更新UI
   mThread = Thread.currentThread();
}
複製程式碼

我們繼續點進WindowManagerGlobal.getWindowSession()方法:

public static IWindowSession getWindowSession() {
   synchronized (WindowManagerGlobal.class) {
       if (sWindowSession == null) {
           try {
               InputMethodManager imm = InputMethodManager.getInstance();
               // 獲取WindowManagerService
               IWindowManager windowManager = getWindowManagerService();
               // 與WindowManagerService建立聯絡
               sWindowSession = windowManager.openSession(
                       new IWindowSessionCallback.Stub() {
                           @Override
                           public void onAnimatorScaleChanged(float scale) {
                               ValueAnimator.setDurationScale(scale);
                           }
                       },
                       imm.getClient(), imm.getInputContext());
           } catch (RemoteException e) {
               throw e.rethrowFromSystemServer();
           }
       }
       return sWindowSession;
   }
}

public static IWindowManager getWindowManagerService() {
   synchronized (WindowManagerGlobal.class) {
       if (sWindowManagerService == null) {
           sWindowManagerService = IWindowManager.Stub.asInterface(
                   ServiceManager.getService("window"));
       }
       return sWindowManagerService;
   }
}
複製程式碼

最終通過ServiceManager.getService("window")獲取WMS,並將WMS轉換為IWindowManager。

public static IBinder getService(String name) {
   try {
       IBinder service = sCache.get(name);
       if (service != null) {
           return service;
       } else {
           return getIServiceManager().getService(name);
       }
   } catch (RemoteException e) {
       Log.e(TAG, "error in getService", e);
   }
   return null;
}
複製程式碼

通過getService方法可以發現,返回的是一個IBinder物件,這個也就能從側面說明Framework和WMS之間是通過Binder機制進行通訊的,獲取到WMS之後再通過IWindowManager.Stub.asInterface方法將WMS的IBinder轉換成WindowManager物件。最終通過openSession與WMS建立通訊對話,之後就可以通過這個session,java層和native層之間就可以通訊了。

這個時候View並不會顯示在介面上,WMS只負責管理View的z-order,也就是管理哪個view應該顯示在最上層。這個時候WindowManagerGlobal之後就呼叫了root.setView(view, wparams, panelParentView)方法,這個方法就是發起顯示請求:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
   synchronized (this) {
       if (mView == null) {
       		// 請求佈局
           requestLayout();
           // 向WMS發起請求
           res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                       getHostVisibility(), mDisplay.getDisplayId(),
                       mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                       mAttachInfo.mOutsets, mInputChannel);
       }
   }
}

@Override
public void requestLayout() {
   if (!mHandlingLayoutInLayoutRequest) {
       checkThread();
       mLayoutRequested = true;
       scheduleTraversals();
   }
}

void scheduleTraversals() {
   if (!mTraversalScheduled) {
       mChoreographer.postCallback(
               Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
   }
}

final class TraversalRunnable implements Runnable {
   @Override
   public void run() {
       doTraversal();
   }
}

void doTraversal() {
   if (mTraversalScheduled) {
       performTraversals();
   }
}

private void performTraversals() {
	// 獲取Surface,用於圖形繪製
	// performMeasure
	// performLayout
	// performDraw
}
複製程式碼

呼叫performTraversals方法,這個方法就是我們繪製介面的方法,裡面會呼叫我們所熟悉的onMeasure、onLayout、onDraw方法。

private void performDraw() {
   try {
   	  // 呼叫繪製函式
       draw(fullRedrawNeeded);
   } finally {
       mIsDrawing = false;
       Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   }
}

private void draw(boolean fullRedrawNeeded) {
	// 獲取surface
   Surface surface = mSurface;
   if (!surface.isValid()) {
       return;
   }
	// 繪製需要更新
   if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
   		// 使用硬體加速
       if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
				// 使用硬體渲染繪製
           mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
       } else {
       		// 使用cpu繪製
           if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
               return;
           }
       }
   }

   if (animating) {
       mFullRedrawNeeded = true;
       scheduleTraversals();
   }
}
複製程式碼

在draw函式中獲取需要繪製的區域,再判斷是否使用硬體加速。 通常情況下都是使用cpu繪製,也就是直接呼叫drawSoftware:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
       boolean scalingRequired, Rect dirty) {

   // Draw with software renderer.
   final Canvas canvas;
   try {
       final int left = dirty.left;
       final int top = dirty.top;
       final int right = dirty.right;
       final int bottom = dirty.bottom;

			// 獲取canvas物件,用於framework層繪製
       canvas = mSurface.lockCanvas(dirty);
   } catch (Surface.OutOfResourcesException e) {
   		return false;
   }

   try {
   		// 開始繪製,從decorView開始繪製
         mView.draw(canvas);
   } finally {
       	// 釋放canvas鎖,並通知SurfaceFlinger更新
       	// private static native void nHwuiDraw(long renderer); 呼叫native方法
           surface.unlockCanvasAndPost(canvas);
   }
   return true;
}
複製程式碼

從上面的函式可以看出繪製的過程:

  1. 判斷使用gpu還是cpu渲染
  2. 獲取繪製的surface物件
  3. 通過surface獲取並鎖住canvas物件
  4. 從DecorView開始發起整個view樹的繪製
  5. 解鎖canvas物件,並通知surfaceFlinger物件更新檢視,呼叫native方法。

到此為止,Activity、Dialog等元件的View就顯示到使用者的介面上了。

相關文章