android6.0 Activity(四) Surface创建
原文:http://blog.csdn.net/luoshengyang/article/details/8303098。原文代码比較老了,可是核心不变。在原文基础上改动了一些代码,以及增加自己少量的理解。
在上一篇博客中。我们分析了应用程序窗体连接到WindowManagerService服务的过程。
在这个过程中,WindowManagerService服务会为应用程序窗体创建过一个到SurfaceFlinger服务的连接。
有了这个连接之后。WindowManagerService服务就能够为应用程序窗体创建画图表面了,以便能够用来渲染窗体的UI。
在本文中,我们就具体分析应用程序窗体的画图表面的创建过程。
一、Surface创建过程介绍
这节主要是把http://blog.csdn.net/luoshengyang/article/details/8303098这篇博客复制过来的
每个在C++层实现的应用程序窗体都须要有一个画图表面,然后才干够将自己的UI表现出来。这个画图表面是须要由应用程序进程请求SurfaceFlinger服务来创建的,在SurfaceFlinger服务内部使用一个Layer对象来描写叙述,同一时候,SurfaceFlinger服务会返回一个实现了ISurface接口的Binder本地对象给应用程序进程,于是,应用程序进程就能够获得一个实现了ISurface接口的Binder代理对象。有了这个实现了ISurface接口的Binder代理对象之后。在C++层实现的应用程序窗体就能够请求SurfaceFlinger服务分配图形缓冲区以及渲染已经填充好UI数据的图形缓冲区了。
对于在Java层实现的Android应用程序窗体来说,它也须要请求SurfaceFlinger服务为它创建画图表面,这个画图表面使用一个Surface对象来描写叙述。因为在Java层实现的Android应用程序窗体还要接受WindowManagerService服务管理。因此,它的画图表面的创建流程就会比在C++层实现的应用程序窗体复杂一些。
详细来说,就是在在Java层实现的Android应用程序窗体的画图表面是通过两个Surface对象来描写叙述,一个是在应用程序进程这一側创建的,还有一个是在WindowManagerService服务这一側创建的,它们相应于SurfaceFlinger服务这一側的同一个Layer对象,如图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" style="width:393px; height:375px">
在应用程序进程这一側。每个应用程序窗体,即每个Activity组件,都有一个关联的Surface对象,这个Surface对象是保在在一个关联的ViewRootImpl对象的成员变量mSurface中的。
这里我们仅仅关注Surface类的实现。在应用程序进程这一側,每个Java层的Surface对都相应有一个C++层的Surface对象。
在WindowManagerService服务这一側。每个应用程序窗体,即每个Activity组件。都有一个相应的WindowState对象,这个WindowState对象的成员变量mSurface相同是指向了一个Surface对象,在WindowManagerService服务这一側,每个Java层的Surface对都相应有一个C++层的SurfaceControl对象。
一个应用程序窗体分别位于应用程序进程和WindowManagerService服务中的两个Surface对象有什么差别呢?尽管它们都是用来操作位于SurfaceFlinger服务中的同一个Layer对象的,只是,它们的操作方式却不一样。详细来说,就是位于应用程序进程这一側的Surface对象负责绘制应用程序窗体的UI,即往应用程序窗体的图形缓冲区填充UI数据,而位于WindowManagerService服务这一側的Surface对象负责设置应用程序窗体的属性。比如位置、大小等属性。这两种不同的操作方式各自是通过C++层的Surface对象和SurfaceControl对象来完毕的。因此。位于应用程序进程和WindowManagerService服务中的两个Surface对象的使用方法是有差别的。
之所以会有这种差别。是由于绘制应用程序窗体是独立的,由应用程序进程来完就可以。而设置应用程序窗体的属性却须要全局考虑,即须要由WindowManagerService服务来统筹安排,比如,一个应用程序窗体的Z轴坐标大小要考虑它到的窗体类型以及它与系统中的其他窗体的关系。
讲到这里。另外一个问题又来了,因为一个应用程序窗体相应有两个Surface对象,那么它们是怎样创建出来的呢?简单地说,就是依照下面步骤来创建:
1. 应用程序进程请求WindowManagerService服务为一个应用程序窗体创建一个Surface对象;
2. WindowManagerService服务请求SurfaceFlinger服务创建一个Layer对象,而且获得一个ISurface接口。
3. WindowManagerService服务将获得的ISurface接口保存在其内部的一个Surface对象中,而且将该ISurface接口返回给应用程序进程;
4. 应用程序进程得到WindowManagerService服务返回的ISurface接口之后,再将其封装成其内部的另外一个Surface对象中。
那么应用程序窗体的画图表面又是什么时候创建的呢?通常是在不存在的时候就创建。由于应用程序窗体在执行的过程中,它的画图表面会依据须要来销毁以及又一次创建的,比如。应用程序窗体在第一次显示的时候,就会请求WindowManagerService服务为其创建绘制表面。从前面Android应用程序窗体(Activity)的视图对象(View)的创建过程分析一文能够知道,当一个应用程序窗体被激活而且它的视图对象创建完毕之后,应用程序进程就会调用与其所关联的一个ViewRootImpl对象的成员函数requestLayout来请求对其UI进行布局以及显示。由于这时候应用程序窗体的画图表面尚未创建,因此,ViewRoot类的成员函数requestLayout就会请求WindowManagerService服务来创建画图表面。接下来,我们就从ViewRoot类的成员函数requestLayout開始。分析应用程序窗体的画图表面的创建过程。如图所看到的:
二、requestLayout函数
之前我们分析过在ViewRootImpl的setView函数中调用了requestLayout函数,如今我们来分析下这个函数。 ViewRootImpl类的成员函数requestLayout首先调用另外一个成员函数checkThread来检查当前线程是否就是创建当前正在处理的ViewRootImpl对象的线程。假设不是的话,那么ViewRoot类的成员函数checkThread就会抛出一个异常出来。ViewRoot类是从Handler类继承下来的,用来处理应用程序窗体的UI布局和渲染等消息。
因为这些消息都是与Ui相关的。因此它们就须要在UI线程中处理,这样我们就能够判断出当前正在处理的ViewRootImpl对象是要应用程序进程的UI线程中创建的。进一步地,我们就能够判断出ViewRootImpl类的成员函数checkThread实际上就是用来检查当前线程是否是应用程序进程的UI线程,假设不是的话。它就会抛出一个异常出来。
通过了上述检查之后,ViewRootImpl类的成员函数requestLayout首先将其成员变量mLayoutRequested的值设置为true。表示应用程序进程的UI线程正在被请求运行一个UI布局操作。接着再调用另外一个成员函数scheduleTraversals来继续运行UI布局的操作。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在scheduleTraversals函数中,ViewRootImpl类的成员变量mTraversalScheduled用来表示应用程序进程的UI线程是否已经在scheduleTraversals。假设已经调度了的话。它的值就会等于true。在这样的情况下, ViewRootImpl类的成员函数scheduleTraversals就什么也不做。否则的话,它就会首先将成员变量mTraversalScheduled的值设置为true,就会发送一个消息来处理mTraversalRunnable这个Runnable。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
我们来看下TraversalRunnable 这个Runnable,就是调用了doTraversal函数
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
而在doTraversal这个函数中调用了performTraversals函数
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
} performTraversals(); if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
ViewRootImpl类的成员函数performTraversals的实现是相当复杂的。这里我们分析它的实现框架.
在分析ViewRootImpl类的成员函数performTraversals的实现框架之前,我们首先了解ViewRoot类的下面五个成员变量:
--mView:它的类型为View,但它实际上指向的是一个DecorView对象。用来描写叙述应用程序窗体的顶级视图,这一点能够參考前面Android应用程序窗体(Activity)的视图对象(View)的创建过程分析一文。
--mLayoutRequested:这是一个布尔变量。用来描写叙述应用程序进程的UI线程是否须要正在被请求运行一个UI布局操作。
--mFirst:这是一个布尔变量,用来描写叙述应用程序进程的UI线程是否第一次处理一个应用程序窗体的UI。
--mFullRedrawNeeded:这是一个布尔变量。用来描写叙述应用程序进程的UI线程是否须要将一个应用程序窗体的所有区域都又一次绘制。
--mSurface:它指向一个Java层的Surface对象。用来描写叙述一个应用程序窗体的画图表面。
注意,成员变量mSurface所指向的Surface对象在创建的时候,还没有在C++层有一个关联的Surface对象,因此。这时候它描写叙述的就是一个无效的画图表面。另外,这个Surface对象在应用程序窗体执行的过程中,也会可能被销毁,因此,这时候它描写叙述的画图表面也会变得无效。在上述两种情况中,我们都须要请求WindowManagerService服务来为当前正在处理的应用程序窗体创建有一个有效的画图表面,以便能够在上面渲染UI。这个创建画图表面的过程正是本文所要关心的。
理解了上述五个成员变量之后。我们就能够分析ViewRootImpl类的成员函数performTraversals的实现框架了,例如以下所看到的:
1. 将成员变量mView和mFullRedrawNeeded的值分别保存在本地变量host和fullRedrawNeeded中。而且将成员变量mTraversalScheduled的值设置为false,表示应用程序进程的UI线程中的消息已经被处理了。
2. 本地变量newSurface用来描写叙述当前正在处理的应用程序窗体在本轮的消息处理中是否新创建了一个画图表面。它的初始值为false。
3. 假设成员变量mLayoutRequested的值等于true,那么就表示应用程序进程的UI线程正在被请求对当前正在处理的应用程序窗体运行一个UI布局操作,因此,这时候就会调用本地变量host所描写叙述的一个顶层视图对象的成员函数measure来測量位于各个层次的UI控件的大小。
4. 假设当前正在处理的应用程序窗体的UI是第一次被处理。即成员变量mFirst的值等于true,或者当前正在处理的应用程序窗体的大小发生了变化。即本地变量windowShouldResize的值等于true,或者当前正在处理的应用程序窗体的边衬发生了变化。即本地变量insetsChanged的值等于true,或者正在处理的应用程序窗体的可见性发生了变化。即本地变量viewVisibilityChanged的值等于true。或者正在处理的应用程序窗体的UI布局參数发生了变化,即本地变量params指向了一个WindowManager.LayoutParams对象,那么应用程序进程的UI线程就会调用另外一个成员函数relayoutWindow来请求WindowManagerService服务又一次布局系统中的全部窗体。WindowManagerService服务在又一次布局系统中的全部窗体的过程中,假设发现当前正在处理的应用程序窗体尚未具有一个有效的画图表面,那么就会为它创建一个有效的画图表面。这一点是我们在本文中所要关注的。
5. 应用程序进程的UI线程在调用ViewRootImpl类的成员函数relayoutWindow来请求WindowManagerService服务又一次布局系统中的全部窗体之前。会调用成员变量mSurface所指向的一个Surface对象的成员函数isValid来推断它描写叙述的是否是一个有效的画图表面。而且将结果保存在本地变量hadSurface中。
6. 应用程序进程的UI线程在调用ViewRootImpl类的成员函数relayoutWindow来请求WindowManagerService服务又一次布局系统中的全部窗体之后,又会继续调用成员变量mSurface所指向的一个Surface对象的成员函数isValid来推断它描写叙述的是否是一个有效的画图表面。假设这时候成员变量mSurface所指向的一个Surface对象描写叙述的是否是一个有效的画图表面,而且本地变量hadSurface的值等于false,那么就说明WindowManagerService服务为当前正在处理的应用程序窗体新创建了一个有效的画图表面。于是就会将本地变量newSurface和fullRedrawNeeded的值均改动为true。
7. 应用程序进程的UI线程再次推断mLayoutRequested的值是否等于true。假设等于的话,那么就说明须要对当前正在处理的应用程序窗体的UI进行又一次布局,这是通过调用本地变量host所描写叙述的一个顶层视图对象的成员函数layout来实现的。在对当前正在处理的应用程序窗体的UI进行又一次布局之前。应用程序进程的UI线程会将成员变量mLayoutRequested的值设置为false,表示之前所请求的一个UI布局操作已经得到处理了。
8. 应用程序进程的UI线程接下来就要開始对当前正在处理的应用程序窗体的UI进行又一次绘制了,只是在重绘之前,会先询问一下那些注冊到当前正在处理的应用程序窗体中的Tree Observer,即调用它们的成员函数dispatchOnPreDraw,看看它们是否须要取消接下来的重绘操作,这个询问结果保存在本地变量cancelDraw中。
9. 假设本地变量cancelDraw的值等于false,而且本地变量newSurface的值也等于false,那么就说明注冊到当前正在处理的应用程序窗体中的Tree Observer不要求取消当前的这次重绘操作,而且当前正在处理的应用程序窗体也没有获得一个新的画图表面。在这样的情况下。应用程序进程的UI线程就会调用ViewRoot类的成员函数draw来对当前正在处理的应用程序窗体的UI进行重绘。
在重绘之前,还会将ViewRoot类的成员变量mFullRedrawNeeded的值重置为false。
10. 假设本地变量cancelDraw的值等于true,或者本地变量newSurface的值等于true,那么就说明注冊到当前正在处理的应用程序窗体中的Tree Observer要求取消当前的这次重绘操作。或者当前正在处理的应用程序窗体获得了一个新的画图表面。在这两种情况下,应用程序进程的UI线程就不能对当前正在处理的应用程序窗体的UI进行重绘了,而是要等到下一个消息到来的时候。再进行重绘。以便使得当前正在处理的应用程序窗体的各项參数能够得到又一次设置。
下一个消息须要立即被调度。因此。应用程序进程的UI线程就会又一次运行ViewRootImpl类的成员函数scheduleTraversals。
这样,我们就分析完毕ViewRoot类的成员函数performTraversals的实现框架了,接下来我们就继续分析ViewRootImpl类的成员函数relayoutWindow的实现,以便能够看到当前正在处理的应用程序窗体的画图表面是怎样创建的。
在performTraversals函数中调用了relayoutWindow函数例如以下:
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
然后在relayoutWindow函数中调用了WindowSession的relayout函数
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, mPendingConfiguration, mSurface);
我们先看看IWindowSession函数。这个函数是一个aidl须要在out文件夹下看其java文件
public interface IWindowSession extends android.os.IInterface
{
...... public static abstract class Stub extends android.os.Binder implements android.view.IWindowSession
{
...... private static class Proxy implements android.view.IWindowSession
{
private android.os.IBinder mRemote;
...... public int relayout(android.view.IWindow window, android.view.WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending,
android.graphics.Rect outFrame,
android.graphics.Rect outContentInsets,
android.graphics.Rect outVisibleInsets,
android.content.res.Configuration outConfig,
android.view.Surface outSurface) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain(); int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((window!=null))? (window.asBinder()):(null))); if ((attrs!=null)) {
_data.writeInt(1);
attrs.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
} _data.writeInt(requestedWidth);
_data.writeInt(requestedHeight);
_data.writeInt(viewVisibility);
_data.writeInt(((insetsPending)?(1):(0))); mRemote.transact(Stub.TRANSACTION_relayout, _data, _reply, 0); _reply.readException();
_result = _reply.readInt(); if ((0!=_reply.readInt())) {
outFrame.readFromParcel(_reply);
} if ((0!=_reply.readInt())) {
outContentInsets.readFromParcel(_reply);
} if ((0!=_reply.readInt())) {
outVisibleInsets.readFromParcel(_reply);
} if ((0!=_reply.readInt())) {
outConfig.readFromParcel(_reply);
} if ((0!=_reply.readInt())) {
outSurface.readFromParcel(_reply);
} } finally {
_reply.recycle();
_data.recycle();
} return _result;
} ......
} ......
} ......
}
IWindowSession.Stub.Proxy类的成员函数relayout首先将从前面传进来的各个參数写入到Parcel对象_data中,接着再通过其成员变量mRemote所描写叙述的一个Binder代理对象向执行在WindowManagerService服务内部的一个Session对象发送一个类型为TRANSACTION_relayout的进程间通信请求,当中。这个Session对象是用来描写叙述从当前应用程序进程到WindowManagerService服务的一个连接的。
我们来看下ViewRootImpl的Surface的readFromParcel函数,获取数据之后调用了setNativeObjectLocked函数
public void readFromParcel(Parcel source) {
if (source == null) {
throw new IllegalArgumentException("source must not be null");
} synchronized (mLock) {
// nativeReadFromParcel() will either return mNativeObject, or
// create a new native Surface and return it after reducing
// the reference count on mNativeObject. Either way, it is
// not necessary to call nativeRelease() here.
mName = source.readString();
setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
}
setNativeObjectLocked函数保存了从WMS传过来的指针。
private void setNativeObjectLocked(long ptr) {
if (mNativeObject != ptr) {
if (mNativeObject == 0 && ptr != 0) {
mCloseGuard.open("release");
} else if (mNativeObject != 0 && ptr == 0) {
mCloseGuard.close();
}
mNativeObject = ptr;
mGenerationId += 1;
if (mHwuiContext != null) {
mHwuiContext.updateSurface();
}
}
}
当执行在WindowManagerService服务内部的Session对象处理完毕当前应用程序进程发送过来的类型为TRANSACTION_relayout的进程间通信请求之后,就会将处理结果写入到Parcel对象_reply中。而且将这个Parcel对象_reply返回给当前应用程序进程处理。返回结果包括了一系列与參数window所描写叙述的应用程序窗体相关的參数,例如以下所看到的:
1. 窗体的大小:终于保存在输出參数outFrame中。
2. 窗体的内容区域边衬大小:终于保存在输出參数outContentInsets中。
3. 窗体的可见区域边衬大小:终于保存在输出參数outVisibleInsets中。
4. 窗体的配置信息:终于保存在输出參数outConfig中。
5. 窗体的画图表面:终于保存在输出參数outSurface中。
这里我们仅仅关注从WindowManagerService服务返回来的窗体画图表面是怎样保存到输出參数outSurface中的,即关注Surface类的成员函数readFromParcel的实现。从前面的调用过程能够知道,输出參数outSurface描写叙述的便是当前正在处理的应用程序窗体的画图表面。将WindowManagerService服务返回来的窗体画图表面保存在它里面。就相当于是为当前正在处理的应用程序窗体创建了一个画图表面。
在分析Surface类的成员函数readFromParcel的实现之前,我们先分析Session类的成员函数relayout的实现,由于它是用来处理类型为TRANSACTION_relayout的进程间通信请求的。
2.1 WMS中创建SurfaceControl
session类的relayout的话最后调用了WMS的relayoutWindow函数
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Configuration
outConfig,
Surface outSurface) {
if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outConfig, outSurface);
if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
return res;
}
在WMS中后面有例如以下代码,是把对象传到outSurface中去了。
SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
if (surfaceControl != null) {
outSurface.copyFrom(surfaceControl);
WindowManagerService类的成员函数relayoutWindow的实现是相当复杂的。这里我们仅仅关注与创建应用程序窗体的画图表面相关的代码。
简单来说。WindowManagerService类的成员函数relayoutWindow依据应用程序进程传递过来的一系列数据来又一次设置由參数client所描写叙述的一个应用程序窗体的大小和可见性等信息。而当一个应用程序窗体的大小或者可见性发生变化之后,系统中当前获得焦点的窗体,以及输入法窗体和壁纸窗体等都可能会发生变化,并且也会对其他窗体产生影响,因此,这时候WindowManagerService类的成员函数relayoutWindow就会对系统中的窗体的布局进行又一次调整。对系统中的窗体的布局进行又一次调整的过程是整个WindowManagerService服务最为复杂和核心的内容。我们相同是在后面的文章中再具体分析。
如今,我们就主要分析參数client所描写叙述的一个应用程序窗体的画图表面的创建过程。
WindowManagerService类的成员函数relayoutWindow首先获得与參数client所相应的一个WindowState对象win,这是通过调用WindowManagerService类的成员函数windowForClientLocked来实现的。假设这个相应的WindowState对象win不存在,那么就说明应用程序进程所请求处理的应用程序窗体不存在,这时候WindowManagerService类的成员函数relayoutWindow就直接返回一个0值给应用程序进程。
WindowManagerService类的成员函数relayoutWindow接下来推断參数client所描写叙述的一个应用程序窗体是否是可见的。一个窗体仅仅有在可见的情况下。WindowManagerService服务才会为它创建一个画图表面。 一个窗体是否可见由下面两个条件决定:
1. 參数viewVisibility的值等于View.VISIBLE,表示应用程序进程请求将它设置为可见的。
2. WindowState对象win的成员变量mAppToken不等于null,而且它所描写叙述的一个AppWindowToken对象的成员变量clientHidden的值等于false。这意味着參数client所描写叙述的窗体是一个应用程序窗体,即一个Activity组件窗体,而且这个Activity组件当前是处于可见状态的。当一个Activity组件当前是处于不可见状态时,它的窗体就也必须是处于不可见状态。
注意。当WindowState对象win的成员变量mAppToken等于null时。仅仅要满足条件1就能够了。由于这时候參数client所描写叙述的窗体不是一个Activity组件窗体。它的可见性不像Activity组件窗体一样受到Activity组件的可见性影响。
我们假设參数client所描写叙述的是一个应用程序窗体,而且这个应用程序窗体是可见的,那么WindowManagerService类的成员函数relayoutWindow接下来就会调用WindowState对象win的winAnimator的函数createSurfaceLocked来为它创建一个画图表面。假设这个画图表面能创建成功,那么WindowManagerService类的成员函数relayoutWindow就会将它的内容复制到输出參数outSource所描写叙述的一个Surface对象去,以便能够将它返回给应用程序进程处理。还有一方面,假设这个画图表面不能创建成功。那么WindowManagerService类的成员函数relayoutWindow就会将输出參数outSource所描写叙述的一个Surface对象的内容释放掉,以便应用程序进程知道该Surface对象所描写叙述的画图表面已经失效了。
接下来,我们就继续分析WindowState类的winAnimator的成员函数createSurfaceLocked的实现,以便能够了解一个应用程序窗体的画图表面的创建过程。
SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
if (mSurfaceControl == null) {
if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
"createSurface " + this + ": mDrawState=DRAW_PENDING");
mDrawState = DRAW_PENDING;
if (w.mAppToken != null) {
if (w.mAppToken.mAppAnimator.animation == null) {
w.mAppToken.allDrawn = false;
w.mAppToken.deferClearAllDrawn = false;
} else {
// Currently animating, persist current state of allDrawn until animation
// is complete.
w.mAppToken.deferClearAllDrawn = true;
}
} mService.makeWindowFreezingScreenIfNeededLocked(w); int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs; if (mService.isSecureLocked(w)) {
flags |= SurfaceControl.SECURE;
} int width;
int height;
if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
// for a scaled surface, we always want the requested
// size.
width = w.mRequestedWidth;
height = w.mRequestedHeight;
} else {
width = w.mCompatFrame.width();
height = w.mCompatFrame.height();
} // Something is wrong and SurfaceFlinger will not like this,
// try to revert to sane values
if (width <= 0) {
width = 1;
}
if (height <= 0) {
height = 1;
} float left = w.mFrame.left + w.mXOffset;
float top = w.mFrame.top + w.mYOffset; // Adjust for surface insets.
width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
left -= attrs.surfaceInsets.left;
top -= attrs.surfaceInsets.top; if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Creating surface in session "
+ mSession.mSurfaceSession + " window " + this
+ " w=" + width + " h=" + height
+ " x=" + left + " y=" + top
+ " format=" + attrs.format + " flags=" + flags);
} // We may abort, so initialize to defaults.
mSurfaceShown = false;
mSurfaceLayer = 0;
mSurfaceAlpha = 0;
mSurfaceX = 0;
mSurfaceY = 0;
w.mLastSystemDecorRect.set(0, 0, 0, 0);
mHasClipRect = false;
mClipRect.set(0, 0, 0, 0);
mLastClipRect.set(0, 0, 0, 0); // Set up surface control with initial size.
try {
mSurfaceW = width;
mSurfaceH = height; final boolean isHwAccelerated = (attrs.flags &
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
if (!PixelFormat.formatHasAlpha(attrs.format)
&& attrs.surfaceInsets.left == 0
&& attrs.surfaceInsets.top == 0
&& attrs.surfaceInsets.right == 0
&& attrs.surfaceInsets.bottom == 0) {
flags |= SurfaceControl.OPAQUE;
} mSurfaceFormat = format;
if (DEBUG_SURFACE_TRACE) {
mSurfaceControl = new SurfaceTrace(//新建一个SurfaceTrace对象
mSession.mSurfaceSession,
attrs.getTitle().toString(),
width, height, format, flags);
} else {
mSurfaceControl = new SurfaceControl(
mSession.mSurfaceSession,
attrs.getTitle().toString(),
width, height, format, flags);
}
在创建一个应用程序窗体的画图表面之前。我们须要知道下面数据:
1. 应用程序窗体它所执行的应用程序进程的PID。
2. 与应用程序窗体它所执行的应用程序进程所关联的一个SurfaceSession对象。
3. 应用程序窗体的标题。
4. 应用程序窗体的像素格式。
5. 应用程序窗体的宽度。
6. 应用程序窗体的高度。
7. 应用程序窗体的图形缓冲区属性标志。
第1个和第2个数据能够通过当前正在处理的WindowState对象的成员变量mSession所描写叙述的一个Session对象的成员变量mPid和mSurfaceSession来获得。第3个和第4个数据能够通过当前正在处理的WindowState对象的成员变量mAttr所描写叙述的一个WindowManager.LayoutParams对象的成员函数getTitle以及成员变量format来获得;接下来我们就分析后面3个数据是怎样获得的。
WindowState类的成员变量mFrame的类型为Rect。它用来描写叙述应用程序窗体的位置和大小,它们是由WindowManagerService服务依据屏幕大小以及其他属性计算出来的。因此,通过调用过它的成员函数width和height就能够得到要创建画图表面的应用程序窗体的宽度和高度。而且保存在变量w和h中。
可是,当当前正在处理的WindowState对象的成员变量mAttr所描写叙述的一个WindowManager.LayoutParams对象的成员变量flags的LayoutParams.FLAG_SCALED位不等于0时,就说明应用程序进程指定了该应用程序窗体的大小,这时候指定的应用程序窗体的宽度和高度就保存在WindowState类的成员变量mRequestedWidth和mRequestedHeight中,因此,我们就须要将当前正在处理的WindowState对象的成员变量mRequestedWidth和mRequestedHeight的值分别保存在变量w和h中。
经过上述两步计算之后,假设得到的变量w和h等于0,那么就说明当前正在处理的WindowState对象所描写叙述的应用程序窗体的大小还没有经过计算。或者还没有被指定过,这时候就须要将它们的值设置为1。避免接下来创建一个大小为0的画图表面。
假设当前正在处理的WindowState对象的成员变量mAttr所描写叙述的一个WindowManager.LayoutParams对象的成员变量memoryType的值等于MEMORY_TYPE_PUSH_BUFFERS,那么就说明正在处理的应用程序窗体不拥有专属的图形缓冲区,这时候就须要将用来描写叙述正在处理的应用程序窗体的图形缓冲区属性标志的变量flags的Surface.PUSH_BUFFERS位设置为1。
此外。假设当前正在处理的WindowState对象的成员变量mAttr所描写叙述的一个WindowManager.LayoutParams对象的成员变量flags的WindowManager.LayoutParams.FLAG_SECURE位不等于0,那么就说明正在处理的应用程序窗体的界面是安全的。即是受保护的。这时候就须要将用来描写叙述正在处理的应用程序窗体的图形缓冲区属性标志的变量flags的Surface.SECURE位设置为1。当一个应用程序窗体的界面是受保护时,SurfaceFlinger服务在运行截屏功能时,就不能把它的界面截取下来。
上述数据准备就绪之后,就能够创建当前正在处理的WindowState对象所描写叙述的一个应用程序窗体的画图表面了,只是在创建之前,还会初始化该WindowState对象的下面成员变量:
--mReportDestroySurface的值被设置为false。当一个应用程序窗体的画图表面被销毁时,WindowManagerService服务就会将对应的WindowState对象的成员变量mReportDestroySurface的值设置为true,表示要向该应用程序窗体所执行在应用程序进程发送一个画图表面销毁通知。
--mSurfacePendingDestroy的值被设置为false。当一个应用程序窗体的画图表面正在等待销毁时,WindowManagerService服务就会将对应的WindowState对象的成员变量mReportDestroySurface的值设置为true。
--mDrawPending的值被设置为true。当一个应用程序窗体的画图表面处于创建之后而且绘制之前时,WindowManagerService服务就会将对应的WindowState对象的成员变量mDrawPending的值设置为true,以表示该应用程序窗体的画图表面正在等待绘制。
--mCommitDrawPending的值被设置为false。
当一个应用程序窗体的画图表面绘制完毕之后而且能够显示出来之前时,WindowManagerService服务就会将对应的WindowState对象的成员变量mCommitDrawPending的值设置为true。以表示该应用程序窗体正在等待显示。
--mReadyToShow的值被设置为false。有时候当一个应用程序窗体的画图表面绘制完毕而且能够显示出来之后,因为与该应用程序窗体所关联的一个Activity组件的其他窗体还未准备好显示出来。这时候WindowManagerService服务就会将对应的WindowState对象的成员变量mReadyToShow的值设置为true,以表示该应用程序窗体须要延迟显示出来。即须要等到与该应用程序窗体所关联的一个Activity组件的其他窗体也能够显示出来之后再显示。
--假设成员变量mAppToken的值不等于null。那么就须要将它所描写叙述的一个AppWindowToken对象的成员变量allDrawn的值设置为false。
从前面Android应用程序窗体(Activity)与WindowManagerService服务的连接过程分析一文能够知道,一个AppWindowToken对象用来描写叙述一个Activity组件的,当该AppWindowToken对象的成员变量allDrawn的值等于true时,就表示属于该Activity组件的全部窗体都已经绘制完毕了。
--mSurfaceShown的值被设置为false,表示应用程序窗体还没有显示出来,它是用来显示调试信息的。
--mSurfaceLayer的值被设置为0。表示应用程序窗体的Z轴位置。它是用来显示调试信息的。
--mSurfaceAlpha的值被设置为1,表示应用程序窗体的透明值。它是用来显示调试信息的。
--mSurfaceX的值被设置为0,表示应用程序程序窗体的X轴位置,它是用来显示调试信息的。
--mSurfaceY的值被设置为0。表示应用程序程序窗体的Y轴位置,它是用来显示调试信息的。
--mSurfaceW的值被设置为w,表示应用程序程序窗体的宽度,它是用来显示调试信息的。
--mSurfaceH的值被设置为h。表示应用程序程序窗体的高度。它是用来显示调试信息的。
一个应用程序窗体的画图表面在创建完毕之后,函数就会将得到的一个Surface对象保存在当前正在处理的WindowState对象的成员变量mSurface中。注意。假设创建画图表面失败,而且从Surface类的构造函数抛出来的异常的类型为Surface.OutOfResourcesException,那么就说明系统当前的内存不足了。这时候函数就会调用WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来回收内存。
假设一切正常。那么函数接下来还会设置当前正在处理的WindowState对象所描写叙述应用程序窗体的下面属性:
1. X轴和Y轴位置。前面提到。WindowState类的成员变量mFrame是用来描写叙述应用程序窗体的位置和大小的,当中。位置就是通过它所描写叙述的一个Rect对象的成员变量left和top来表示的,它们分别应用窗体在X轴和Y轴上的位置。此外。当一个WindowState对象所描写叙述的应用程序窗体是一个壁纸窗体时。该WindowState对象的成员变量mXOffset和mYOffset用来描写叙述壁纸窗体相对当前要显示的窗体在X轴和Y轴上的偏移量。因此,将WindowState类的成员变量mXOffset的值加上另外一个成员变量mFrame所描写叙述的一个Rect对象的成员变量left的值,就能够得到一个应用程序窗体在X轴上的位置,相同。将WindowState类的成员变量mYOffset的值加上另外一个成员变量mFrame所描写叙述的一个Rect对象的成员变量top的值。就能够得到一个应用程序窗体在Y轴上的位置。终于得到的位置值就分别保存在WindowState类的成员变量mSurfaceX和mSurfaceY,而且会调用WindowState类的成员变量mSurface所描写叙述的一个Surface对象的成员函数setPosition来将它们设置到SurfaceFlinger服务中去。
2. Z轴位置。
在前面Android应用程序窗体(Activity)与WindowManagerService服务的连接过程分析一文中提到,WindowState类的成员变量mAnimLayer用来描写叙述一个应用程序窗体的Z轴位置,因此。这里就会先将它保存在WindowState类的另外一个成员变量mSurfaceLayer中。然后再调用WindowState类的成员变量mSurface所描写叙述的一个Surface对象的成员函数setLayer来将它设置到SurfaceFlinger服务中去。
3. 抖动标志。
假设WindowState类的成员变量mAttr所描写叙述的一个WindowManager.LayoutParams对象的成员变量flags的WindowManager.LayoutParams.FLAG_DITHER位不等于0,那么就说明一个应用程序窗体的图形缓冲区在渲染时。须要进行抖动处理,这时候就会调用WindowState类的成员变量mSurface所描写叙述的一个Surface对象的成员函数setLayer来将相应的应用程序窗体的图形缓冲区的属性标志的Surface.SURFACE_DITHER位设置为1。
4. 显示状态。
因为当前正在处理的WindowState对象所描写叙述的一个应用程序窗体的画图表面刚刚创建出来,因此。我们就须要通知SurfaceFlinger服务将它隐藏起来,这是通过调用当前正在处理的WindowState对象的成员变量mSurface所描写叙述的一个Surface对象的成员变量hide来实现的。
这时候还会将当前正在处理的WindowState对象的成员变量mSurfaceShown和mLastHidden的值分别设置为false和true。以表示相应的应用程序窗体是处于隐藏状态的。
注意,为了避免SurfaceFlinger服务每设置一个应用程序窗体属性就又一次渲染一次系统的UI,上述4个属性设置须要在一个事务中进行,这样就能够避免出现界面闪烁。我们通过调用Surface类的静态成员函数openTransaction和closeTransaction就能够分别在SurfaceFlinger服务中打开和关闭一个事务。
还有另外一个地方须要注意的是。在设置应用程序窗体属性的过程中,假设抛出了一个RuntimeException异常。那么就说明系统当前的内存不足了。这时候函数也会调用WindowManagerService类的成员函数reclaimSomeSurfaceMemoryLocked来回收内存。
接下来,我们就继续分析Surface类的构造函数的实现,以便能够了解一个应用程序窗体的画图表面的具体创建过程。
2.2 SurfaceControl在JNI层创建了SurfaceControl C++层对象那个
以下我们再来看看SurfaceControl的构造函数。主要是调用了nativeCreate函数
public SurfaceControl(SurfaceSession session,
String name, int w, int h, int format, int flags)
throws OutOfResourcesException {
if (session == null) {
throw new IllegalArgumentException("session must not be null");
}
if (name == null) {
throw new IllegalArgumentException("name must not be null");
} if ((flags & SurfaceControl.HIDDEN) == 0) {
Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
+ "to ensure that they are not made visible prematurely before "
+ "all of the surface's properties have been configured. "
+ "Set the other properties and make the surface visible within "
+ "a transaction. New surface name: " + name,
new Throwable());
} mName = name;
mNativeObject = nativeCreate(session, name, w, h, format, flags);
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
} mCloseGuard.open("release");
}
我们来看下这个nativeCreate就是通过上篇博客说的SurfaceComposerClient来获取一个c++层的SurfaceControl
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
jstring nameStr, jint w, jint h, jint format, jint flags) {
ScopedUtfChars name(env, nameStr);
sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
sp<SurfaceControl> surface = client->createSurface(
String8(name.c_str()), w, h, format, flags);
if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return 0;
}
surface->incStrong((void *)nativeCreate);
return reinterpret_cast<jlong>(surface.get());
}
而nativeCreate返回的值保存在mNativeObject 中,后面全部一些操作函数都是通过mNativeObject 传到JNI进行操作的。
public void setLayer(int zorder) {
checkNotReleased();
nativeSetLayer(mNativeObject, zorder);
} public void setPosition(float x, float y) {
checkNotReleased();
nativeSetPosition(mNativeObject, x, y);
} public void setSize(int w, int h) {
checkNotReleased();
nativeSetSize(mNativeObject, w, h);
在WMS中的Surface是保存在每个Activity相应的WindowState的winAnimator的mSurfaceControl成员变量中。而Activity的Surface是保存在ViewRootImpl的Surface是通过WMS的outSurface保存在ViewRoot的mSurface中。
所以最后不管在WMS的SurfaceControl还是ViewRootImpl的Surface最后在c++层用的是同一个对象。
三、总结
至此,我们就分析完毕Android应用程序窗体的画图表面的创建过程了。通过这个过程我们就能够知道:
1. 每个应用程序窗体都相应有两个Java层的Surface对象,当中一个是在WindowManagerService服务这一側创建的。而另外一个是在应用程序进程这一側创建的。
2. 在WindowManagerService服务这一側创建的Java层的Surface对象在C++层关联有一个SurfaceControl对象,用来设置应用窗体的属性,比如,大小和位置等。
3. 在应用程序进程这一側创建的ava层的Surface对象在C++层关联有一个Surface对象。用来绘制应用程序窗品的UI。
理解上述三个结论对理解Android应用程序窗体的实现框架以及WindowManagerService服务的实现都很重要。
一个应用程序窗体的画图表面在创建完毕之后,接下来应用程序进程就能够在上面绘制它的UI了。
android6.0 Activity(四) Surface创建的更多相关文章
- android6.0 适配的问题——activity销毁的问题
1.最近我去运行我们公司所开发的APP,发现出现很多问题,就是从前一个页面跳到另外一个页面后,前一个页面会被销毁. 正常来说,activity跳转过程是这样: A: 存在intent +setActi ...
- Android6.0权限大全和权限分类
本文转载至: https://blog.csdn.net/qq_26440221/article/details/53097868 自从出了Android6.0权限管理之后,再也不能像以前那样粘贴复制 ...
- Android app 在线更新那点事儿(适配Android6.0、7.0、8.0)
一.前言 app在线更新是一个比较常见需求,新版本发布时,用户进入我们的app,就会弹出更新提示框,第一时间更新新版本app.在线更新分为以下几个步骤: 1, 通过接口获取线上版本号,versionC ...
- android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。
android6.0 参考一:简书Android 6.0 新特性详解 参考二:关于Android6.0以上系统的权限问题 参考三:值得你关注的Android6.0上的重要变化(一) 参考四:值得你关注 ...
- Android开发学习之路-Android6.0运行时权限
在Android6.0以后开始,对于部分敏感的“危险”权限,需要在应用运行时向用户申请,只有用户允许的情况下这个权限才会被授予给应用.这对于用户来说,无疑是一个提升安全性的做法.那么对于开发者,应该怎 ...
- Android6.0 中appcompat_v7 报错
更新了AndroidSDK以后 各种错误,新建一个项目会附赠一个appcompat_v7,你只要知道这个是一个兼容包就可以了,具体的特性可以看相关介绍,其实也没啥特别的就是为了兼容低版本的呗, 但是呢 ...
- Android6.0机型上调用系统相机拍照返回的resultCode值始终等于0的问题
版权声明:本文为博主原创文章,未经博主允许不得转载. 正常情况下调用系统相机拍照: 如果拍照后点击的是“确定”图标,返回的resultCode = -1(Activity.RESULT_OK): 如果 ...
- Android项目的targetSDK>=23,在低于Android6.0的部分测试机(类似华为)上运行时出现的系统权限问题
相信大家对Android6.0以上的动态权限已经有所了解,很多童鞋也已经跃跃欲试地将自己项目的targetSDK升级到了23及其以上,很不幸的是我也成为了其中一员,然而我还是图样图森破了,升级之后的问 ...
- 说说Android6.0动态申请权限的那些坑
白天在做SDK23版本的适配,遇到了不少坑,现在抽空记下来,以此为戒. 首先要知道哪些坑,就得先了解一些定义和基本使用方式. 那么先介绍一下动态申请的权限分组情况. 下面的权限组是由谷歌官方定义的,目 ...
随机推荐
- [ZJOI2016]大森林
Description: 小Y家里有一个大森林,里面有n棵树,编号从1到n 0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例 ...
- CentOS7.5 通过wget下载文件到指定目录
在Linux命令行下面下载文件,通过wget是比较普遍简单的,比如在CentOS7 里面也一样. 我们先来看下自己的CentOS7 系统有没有安装wget: [root@test redis]# rp ...
- [OPENCV]cvHoughLines2使用说明
1.cvHoughLines2函数定义: CvSeq *cvHoughLines2 { CvArr *image, void *line_storage, int method, double rho ...
- MPU和CPU有什么区别?
MPU(或称MP,微处理器)和CPU(中央处理器)同为处理器,但范畴不同. 计算机(即电脑)分为巨型机,大型机,中型机,小型机和微型计算机5类.这5类计算机的运算核心统称为CPU,而MPU只是微型计算 ...
- PAT基础6-12
6-12 判断奇偶性 (10 分) 本题要求实现判断给定整数奇偶性的函数. 函数接口定义: int even( int n ); 其中n是用户传入的整型参数.当n为偶数时,函数返回1:n为奇数时返回0 ...
- JS如何判断浏览器类型和详细区分IE各版本浏览器
/* * 描述:判断浏览器信息 * 编写:LittleQiang_w * 日期:2016.1.5 * 版本:V1.1 */ //判断当前浏览类型 function BrowserType() { va ...
- Javascript 字符串(二)常用操作整理
一.js获取字符串的字节数 这个好使--- function getBytesLength(str) { // 在GBK编码里,除了ASCII字符,其它都占两个字符宽 return str.repla ...
- Go语言排序算法实现
// Algorithm project Algorithm.go package Algorithm // 冒泡排序 func BubbleSort(a []int) { n := len(a) ; ...
- 初学vue出现空格警告的原因及其解决办法
初学vue自己新建一个vue项目来做学习demo.不过在编写代码时一直出现空格不规范的警告.严重影响初学者的热情.错误如下图所示.(这样的错误很多,但大概翻译成中文的意思都是说空格使用不规范.) 这是 ...
- zookeeper视图工具
https://www.cnblogs.com/xd502djj/p/8919425.html