css3 模拟winphone 界面,从系统角度理解Android的界面绘制

对于ViewTree的绘制流程,Android开发者都很熟悉了,但如果要从整个系统的全局角度出发,理解Android的界面绘制机制,就需要了解系统的层级分工和设计实现,本文记录了个人对该机制的一些理解。

我们先尝试理解整个系统的分工,再看Activity如何利用这个分工体系,最后再看View的绘制,其实,整个结构大概是这样的:

b7b0be042ebf?utm_source=oschina-app

Android的界面绘制

整个系统的分工

任何一个操作系统要实现界面绘制,都需要处理好应用层、系统层和硬件层的分工协作,一般来说:

应用层负责定义画面的内容;

系统层负责综合整个屏幕的画面并保证流畅;

硬件层负责把数据输出到显示设备上。

我们分别来看:

应用层

除了系统窗口(如Toast),我们主要在Activity中绘制界面,这需要解决两个问题:

定义显示内容,基本原理就是在Canvas上绘制界面,然后调用surfaceholder.unlockCanvasAndPost函数,渲染到Surface中(视频是解码出视频帧,渲染到Surface上),Surface实际处于系统层,通过Ashmem共享内存传给Activity使用。

定义显示位置、层次和生命期,基本原理就是Activity的PhoneWindow利用Bindler机制和系统层通信,交给系统层去统一管理,如addView、removeView等都是通过WMS去做的。

系统Framework层

Android系统是在linux基础上扩展出来的,结构相对复杂,仅系统启动画面就有三个:BootLoader、linux内核、android系统服务,这三个都启动完,才会打开Launcher。

对于应用开发来说,最重要的是系统层中的Framework层,主要包括WMS和SurfaceFlinger两个系统服务,都运行在SystemServer进程中:

WMS,主要负责两件事,window的层级、window的管理:

层级上,WMS把所有界面分为应用window、子window和系统window三种,分别有自己的层级范围(1 - ~、1000 - ~、2000 - ~)。

管理上,WMS要负责添加和移除window,管理这些window的位置、大小和生命变化。

另外,WMS在调整window时,还需要通知SurfaceFlinger去更新界面,这样用户才能看到界面调整后的效果。

SurfaceFlinger,主要负责两件事,为应用提供Surface、整合图形数据:

为应用提供Surface,Activity获取Surface时,是WMS代为向SurfaceFlinger做的请求

整合图形数据,根据WMS的窗口层级,把相关的Surface整合起来,并放到BufferQueue里,供底层绘制界面,实际上起到了生产者的作用。

系统HAL层、系统Linux Kernel层和硬件层

把系统层的这两部分和硬件层放在一起说,是因为他们联系更紧密,更偏底层,平时做应用开发时也基本不涉及到。

HAL层:是个抽象接口,处理界面的是Gralloc接口,HAL是为了解决linux硬件驱动的版权问题(Android开源,但是有些厂商的硬件驱动不开源,用HAL可以规避这些问题)。

Linux Kernel层:Linux 内核使用帧缓冲FrameBuffer来实现显示功能,作为内存缓冲区(有32个Slot),既是操作硬件设备的接口,又可以缓解画面流畅和完整性的问题(队列、生产和消费)。

硬件层:利用驱动把数据输出到显示设备上。

Activity与Framework层的合作机制

了解过系统分工,我们就知道,Activity需要与Framework层的SurfaceFlinger和WMS合作,才能实现界面绘制,这个合作机制需要解决这样几个问题:

Window如何创建与使用

Surface如何获取与使用

View如何创建与绘制

Window的创建与使用

Window的管理核心在WMS,所以Window的创建和使用都需要与WMS建立通信,并交给WMS管理和调度。

关于创建,Activity启动时创建PhoneWindow,并与WMS建立通信,以便统一管理。

关于使用,在ViewRootImpl中调用WMS的addToDisplay,实现添加窗口。

具体过程如下:

首先,主线程ActivityThread启动Activity时,调用的performLaunchActivity会执行activity的attach函数关联context,application等,这时就会创建PhoneWindow,并把PhoneWindow和WMS关联,还会给WMS提供反向访问的Bindler参数mToken:

//Activity源码

final void attach(Context context, ActivityThread aThread,

Instrumentation instr, IBinder token, int ident,

Application application, Intent intent, ActivityInfo info,

CharSequence title, Activity parent, String id,

NonConfigurationInstances lastNonConfigurationInstances,

Configuration config, String referrer, IVoiceInteractor voiceInteractor,

Window window) {

...

mWindow = new PhoneWindow(this, window);

...

mWindow.setWindowManager(

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

mToken, mComponent.flattenToString(),

(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

setWindowManager函数是个多态特性,并不是PhoneWindow的函数,而是抽象类Window的函数:

//Window源码

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,

boolean hardwareAccelerated) {

...

mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

}

所以,PhoneWindow持有一个WindowManagerImpl实例,我们再看WindowManagerImpl的源码:

//WindowManagerImpl源码

public final class WindowManagerImpl implements WindowManager {

//持有WindowManagerGlobal的单例对象

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

所以,App中所有addView的操作,都会经过Activity-->PhoneWindow-->WindowManagerImpl-->WindowManagerGlobal的路径,最终在WindowManagerGlobal中执行,

addView操作是在主线程resume Activity时发起的:

//ActivityThread源码

final void handleResumeActivity(IBinder token,

boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {

...

ActivityClientRecord r = mActivities.get(token);

...

r = performResumeActivity(token, clearHide, reason);

...

r.window = r.activity.getWindow();

View decor = r.window.getDecorView();

...

ViewManager wm = a.getWindowManager();

...

wm.addView(decor, l);

根据前面的分析,addView真正的执行函数是在WindowManagerGlobal中:

//WindowManagerGlobal源码

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

...

ViewRootImpl root;

root = new ViewRootImpl(view.getContext(), display);

... //WindowManagerGlobal会保存每个窗口的viewrootimpl,decorview和params的

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

...

root.setView(view, wparams, panelParentView);

然后,在ViewRootImpl的setView函数中,会调用WMS去addToDisplay:

//ViewRootImpl源码

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

synchronized (this) {

...

//DecorView放在attachInfo对象里

mAttachInfo.mRootView = view;

//mWindow是IWindow对象,实际上是个用来跨进程通信的Bindler,这样WMS可以反向通信

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(),

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mOutsets, mInputChannel);

...

这里面涉及到的类的关系,可以参考Android 窗口管理:如何添加窗口到WMS总结的一张类图

b7b0be042ebf?utm_source=oschina-app

图片来自《Android 窗口管理:如何添加窗口到WMS》

所以,App内部的所有窗口由WindowManagerGlobal统一管理,而android系统的所有窗口由WMS统一管理。

Surface的获取

Surface是ViewRootImpl通过Bindler机制从SurfaceFlinger中通过Ashmem共享内存获取到的。

实际上,ViewRootImpl持有一个Surface对象,所以问题在于,ViewRootImpl中如何为Surface关联到了SurfaceFlinger中的对象。

具体过程如下:

首先,ViewRootImpl在setView和relayoutWindow时,都会调用relayoutWindow:

//ViewRootImpl源码

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

...

requestLayout();

...

@Override

public void requestLayout() {

if (!mHandlingLayoutInLayoutRequest) {

checkThread();

mLayoutRequested = true;

scheduleTraversals();

}

}

...

void scheduleTraversals() {

...

mChoreographer.postCallback(

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

...

final class TraversalRunnable implements Runnable {

@Override

public void run() {

doTraversal();

}

}

...

void doTraversal() {

...

performTraversals();

...

private void performTraversals() {

...

relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

...

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,

boolean insetsPending) throws RemoteException {

...

int relayoutResult = mWindowSession.relayout(

mWindow, mSeq, params,

(int) (mView.getMeasuredWidth() * appScale + 0.5f),

(int) (mView.getMeasuredHeight() * appScale + 0.5f),

viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,

mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,

mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,

mSurface);

最后的mWindowSession.relayout实际上就是Bindler通信了。

然后,WMS会先创建一个SurfaceControl,然后利用copyFrom获取其中的Surface。

//WindowManagerService源码

SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();

if (surfaceControl != null) {

outSurface.copyFrom(surfaceControl);

这会执行native函数nativeCreateFromSurfaceControl

最后,native层会通过SurfaceComposerClient去访问SurfaceFlinger,SurfaceFlinger从BufferQueue中dequeuBuffer,最终返回Surface。(为C++源码)

View如何创建与绘制

我们定义的ViewTree其实是DecorView中R.id.content那一部分,所以View的创建与绘制,核心在于建立与Window的关联,并能访问Surface。

关于Window,DecorView是关联了PhoneWindow。

关于Surface,DecorView通过ViewRootImpl访问Surface。

具体过程如下:

首先,View是从Activity的setContentView开始创建的:

//Activity源码

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);

实际上调用了PhoneWindow的setContentView:

//PhoneWindow源码

@Override

public void setContentView(View view, ViewGroup.LayoutParams params) {

...

installDecor();

...

private void installDecor() {

...

mDecor = generateDecor(-1);

...

mDecor.setWindow(this);

...

在这个过程中,DecorView得到了一个PhoneWindow对象:

//DecorView源码

private PhoneWindow mWindow;

...

void setWindow(PhoneWindow phoneWindow) {

mWindow = phoneWindow;

可以看到,DecorView和PhoneWindow是互相引用的。

这样,DecorView就完成了与PhoneWindow的关联,这样就可以被WMS管理。

然后,DecorView需要获取到ViewRootImpl,DecorView的顶级父类View提供了getViewRootImpl()函数:

//View源码

public ViewRootImpl getViewRootImpl() {

if (mAttachInfo != null) {

return mAttachInfo.mViewRootImpl;

}

return null;

}

Attachnfo是View的内部类,ViewRootImpl在初始化时,会创建这个对象,并把自己传进去:

ViewRootImpl源码

public ViewRootImpl(Context context, Display display) {

...

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

而且,ViewRootImpl在setView时也会设置自己的父控件:

//ViewRootImpl源码

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

...

view.assignParent(this);

这样,DecorView的具体功能就可以交给ViewRootImpl去实现:

//DecorView源码

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

...

getViewRootImpl().requestInvalidateRootRenderNode();

}

最后,ViewRootImpl持有的Surface提供Canvas,用于绘制界面内容:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,

boolean scalingRequired, Rect dirty) {

...

canvas = mSurface.lockCanvas(dirty);

所以,利用ViewRootImpl的Surface提供的Canvas,就可以绘制了。

小结

总的来说,对开发者来说,除了Activity之外,最重要的就是ViewRootImpl、PhoneWindow和WindowManagerGlobal

1.ViewRootImpl

ViewRootImpl是绘制的起点(控制DectorView的绘制),也是绘制的目标(mSurface),每次WindowManagerGlobal中addView,都会生成并保存一个ViewRootImpl对象;最终的绘制canvas,也是渲染到ViewRootImpl持有的mSurface中去。

2.PhoneWindow

PhoneWindow夹在Activity和DecorView之间,主要起到解耦和减负的作用,可以把Activity与View的管理/window的管理切割开。

例如,添加View实际上是交给WindowManager去addView/removeView/updateView,但是Activity不需要直接与WindowManager交互,而是让PhoneWindow去setContentView,PhoneWindow再去调用WindowManager的addView操作。

3.WindowManager

WindowManager是个抽象类,只有WindowManagerImpl一个实现,而WindowManagerImpl实际上。

ViewRootImpl被WindowManagerGlobal严密地管理了起来,WMS管理window时,也是通过操纵ViewRootImpl实现的,所以都说ViewRootImpl是WindowManager和DecorView的连接纽带。

其他

深入理解Android的界面绘制机制,我们就能理解很多扩展功能的原理:

扩展场景1

自定义系统开机画面,虽然没有启动Android,但是可以操作硬件驱动、或操作linux的framebuffer实现界面绘制,这也是各厂商自己定制系统时的修改方法。

扩展场景2

侧滑App,为什么可以向一侧滑动整个App界面,考虑到App实际上是向DecorView添加了ViewTree,这就可以在DecorView和ViewGroup中间插一层透明的View,这样就能滑动原有的ViewTree,达到侧滑效果。

原ViewTree

public class SWLayout extends FrameLayout{

...

//用当前ViewGroup代替ViewTree的根节点

ViewGroup decorView= (ViewGroup) activity.getWindow().getDecorView();

View child=decorView.getChildAt(0);

decorView.removeView(child);

addView(child);

decorView.addView(this);

...

}

扩展场景3

我们知道事件分发是从Activity开始的,但是悬浮窗也可以设置为允许响应事件,但是悬浮窗是没有Activity的,只做了addView,那么悬浮窗的事件是如何响应的?

硬件层拦截到事件,会从WMS传递到ViewRootImpl,而ViewRootImpl是WindowManagerGlobal在addView时创建的,所以悬浮窗虽然只做了addView,也有ViewRootImpl,也能响应事件。

当然,有Activity的情况下,ViewRootImpl的mView是DecorView,所以会把事件传递给DecorView,DecorView处理事件的函数为:

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

final Window.Callback cb = mWindow.getCallback();

return cb != null && !mWindow.isDestroyed() && mFeatureId < 0

? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);

}

其中,mWindow是持有DecorView的PhoneWindow对象,而这个PhoneWindow对象的Callback,是在Activity的attach中,创建出PhoneWindow后,把Activity作为了Callback:

//Activity源码

final void attach(Context context, ActivityThread aThread,

Instrumentation instr, IBinder token, int ident,

Application application, Intent intent, ActivityInfo info,

CharSequence title, Activity parent, String id,

NonConfigurationInstances lastNonConfigurationInstances,

Configuration config, String referrer, IVoiceInteractor voiceInteractor,

Window window) {

...

mWindow = new PhoneWindow(this, window);

...

mWindow.setCallback(this);

所以,在有Activity的情况下,事件传递是ViewRootImpl-->DectorView-->PhoneWindow.Callback也就是Activity,然后再传递给PhoneWindow-->DectorView-->ViewTree,这也可以解释为什么DectorView里同时存在dispatchTouchEvent和superDispatchTouchEvent两个函数。

而在没有Activity的情况下,ViewRootImpl的mView是我们addView时传入的ViewTree,事件就直接传递给ViewTree了。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值