浅析Android View绘制过程中的Surface
前言
在《浅析Android中View的测量布局流程》中我们对VSYNC
信号到达App
进程之后开启的View
布局过程进行了分析,经过对整个App
界面的View
树进行遍历完成了测量和布局,确定了View
的大小以及在屏幕中所处的位置。但是,如果想让用户在屏幕上看到View
的内容,还需要根据View
的绘制生成图形数据并交由硬件进行屏幕刷新。
View
的绘制主要负责将业务层的各种API
调用转换为指令,然后交给渲染引擎进行处理,最终生成能够被硬件直接处理的数据。这个过程主要分为渲染数据的生产以及消费,一般来说,渲染数据的生产者是各个App
进程,而消费者则是SurfaceFlinger
进程,这里会涉及到渲染数据的跨进程传输问题。下面将会对渲染数据的跨进程传输的实现进行分析。
Surface
作为数据载体负责打通App
进程与SurfaceFlinger
进程之间的数据交互,同时Surface
是属于App
进程内的资源,因此先从App
进程这个生产者出发,基于Surface
的创建流程及其使用,对渲染数据的传递机制进行分析。
绘制前的准备
在《浅析Android中View的测量布局流程》中有分析到,当测量数据发生变化时,需要对窗体大小进行更新,因为测量数据的变化导致视图展示区域随之发生变化。
根据分析测量布局流程的相关源码实现可知,一个ViewRootImpl
对象被创建时,都会创建一个Surface
对象以及SurfaceControl
对象,但是Surface
对象并不是立即可用于绘制渲染的,而只是一个壳子,其真正的实现是在native层。而在测量之后宽高如果发生变化,则需要对窗口大小进行更新,此时会对Surface
以及SurfaceControl
对象进行处理,即更新native层的Surface
以及SurfaceControl
对象,之后Surface
对象将进入可用状态。
SurfaceControl的创建
首先,App
进程为每一个Activity
创建了一个Window
,而每一个Window
会对应一个ViewRootImpl
,每一个ViewRootImpl
持有一个Surface
以及SurfaceControl
对象。然后,SystemServer
进程会对应地为App
进程的每一个Window
创建一个Window
,相对应地,也会为每一个Window
创建一个SurfaceControl
对象。下面我们看下SystemServer
进程中的SurfaceControl
的创建过程。
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks, AttachedSurfaceControl {final W mWindow; // mWindow = new W(this);public final Surface mSurface = new Surface();private final SurfaceControl mSurfaceControl = new SurfaceControl();// ...private void performTraversals() {// ...boolean windowShouldResize = layoutRequested && windowSizeMayChange && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth && frame.width() != mWidth) || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() != mHeight));// ...if (mFirst || windowShouldResize || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {try {// ...// 更新windowrelayoutResult = relayoutWindow(params, viewVisibility, insetsPending);// ...} catch (RemoteException e) {} finally {// ...}}// ...}private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException {// ...if (LOCAL_LAYOUT) {// ...} else {// mWindowSession是IWindowSession类型的对象,即Binder代理对象,对应实现是SystemServer进程中的Session类的实例,每个进程的IWindowSession对应SystemServer进程中一个Session实例relayoutResult = mWindowSession.relayout(mWindow, params, requestedWidth, requestedHeight, viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mRelayoutBundle);// ...}// ...// mSurfaceControl实例可用之后,根据mSurfaceControl的Surface信息对mSurface进行更新if (mSurfaceControl.isValid()) {if (!useBLAST()) {mSurface.copyFrom(mSurfaceControl);} else {updateBlastSurfaceIfNeeded();}// ...} else {// ...}// ...return relayoutResult;}// ...
}
relayoutWindow
方法会通过Binder
请求到SystemServer
进程,对之前SystemServer
进程中创建的window
实例进行更新。mWindowSession.relayout
会调用到WindowManagerService
中的相关逻辑,经过relayout
方法调用了WindowManagerService#createSurfaceControl
方法,完成SystemServer
进程中SurfaceControl
对象的创建。
从SystemServer
进程返回之后,通过getSurfaceControl
方法将新创建的SurfaceControl
对象的属性拷贝回App
进程的SurfaceControl
对象(即ViewRootImpl#mSurfaceControl
)中。
// com.android.server.wm.Session
/*** Session代表一个活跃的客户端session。SystemServer进程中会为每个进程维护一个Session对象,用于window相关的Binder通信。*/
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {// ...@Overridepublic int relayout(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq, int lastSyncSeqId, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Bundle outSyncSeqIdBundle) {// ...int res = mService.relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq, lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls, outSyncSeqIdBundle);// ...return res;}// ...
}// com.android.server.wm.WindowManagerService
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {// ...public int relayoutWindow(Session session, IWindow client, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq, int lastSyncSeqId, ClientWindowFrames outFrames, MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Bundle outSyncIdBundle) {// ...synchronized (mGlobalLock) {// 获取窗体状态描述,每一个Window(通常对应一个Activity)都对应一个WindowState。final WindowState win = windowForClientLocked(session, client, false);// 获取WindowStateAnimator,用于创建SurfaceControlWindowStateAnimator winAnimator = win.mWinAnimator;// ...// 只有view可见或者相关联的appToken没有隐藏时才应该relayout。final boolean shouldRelayout = viewVisibility == View.VISIBLE && (win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING || win.mActivityRecord.isClientVisible());// ...if (shouldRelayout && outSurfaceControl != null) {try {// 创建SurfaceControl并将其拷贝到outSurfaceControl,outSurfaceControlresult = createSurfaceControl(outSurfaceControl, result, win, winAnimator);} catch (Exception e) {// ...return 0;}}// ...}// ...return result;}private int createSurfaceControl(SurfaceControl outSurfaceControl, int result, WindowState win, WindowStateAnimator winAnimator) {if (!win.mHasSurface) {result |= RELAYOUT_RES_SURFACE_CHANGED;}WindowSurfaceController surfaceController;try {// 1. 通过WindowStateAnimator对象创建WindowSurfaceController对象surfaceController = winAnimator.createSurfaceLocked();} finally {// ...}if (surfaceController != null) {// 2. 通过WindowSurfaceController将SurfaceController拷贝到outSurfaceControl中,outSurfaceControl对应App进程中的ViewRootImpl的mSurfaceControl变量surfaceController.getSurfaceControl(outSurfaceControl); } else {// ...outSurfaceControl.release();}return result;}// ...
}/*** 为单个WindowState跟踪动画和surface的操作.**/// com.android.server.wm.WindowStateAnimator
class WindowStateAnimator {final WindowState mWin;WindowSurfaceController mSurfaceController;// 创建WindowSurfaceControllerWindowSurfaceController createSurfaceLocked() {final WindowState w = mWin;// 如果已经创建过就不会重复创建了if (mSurfaceController != null) {return mSurfaceController;}try {// ...mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format, flags, this, attrs.type);// ...} catch (OutOfResourcesException e) {// ...return null;} catch (Exception e) {// ...return null;}// ...return mSurfaceController;}// ...
}// com.android.server.wm.WindowSurfaceController
class WindowSurfaceController {WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator, int windowType) {// ...final SurfaceControl.Builder b = win.makeSurface().setParent(win.getSurfaceControl()).setName(name).setFormat(format).setFlags(flags).setMetadata(METADATA_WINDOW_TYPE, windowType).setMetadata(METADATA_OWNER_UID, mWindowSession.mUid).setMetadata(METADATA_OWNER_PID, mWindowSession.mPid).setCallsite("WindowSurfaceController");// ...mSurfaceControl = b.build();}public static class Builder {@NonNullpublic SurfaceControl build() {// ....return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata, mLocalOwnerView, mCallsite);}}
}
可以看出,WindowSurfaceController
是SurfaceControl
的包装类,通过持有SurfaceControl
对象来对Surface
进行操作,WindowSurfaceController
以及SurfaceControl
都是在SystemServer
进程创建的。接下来,继续跟着SurfaceControl
的构造函数看下,SurfaceControl
的创建具体做了哪些事情。
/*** 持有一个由系统合成器管理的Surface对象。这个SurfaceControl对象由buffer以及如何显示buffer的信息组成。* 通过构造的Surface对象可以提交数据到buffer,用于合成上屏。*/
public final class SurfaceControl implements Parcelable {public long mNativeObject;private long mNativeHandle;private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView, String callsite) throws OutOfResourcesException, IllegalArgumentException {// ...long nativeObject = 0;try {// ...nativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0, metaParcel);} finally {metaParcel.recycle();}// ...assignNativeObject(nativeObject, callsite);// ...}private static native long nativeCreate(SurfaceSession session, String name, int w, int h, int format, int flags, long parentObject, Parcel metadata) throws OutOfResourcesException;private void assignNativeObject(long nativeObject, String callsite) {if (mNativeObject != 0) {release();}if (nativeObject != 0) {mFreeNativeResources = sRegistry.registerNativeAllocation(this, nativeObject);}// 记录native层Surface对象的句柄值mNativeObject = nativeObject;mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;if (sDebugUsageAfterRelease && mNativeObject == 0) {mReleaseStack = new Throwable("Assigned invalid nativeObject");} else {mReleaseStack = null;}setUnreleasedWarningCallSite(callsite);addToRegistry();}
}
从源码可以看出,Java层的SurfaceControl
对象其实是一个壳,其内部的主要实现是在native层的SurfaceControl
对象中的,通过持有native层的SurfaceControl
对象的句柄值对其进行调用。因此,SurfaceControl
的关键实现在nativeCreate
这个native方法中。
// frameworks/base/core/jni/android_view_SurfaceControl.cpp
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, jobject metadataParcel) {// 获取SurfaceComposerClient对象,SurfaceComposerClient对象负责与SurfaceFlinger进程进行交互sp<SurfaceComposerClient> client;if (sessionObj != NULL) {client = android_view_SurfaceSession_getClient(env, sessionObj);} else {client = SurfaceComposerClient::getDefault();}SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);sp<SurfaceControl> surface;// ...// 创建SurfaceControl对象status_t err = client->createSurfaceChecked(String8(name.c_str()), w, h, format, &surface, flags, parentHandle, std::move(metadata));if (err == NAME_NOT_FOUND) {jniThrowException(env, "java/lang/IllegalArgumentException", NULL);return 0;} else if (err != NO_ERROR) {jniThrowException(env, OutOfResourcesException, statusToString(err).c_str());return 0;}surface->incStrong((void *)nativeCreate);return reinterpret_cast<jlong>(surface.get());
}
从源码中得知,通过SurfaceComposerClient
调用createSurfaceChecked
方法进行native层的SurfaceControl
的创建,内部通过成员变量mClient
跨进程调用到SurfaceFlinger
进程,mClient
的远程实现是SurfaceFlinger
进程的Client
类。
// frameworks/native/libs/gui/SurfaceComposerClient.cpp
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h, PixelFormat format, sp<SurfaceControl>* outSurface, int32_t flags, const sp<IBinder>& parentHandle, LayerMetadata metadata, uint32_t* outTransformHint) {status_t err = mStatus;if (mStatus == NO_ERROR) {gui::CreateSurfaceResult result;// 请求SurfaceFlinger进程创建一个SurfaceControlbinder::Status status = mClient->createSurface(std::string(name.c_str()), flags, parentHandle, std::move(metadata), &result);err = statusTFromBinderStatus(status);// ... if (err == NO_ERROR) {// 根据CreateSurfaceResult构造SurfaceControl对象,并将其返回// result.handle是一个Binder对象,封装了SurfaceFlinger进程的Layer以及SurfaceFlinger*outSurface = new SurfaceControl(this, result.handle, result.layerId, toString(result.layerName), w, h, format, result.transformHint, flags);}}return err;
}// frameworks/native/libs/gui/SurfaceControl.cpp
SurfaceControl::SurfaceControl(const sp<SurfaceComposerClient>& client, const sp<IBinder>& handle, int32_t layerId, const std::string& name, uint32_t w, uint32_t h, PixelFormat format, uint32_t transform, uint32_t flags): mClient(client),mHandle(handle),mLayerId(layerId),mName(name),mTransformHint(transform),mWidth(w),mHeight(h),mFormat(format),mCreateFlags(flags) {}
SurfaceFlinger
进程调用Client::createSurface
创建Surface
对象,并根据方法的返回结果构造了SystemServer
进程的SurfaceControl
对象,并将其返回最终拷贝回App
进程的SurfaceControl
对象,即ViewRootImpl#mSurfaceControl
。
但是,到这里其实并没有发现跨进程传输数据相关的代码实现,因此只能继续看下SurfaceFlinger
进程的Client::createSurface
方法具体做了什么事情。
// frameworks/native/services/surfaceflinger/Client.cpp
binder::Status Client::createSurface(const std::string& name, int32_t flags, const sp<IBinder>& parent, const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult) {sp<IBinder> handle;LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), name.c_str(), static_cast<uint32_t>(flags), std::move(metadata));args.parentHandle = parent;// 为App进程的SurfaceControl创建Layerconst status_t status = mFlinger->createLayer(args, *outResult);return binderStatusFromStatusT(status);
}// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) {status_t result = NO_ERROR;sp<Layer> layer;switch (args.flags & ISurfaceComposerClient::eFXSurfaceMask) {case ISurfaceComposerClient::eFXSurfaceBufferQueue:case ISurfaceComposerClient::eFXSurfaceContainer:case ISurfaceComposerClient::eFXSurfaceBufferState:args.flags |= ISurfaceComposerClient::eNoColorFill;[[fallthrough]];case ISurfaceComposerClient::eFXSurfaceEffect: {// 根据args创建Layerresult = createBufferStateLayer(args, &outResult.handle, &layer);std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();if (pendingBufferCounter) {std::string counterName = layer->getPendingBufferCounterName();mBufferCountTracker.add(outResult.handle->localBinder(), counterName, pendingBufferCounter);}} break;default:result = BAD_VALUE;break;}if (result != NO_ERROR) {return result;}sp<Layer> parent = LayerHandle::getLayer(args.parentHandle.promote());uint32_t outTransformHint;// 将创建好的Layer添加到缓存result = addClientLayer(args, outResult.handle, layer, parent, &outTransformHint);outResult.layerId = layer->sequence;outResult.layerName = String16(layer->getDebugName());return result;
}// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* handle, sp<Layer>* outLayer) {*outLayer = getFactory().createBufferStateLayer(args);*handle = (*outLayer)->getHandle();return NO_ERROR;
}// frameworks/native/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
sp<Layer> DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) {return sp<Layer>::make(args);
}// frameworks/native/services/surfaceflinger/Layer.cpp
Layer::Layer(const surfaceflinger::LayerCreationArgs& args): sequence(args.sequence),mFlinger(sp<SurfaceFlinger>::fromExisting(args.flinger)),mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),mClientRef(args.client),mWindowType(static_cast<WindowInfo::Type>(args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),mLayerCreationFlags(args.flags),mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName, this)) {// ...
}
SurfaceFlinger
进程中调用了SurfaceFlinger::createBufferStateLayer
方法创建了Layer
对象,因此SystemServer
进程中的SurfaceControl
对象对应SurfaceFlinger
进程的Layer
对象。
上面提到,经过App
进程到SystemServer
进程再到SurfaceFlinger
进程,最终完成了一系列window
相关的对象创建。最终依次返回并将相关信息跨进程拷贝回App
进程。之后因为App
进程的mSurfaceControl
处于有效状态,此时将会App
进程的mSurface
进行更新操作。
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks, AttachedSurfaceControl {final W mWindow; // mWindow = new W(this);public final Surface mSurface = new Surface();private final SurfaceControl mSurfaceControl = new SurfaceControl();private boolean mUseBLASTAdapter;private boolean mForceDisableBLAST;// ...public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {synchronized (this) {if (mView == null) {mView = view;// ...int res;// ...try {// ...res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, mTempControls);// ...} catch (RemoteException e) {// ...} finally {// ...}// ...// 是否开启BLAST取决于SystemServer进程返回的resif ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {mUseBLASTAdapter = true;}// ...}}}private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException {// ...if (LOCAL_LAYOUT) {// ...} else {relayoutResult = mWindowSession.relayout(mWindow, params, requestedWidth, requestedHeight, viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mRelayoutBundle);// ...}// ...// mSurfaceControl实例可用之后,根据mSurfaceControl的Surface信息对mSurface进行更新if (mSurfaceControl.isValid()) {if (!useBLAST()) {mSurface.copyFrom(mSurfaceControl);} else {updateBlastSurfaceIfNeeded();}// ...} else {// ...}// ...return relayoutResult;}boolean useBLAST() {return mUseBLASTAdapter && !mForceDisableBLAST;}// ...
}
在更新mSurface
之前会判断是否使用BLAST
,而是否使用BLAST
是在addView
的时候由SystemServer
进程决定的,根据源码可知Android S开始默认开启BLAST
,因此最后调用了updateBlastSurfaceIfNeeded
方法,其内部会调用Surface#transferFrom
方法,进而拷贝mNativeObject
的值。
// com.android.server.wm.Session
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {@Overridepublic int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes, InputChannel outInputChannel, InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame, float[] outSizeCompatScale) {return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,outAttachedFrame, outSizeCompatScale);}
}// com.android.server.wm.WindowManagerService
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {// ...// Whether the system should use BLAST for ViewRootImplfinal boolean mUseBLAST; // Android S之后默认开启// ...private WindowManagerService(Context context, InputManagerService inputManager,boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,DisplayWindowSettingsProvider displayWindowSettingsProvider,Supplier<SurfaceControl.Transaction> transactionFactory,Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {// ...mUseBLAST = Settings.Global.getInt(resolver, Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, 1) == 1;// ...}public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,float[] outSizeCompatScale) {// ...int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,appOp);if (res != ADD_OKAY) {return res;}// ...synchronized (mGlobalLock) {// ...res = ADD_OKAY;if (mUseBLAST) {res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;}// ...}Binder.restoreCallingIdentity(origId);return res;}
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks, AttachedSurfaceControl {final W mWindow; // mWindow = new W(this);public final Surface mSurface = new Surface();private final SurfaceControl mSurfaceControl = new SurfaceControl();private BLASTBufferQueue mBlastBufferQueue;private boolean mUseBLASTAdapter;private boolean mForceDisableBLAST;// ...void updateBlastSurfaceIfNeeded() {if (!mSurfaceControl.isValid()) {return;}// 如果对应的native层的SurfaceControl对象是同一个,则直接更新即可,不需要重新创建BLASTBufferQueueif (mBlastBufferQueue != null && mBlastBufferQueue.isSameSurfaceControl(mSurfaceControl)) {mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);return;}// 如果更新了SurfaceControl,那么销毁并重建BBQ(BLASTBufferQueue)来重置BufferQueue及BLASTBufferQueue的状态.if (mBlastBufferQueue != null) {mBlastBufferQueue.destroy();}mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);Surface blastSurface = mBlastBufferQueue.createSurface();// Only call transferFrom if the surface has changed to prevent inc the generation ID and// causing EGL resources to be recreated.mSurface.transferFrom(blastSurface);}// ...
}
因此,Android S开始由App进程创建管理BufferQueue
,即ViewRootImpl#mBlastBufferQueue
。那BLASTBufferQueue
是在什么时候创建的呢?查看源码发现,其实就是在ViewRootImpl#updateBlastSurfaceIfNeeded
方法中创建的。下面看下BLASTBufferQueue
的创建过程。
BLASTBufferQueue的创建
根据上面的分析得出,App
进程在创建完SurfaceControl
之后,会继续创建BLASTBufferQueue
,并且在创建BLASTBufferQueue
之前并没有发现和图像数据跨进程传输相关的代码实现,所以不妨假设下,BLASTBufferQueue
的创建其实就是为跨进程传输图像数据做准备。
// android.graphics.BLASTBufferQueue
public final class BLASTBufferQueue {// Note: This field is accessed by native code.public long mNativeObject; // BLASTBufferQueue*/** Create a new connection with the surface flinger. */public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @PixelFormat.Format int format) {this(name, true /* updateDestinationFrame */);update(sc, width, height, format);}public BLASTBufferQueue(String name, boolean updateDestinationFrame) {mNativeObject = nativeCreate(name, updateDestinationFrame);}private static native long nativeCreate(String name, boolean updateDestinationFrame);private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height, int format);
}
从BLASTBufferQueue
的构造函数可以看到,和SurfaceControl
以及Surface
类似,都是通过JNI
调用到native层去创建对应的对象。因此,Java层的BLASTBufferQueue
对象也是native层的BLASTBufferQueue
的壳,因此,进一步分析native层的代码。
// frameworks/base/core/jni/android_graphics_BLASTBufferQueue.cpp
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jboolean updateDestinationFrame) {ScopedUtfChars name(env, jName);sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str(), updateDestinationFrame);queue->incStrong((void*)nativeCreate);return reinterpret_cast<jlong>(queue.get());
}static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, jlong height, jint format) {sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);queue->update(reinterpret_cast<SurfaceControl*>(surfaceControl), width, height, format);
}
native层在创建了BLASTBufferQueue
对象之后,调用了BLASTBufferQueue::update
方法。
// frameworks/native/libs/gui/BLASTBufferQueue.cpp
BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinationFrame): mSurfaceControl(nullptr),mSize(1, 1),mRequestedSize(mSize),mFormat(PIXEL_FORMAT_RGBA_8888),mTransactionReadyCallback(nullptr),mSyncTransaction(nullptr),mUpdateDestinationFrame(updateDestinationFrame) {// 1. 先创建BufferQueue,然后初始化mProducer以及mConsumercreateBufferQueue(&mProducer, &mConsumer);// 因为是在client进程,因此为dequeue操作设置超时来保证dequeueBuffer时会阻塞线程。mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());// buffer的默认数量为2mProducer->setMaxDequeuedBufferCount(2);// 封装BufferQueueConsumermBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer, GraphicBuffer::USAGE_HW_COMPOSER | GraphicBuffer::USAGE_HW_TEXTURE, 1, false, this);static std::atomic<uint32_t> nextId = 0;mProducerId = nextId++;mName = name + "#" + std::to_string(mProducerId);auto consumerName = mName + "(BLAST Consumer)" + std::to_string(mProducerId);mQueuedBufferTrace = "QueuedBuffer - " + mName + "BLAST#" + std::to_string(mProducerId);mBufferItemConsumer->setName(String8(consumerName.c_str()));// 2. 设置监听器,用于当帧数据可用时消费Buffer数据mBufferItemConsumer->setFrameAvailableListener(this);ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers;mNumAcquired = 0;mNumFrameAvailable = 0;TransactionCompletedListener::getInstance()->addQueueStallListener([&](const std::string& reason) {std::function<void(const std::string&)> callbackCopy;{std::unique_lock _lock{mMutex};callbackCopy = mTransactionHangCallback;}if (callbackCopy) callbackCopy(reason);},this);
}void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, sp<IGraphicBufferConsumer>* outConsumer) {sp<BufferQueueCore> core(new BufferQueueCore());// 将BufferQueueCore传入BBQBufferQueueProducer,这样当producer发起调用时可以异步化,通过持有的BufferQueueCore将结果返回sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core, this));sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core));consumer->setAllowExtraAcquire(true);*outProducer = producer;*outConsumer = consumer;
}// frameworks/native/libs/gui/BufferQueueCore.cpp
BufferQueueCore::BufferQueueCore(): mMutex(),mIsAbandoned(false),mConsumerControlledByApp(false), // consumer不是给App控制的mConsumerName(getUniqueName()),mConsumerListener(),mConsumerUsageBits(0),mConsumerIsProtected(false),mConnectedApi(NO_CONNECTED_API),mLinkedToDeath(),mConnectedProducerListener(),mBufferReleasedCbEnabled(false),mBufferAttachedCbEnabled(false),mSlots(), // BufferSlot数组,长度为64mQueue(), // 元素类型为BufferItem的Vector变量mFreeSlots(), // 元素类型为int的set,对应没有buffer的索引位置mFreeBuffers(), // 元素类型为int的list,对应空闲buffer的索引位置mUnusedSlots(), // 元素类型为int的list,对应可以被释放的buffer的索引位置mActiveBuffers(), // 元素类型为int的set,对应正在使用的buffer的索引位置mDequeueCondition(),mDequeueBufferCannotBlock(false),mQueueBufferCanDrop(false),mLegacyBufferDrop(true),mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),mDefaultWidth(1),mDefaultHeight(1),mDefaultBufferDataSpace(HAL_DATASPACE_UNKNOWN),mMaxBufferCount(BufferQueueDefs::NUM_BUFFER_SLOTS),mMaxAcquiredBufferCount(1),mMaxDequeuedBufferCount(1),mBufferHasBeenQueued(false),mFrameCounter(0),mTransformHint(0),mIsAllocating(false),mIsAllocatingCondition(),mAllowAllocation(true),mBufferAge(0),mGenerationNumber(0),mAsyncMode(false),mSharedBufferMode(false),mAutoRefresh(false),mSharedBufferSlot(INVALID_BUFFER_SLOT),mSharedBufferCache(Rect::INVALID_RECT, 0, NATIVE_WINDOW_SCALING_MODE_FREEZE,HAL_DATASPACE_UNKNOWN),mLastQueuedSlot(INVALID_BUFFER_SLOT),mUniqueId(getUniqueId()),mAutoPrerotation(false),mTransformHintInUse(0) {int numStartingBuffers = getMaxBufferCountLocked();for (int s = 0; s < numStartingBuffers; s++) {mFreeSlots.insert(s);}for (int s = numStartingBuffers; s < BufferQueueDefs::NUM_BUFFER_SLOTS;s++) {mUnusedSlots.push_front(s);}
}
这里创建了BufferQueueCore
对象,并基于BufferQueueCore
对象创建了BBQBufferQueueProducer
以及BufferQueueConsumer
,分别是BufferQueueCore
的生产者和消费者,并被BLASTBufferQueue
的mProducer
和mConsumer
持有,mProducer
的主要操作包括dequeueBuffer
和queueBuffer
,mConsumer
的主要操作包括acquireBuffer
和releaseBuffer
。
可以看到这里主要是创建了一个BufferQueueCore
对象,用于管理Buffer
,而Buffer
是通过mProducer
和mConsumer
使用的,所以看下mProducer
和mConsumer
的内部实现。
BBQBufferQueueProducer的创建
通过源码可以看出,mProducer
和mConsumer
都是通过持有BufferQueueCore
来使用buffer
的,而mProducer
和mConsumer
中都提供了使用buffer
的方法。
// frameworks/native/libs/gui/BLASTBufferQueue.cpp
BBQBufferQueueProducer(const sp<BufferQueueCore>& core, wp<BLASTBufferQueue> bbq) : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/), mBLASTBufferQueue(std::move(bbq)) {}BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) :mCore(core),mSlots(core->mSlots),mConsumerName() {}// frameworks/native/libs/gui/BufferQueueProducer.cpp
BufferQueueProducer::BufferQueueProducer(const sp<BufferQueueCore>& core,bool consumerIsSurfaceFlinger) :mCore(core),mSlots(core->mSlots),mConsumerName(),mStickyTransform(0),mConsumerIsSurfaceFlinger(consumerIsSurfaceFlinger),mLastQueueBufferFence(Fence::NO_FENCE),mLastQueuedTransform(0),mCallbackMutex(),mNextCallbackTicket(0),mCurrentCallbackTicket(0),mCallbackCondition(),mDequeueTimeout(-1),mDequeueWaitingForAllocation(false) {}// frameworks/native/libs/gui/include/gui/BufferQueueProducer.h// dequeueBuffer获取下一个buffer的slot索引给producer使用。
// 如果有一个可用的buffer slot,那么就把slot索引写入参数处并返回,否则返回-EBUSY。
virtual status_t dequeueBuffer(int* outSlot, sp<Fence>* outFence, uint32_t width, uint32_t height, PixelFormat format, uint64_t usage, uint64_t* outBufferAge, FrameEventHistoryDelta* outTimestamps) override;// requestBuffer返回GraphicBuffer到第N个slot。
// 通常是在dequeueBuffer第一次slot N的时候。但是如果dequeueBuffer返回的flags表明之前返回的buffers已经失效的话,就必须再次调用requestBuffer。
virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);// queueBuffer返回一个填充过的buffer到BufferQueue。
// 调用方必须提供一个fence在所有渲染操作完成之后发送信号。
virtual status_t queueBuffer(int slot, const QueueBufferInput& input, QueueBufferOutput* output);// frameworks/native/libs/gui/BufferQueueConsumer.cpp
BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) :mCore(core),mSlots(core->mSlots),mConsumerName() {}// frameworks/native/libs/gui/include/gui/BufferQueueConsumer.h// acquireBuffer尝试获取BufferQueue中的下一个pending的buffer的使用权,如果没有pending的buffer就返回NO_BUFFER_AVAILABLE。 如果一个buffer被成功的获取到,将会返回一个包含buffer相关的信息的BufferItem。
virtual status_t acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber = 0) override;// releaseBuffer从消费者处释放一个buffer slot到BufferQueue中。releaseBuffer调用时有可能还在访问buffer的内容。
// 当buffer不再使用的时候,fence将会发出信号。
virtual status_t releaseBuffer(int slot, uint64_t frameNumber, const sp<Fence>& releaseFence, EGLDisplay display, EGLSyncKHR fence);
当完成BLASTBufferQueue
的创建之后,通过update
方法更新持有的SurfaceControl
变量,将其指向新的SurfaceControl
对象。
// frameworks/native/libs/gui/BLASTBufferQueue.cpp
void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, int32_t format) {std::lock_guard _lock{mMutex};if (mFormat != format) {mFormat = format;mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));}const bool surfaceControlChanged = !SurfaceControl::isSameSurface(mSurfaceControl, surface);bool applyTransaction = false;// 更新持有的SurfaceControl变量mSurfaceControl = surface;SurfaceComposerClient::Transaction t;if (surfaceControlChanged) {t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure, layer_state_t::eEnableBackpressure);applyTransaction = true;}mTransformHint = mSurfaceControl->getTransformHint();mBufferItemConsumer->setTransformHint(mTransformHint);ui::Size newSize(width, height);if (mRequestedSize != newSize) {mRequestedSize.set(newSize);mBufferItemConsumer->setDefaultBufferSize(mRequestedSize.width, mRequestedSize.height);if (mLastBufferInfo.scalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {// If the buffer supports scaling, update the frame immediately since the client may// want to scale the existing buffer to the new size.mSize = mRequestedSize;if (mUpdateDestinationFrame) {t.setDestinationFrame(mSurfaceControl, Rect(newSize));applyTransaction = true;}}}if (applyTransaction) {// All transactions on our apply token are one-way. See comment on mAppliedLastTransactiont.setApplyToken(mApplyToken).apply(false, true);}
}
Surface的创建
在创建完BLASTBufferQueue
之后,会通过新创建的BLASTBufferQueue
对象创建一个Surface
对象,并用新的Surface
对象更新ViewRootImpl#mSurface
,主要是将内部持有的native层的句柄值更新为BLASTBufferQueue
对象创建的native的句柄值。
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks, AttachedSurfaceControl {final W mWindow; // mWindow = new W(this);public final Surface mSurface = new Surface();private final SurfaceControl mSurfaceControl = new SurfaceControl();private BLASTBufferQueue mBlastBufferQueue;private boolean mUseBLASTAdapter;private boolean mForceDisableBLAST;// ...void updateBlastSurfaceIfNeeded() {if (!mSurfaceControl.isValid()) {return;}// 如果对应的native层的SurfaceControl对象是同一个,则直接更新即可,不需要重新创建BLASTBufferQueueif (mBlastBufferQueue != null && mBlastBufferQueue.isSameSurfaceControl(mSurfaceControl)) {mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);return;}// 如果更新了SurfaceControl,那么销毁并重建BBQ(BLASTBufferQueue)来重置BufferQueue及BLASTBufferQueue的状态.if (mBlastBufferQueue != null) {mBlastBufferQueue.destroy();}mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);// 通过新创建的BLASTBufferQueue对象创建Surface对象,并用新的Surface对象更新mSurfaceSurface blastSurface = mBlastBufferQueue.createSurface();// Only call transferFrom if the surface has changed to prevent inc the generation ID and// causing EGL resources to be recreated.mSurface.transferFrom(blastSurface);}// ...
}
可以看到,最终还是BLASTBufferQueue
调用nativeGetSurface
到了native层去创建Surface
对象,这里Surface
对象就持有了BLASTBufferQueue
的mProducer
,这样就可以通过Surface
访问BBQBufferQueueProducer
,然后通过BBQBufferQueueProducer
访问BufferQueueCore
,最终实现Buffer
的访问使用。
public final class BLASTBufferQueue {/*** @return a new Surface instance from the IGraphicsBufferProducer of the adapter.*/public Surface createSurface() {return nativeGetSurface(mNativeObject, false /* includeSurfaceControlHandle */);}private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
}// frameworks/base/core/jni/android_graphics_BLASTBufferQueue.cpp
static jobject nativeGetSurface(JNIEnv* env, jclass clazz, jlong ptr, jboolean includeSurfaceControlHandle) {sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);return android_view_Surface_createFromSurface(env, queue->getSurface(includeSurfaceControlHandle));
}// frameworks/native/libs/gui/BLASTBufferQueue.cpp
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {std::lock_guard _lock{mMutex};sp<IBinder> scHandle = nullptr;if (includeSurfaceControlHandle && mSurfaceControl) {scHandle = mSurfaceControl->getHandle();}return new BBQSurface(mProducer, true, scHandle, this);
}// frameworks/native/libs/gui/BLASTBufferQueue.cpp
public:BBQSurface(const sp<IGraphicBufferProducer>& igbp, bool controlledByApp, const sp<IBinder>& scHandle, const sp<BLASTBufferQueue>& bbq) : Surface(igbp, controlledByApp, scHandle), mBbq(bbq) {}// frameworks/native/libs/gui/Surface.cpp
Surface::Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controlledByApp, const sp<IBinder>& surfaceControlHandle): mGraphicBufferProducer(bufferProducer),mCrop(Rect::EMPTY_RECT),mBufferAge(0),mGenerationNumber(0),mSharedBufferMode(false),mAutoRefresh(false),mAutoPrerotation(false),mSharedBufferSlot(BufferItem::INVALID_BUFFER_SLOT),mSharedBufferHasBeenQueued(false),mQueriedSupportedTimestamps(false),mFrameTimestampsSupportsPresent(false),mEnableFrameTimestamps(false),mFrameEventHistory(std::make_unique<ProducerFrameEventHistory>()) {// Initialize the ANativeWindow function pointers.ANativeWindow::setSwapInterval = hook_setSwapInterval;ANativeWindow::dequeueBuffer = hook_dequeueBuffer;ANativeWindow::cancelBuffer = hook_cancelBuffer;ANativeWindow::queueBuffer = hook_queueBuffer;ANativeWindow::query = hook_query;ANativeWindow::perform = hook_perform;ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED;ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED;ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED;ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED;const_cast<int&>(ANativeWindow::minSwapInterval) = 0;const_cast<int&>(ANativeWindow::maxSwapInterval) = 1;// ...
}
总结
首先,当App
进程的View
的测量数据发生变化时,会导致窗体大小发生变化,此时会调用ViewRootImpl#relayoutWindow
请求SystemServer
进程的WindowManagerService
更新窗体的大小,SystemServer
进程会做如下工作:
SystemServer
进程通过WindowManagerService
创建Java层的SurfaceControl
对象,对应App
进程中Java层的SurfaceControl
对象;SystemServer
进程创建的Java层的SurfaceControl
对象是一个壳,其内部会通过JNI
调用到native层,创建native层的SurfaceControl
对象,并将native层的SurfaceControl
对象的句柄值拷贝到App
进程的SurfaceControl
对象;- 在native层的
SurfaceControl
对象创建的过程中会通过SurfaceComposerClient
请求到SurfaceFlinger
进程,调用Client::createSurface
创建Layer
对象,并将Layer
对象的关键信息返回给SystemServer
进程,并用于构造native层的SurfaceControl
对象;
接着,当App
进程的Java层的SurfaceControl
对象更新了native层的SurfaceControl
对象之后,便会创建Java层的BLASTBufferQueue
对象,同样地,Java层的BLASTBufferQueue
对象会触发native层的BLASTBufferQueue
对象的创建,native层BLASTBufferQueue
对象的创建会做如下工作:
- 创建
BufferQueueCore
并通过BufferQueueCore
创建IGraphicBufferProducer
(BBQBufferQueueProducer
)以及BufferQueueConsumer
,并被BLASTBufferQueue
对象持有; - 调用
BufferQueueCore#update
方法将BufferQueueCore
持有的SurfaceControl
变量指向之前创建的新SurfaceControl
对象;
最后,当SurfaceControl
和BLASTBufferQueue
都创建完成之后,通过BLASTBufferQueue
对象创建一个native层的Surface
对象(持有了BLASTBufferQueue
的IGraphicBufferProducer
),并将其赋值给App
进程的ViewRootImpl#mSurface
持有的句柄值,最终实现通过Surface
访问BBQBufferQueueProducer
,然后通过BBQBufferQueueProducer
访问BufferQueueCore
,最终对Buffer
进行访问使用。
相关文章:
浅析Android View绘制过程中的Surface
前言 在《浅析Android中View的测量布局流程》中我们对VSYNC信号到达App进程之后开启的View布局过程进行了分析,经过对整个App界面的View树进行遍历完成了测量和布局,确定了View的大小以及在屏幕中所处的位置。但是,如果想让用户在屏幕上看到…...
基于卷积神经网络的大豆种子缺陷识别系统,resnet50,mobilenet模型【pytorch框架+python源码】
更多目标检测和图像分类识别项目可看我主页其他文章 功能演示: 大豆种子缺陷识别系统,卷积神经网络,resnet50,mobilenet【pytorch框架,python源码】_哔哩哔哩_bilibili (一)简介 基于卷积神…...
HarmonyOS项目开发一多简介
目录 一、布局能力概述 二、自适应布局 三、响应式布局 四、典型布局场景 一、布局能力概述 布局决定页面元素排布及显示:在页面设计及开发中,布局能力至关重要,主要通过组件结构来确定使用何种布局。 自适应布局与响应式布局࿱…...
C++基础三
构造函数 构造函数(初始化类成员变量): 1、属于类的成员函数之一 2、构造函数没有返回类型 3、构造函数的函数名必须与类名相同 4、构造函数不允许手动调用(不能通过类对象调用) 5、构造函数在类对象创建时会被自动调用 6、如果没有显示声…...
利用ChatGPT完成2024年MathorCup大数据挑战赛-赛道A初赛:台风预测与分析
利用ChatGPT完成2024年MathorCup大数据挑战赛-赛道A初赛:台风预测与分析 引言 在2024年MathorCup大数据挑战赛中,赛道A聚焦于气象数据分析,特别是台风的生成、路径预测、和降水风速特性等内容。本次比赛的任务主要是建立一个分类评价模型&…...
Linux系统操作篇 one -文件指令及文件知识铺垫
Linux操作系统入门-系统篇 前言 Linux操作系统与Windows和MacOS这些系统不同,Linux是黑屏的操作系统,操作方式使用的是指令和代码行来进行,因此相对于Windows和MacOS这些带有图形化界面的系统,Linux的入门门槛和上手程度要更高&…...
隨筆20241028 ISR 的收缩与扩展及其机制解析
在 Kafka 中,ISR(In-Sync Replicas) 是一组副本,它们与 Leader 保持同步,确保数据一致性。然而,ISR 的大小会因多种因素而变化,包括收缩和扩展。以下是 ISR 收缩与扩展的详细解释及其背后的机制…...
linux-字符串相关命令
1、cut 提取文件每一行中的内容 下面是一些常用的 cut 命令选项的说明: -c, --characters列表:提取指定字符位置的数据。-d, --delimiter分界符:指定字段的分隔符,默认为制表符。-f, --fieldsLIST:提取指定字段的数据…...
ES6 函数的扩展
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法 ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面 参数变量是默认声明的,所以不能用 let 或 const 再次声明 使用参数默认值时,函数不能有同名参…...
Mac 查看占用特定端口、终止占用端口的进程
在 macOS 上,可以使用以下命令来查看占用特定端口(例如 8080)的进程: lsof -i :8080命令说明 lsof:列出打开的文件和网络连接信息。-i :8080:筛选出正在监听 8080 端口的进程。 输出结果结构 执行上述命…...
C#入坑JAVA MyBatis入门 CURD 批量 联表分页查询
本文,分享 MyBatis 各种常用操作,不限于链表查询、分页查询等等。 1. 分页查询 在 下文的 的「3.4 selectPage」小节,我们使用 MyBatis Plus 实现了分页查询。除了这种方式,我们也可以使用 XML 实现分页查询。 这里,…...
RabbitMQ 安装(Windows版本)和使用
安装 安装包获取 可以自己找资源,我这里也有百度云的资源,如果没失效的话可以直接用。 通过百度网盘分享的文件:RabbitMQ 链接:https://pan.baidu.com/s/1rzcdeTIYQ4BqzHLDSwCgyw?pwdfj79 提取码:fj79 安装教程…...
Apache paimon表管理
表管理 2.9.4.1 管理快照 1)快照过期 Paimon Writer每次提交都会生成一个或两个快照。每个快照可能会添加一些新的数据文件或将一些旧的数据文件标记为已删除。然而,标记的数据文件并没有真正被删除,因为Paimon还支持时间旅行到更早的快照。它们仅在快照过期时被删除。 …...
java 第19天
一.Lambda表达式 前提是:参数是函数式接口才可以书写Lambda表达式 函数式接口条件: 1.接口 2.只有一个抽象方法 lambda表达式又称为匿名函数,允许匿名函数以参数的形式传入方法,简化代码 lambda表达式分为两部分()->{} …...
什么是服务器?服务器与客户端的关系?本地方访问不了网址与服务器访问不了是什么意思?有何区别
服务器是一种高性能的计算机,它通过网络为其他计算机(称为客户端)提供服务。这些服务可以包括文件存储、打印服务、数据库服务或运行应用程序等。服务器通常具有强大的处理器、大量的内存和大容量的存储空间,以便能够处理多个客户…...
Spring(1)—Spring 框架:Java 开发者的春天
一、关于Spring 1.1 简介 Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。它提供了全面的基础设施支持,使开发者…...
MT1401-MT1410 码题集 (c 语言详解)
目录 MT1401归并排序 MT1402堆排序 MT1403后3位排序 MT1404小大大小排序 MT1405小大大小排序II MT1406数字重排 MT1407插入 MT1408插入 MT1409旋转数组 MT1410逆时针旋转数组 MT1401归并排序 c 语言实现代码 #include <stdio.h>// merge two subarrays void merge(int a…...
React基础语法
1.React介绍 React由Meta公司开发,是一个用于构建Web和原生交互界面的库 1.1 React优势 相较于传统基于DOM开发的优势 1.组件化的开发方式 2.不错的性能 相较于其他前端框架的优势 1.丰富的生态 2.跨平台支持 1.2React的时长情况 全球最流行,大厂…...
《Kadane‘s Algorithm专题:最大和连续子数组》
🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷…...
Vue基础(5)
ref属性 在 Vue2 中,ref是一个特殊的属性,用于在模板中获取对某个 DOM 元素或子组件的引用。通过 ref,我们可以在 JavaScript 代码中直接访问该 DOM 元素或组件实例。 示例: <template><div><input ref"inputField&quo…...
面对复杂的软件需求:5大关键策略!
面对软件需求来源和场景的复杂性,有效地管理和处理需求资料是确保项目成功的关键,能够提高需求理解的准确性,增强团队协作和沟通,降低项目风险,提高开发效率。反之,项目可能面临需求理解不准确、团队沟通不…...
使用Git进行版本控制的最佳实践
文章目录 Git简介基本概念仓库(Repository)提交(Commit)分支(Branching) 常用命令初始化仓库添加文件提交修改查看状态克隆仓库分支操作合并分支推送更改 最佳实践使用有意义的提交信息定期推送至远程仓库使…...
【入门1】顺序结构 - B2025 输出字符菱形
题目描述 用 * 构造一个对角线长 55 个字符,倾斜放置的菱形。 输入格式 没有输入要求。 输出格式 如样例所示。用 * 构成的菱形。 输入输出样例 输入 #1 输出 #1**** ********* <C> : #include<stdio.h>int main() {printf(" *\n ***\n**…...
C#DLL热加载|动态替换
我有一个项目 开始取数据和结束数据部分是一样的,但中间处理数据是根据客户需求来转换的 又要求增加一个客户数据转换 主程序是不能停下来的 所以这个项目转数据转换部分做成插件式 每个客户的数据转换都是一个项目 都是一个DLL 主程序里面定义好接口类或者抽象…...
数据库三大范式
目录 第一范式(1NF) 第二范式(2NF) 第三范式(3NF) Oracle三大范式是数据库设计中的规范化过程,旨在减少数据冗余、提高数据一致性和数据库性能。这三大范式包括第一范式(1NF)、第二范式(2NF)和第三范式(3NF)。 第一范式(1NF) 数据库表的每一列都是不可分割…...
【linux】fdisk磁盘分区管理
介绍 fdisk是一个磁盘分区管理工具,可以用来创建、删除、修改和查看磁盘分区。 fdisk一般都是交互式使用,基础语法: fdisk /dev/sdd。进入交互窗口后,有一些选项,需要了解下: 选项含义n创建新分区p查看磁盘的分区情…...
asp.net core 入口 验证token,但有的接口要跳过验证
asp.net core 入口 验证token,但有的接口要跳过验证 在ASP.NET Core中,你可以使用中间件来验证token,并为特定的接口创建一个属性来标记是否跳过验证。以下是一个简化的例子: 创建一个自定义属性来标记是否跳过验证: public clas…...
[mysql]聚合函数GROUP BY和HAVING的使用和sql查询语句的底层执行逻辑
#GROUP BY的使用 还是先从需求出发,我们现在想求员工表里各个部门的平均工资,最高工资 SELECT department_id,AVG(salary) FROM employees GROUP BY department_id 我们就会知道它会把一样的id分组,没有部门的就会分为一组,我们也可以用其他字段来分组,我们想查询不同jb_id…...
从数据中台到数据飞轮:实现数据驱动的升级之路
从数据中台到数据飞轮:实现数据驱动的升级之路 随着数字化转型的推进,数据已经成为企业最重要的资产之一,企业普遍搭建了数据中台,用于整合、管理和共享数据;然而,近年来,数据中台的风潮逐渐减退…...
小记:SpringBoot中,@Alisa和@ApiModelProperty的区别
在 Spring Boot 中,Alias和ApiModelProperty 这两个注解用于不同的目的。 Alias Alias是一个用于定义别名的注解,通常用于 Bean 属性的别名功能,这样在使用某些框架(如 JPA 或 Jackson)时,可以将一个属性名…...
定制小程序网站开发公司/cpu优化软件
全球领先的企业级和移动软件公司Sybase, Inc. (NYSE: SY)近日宣布,旗下最新一代著名的开发工具—PowerBuilder 12正式面市。PowerBuilder一直备受赞誉,其最新版本使开发者能更便捷、快速且高效地在Microsoft® .NET架构上构建或迁移业务应用软件&…...
如何做网站服务器/网站优化软件哪个好
下面请一字一句地看,一遍就设置成功,比你设置几十遍失败,费时会少得多。 首先,在连接数据库之前必须保证SQL Server 2012是采用SQL Server身份验证方式而不是windows身份验证方式。如果在安装时选用了后者,则重新设置如…...
借贷网站建设/互联网推广是什么意思
hdfs常用命令,可查看博文 hdfs常用命令 //从本地加载数据到表,linux上的文件不会丢失,相当于是复制 ,这是 追加的模式 load data local inpath /data/log/1.txt into table employee; //从本地加载数据到表,linux上的文件不会丢失,相当于是复…...
抖音电商/seo查询是什么
# 开始绘图 fig, ax1 plt.subplots() fig.set_size_inches(12, 6) plt.set_cmap(RdBu) # multiple line plot x np.arange(featureValuedf.shape[1]) # x坐标的范围 lw 4 # 控制线条的粗细 a, ax1.plot(x, featureValuedf.loc[Back].to_numpy(),linewidthlw, labelBack, ma…...
深圳住建厅官方网站/web网页制作成品免费
广义表是数据结构中非常关键的一部分,它的学习对于树和二叉树有很大的起承作用。那么,它是怎么实现的呢?广义表的实现应用到了一个很熟悉的算法——递归。来看看它的代码吧!#pragma once #include<iostream> #include<ca…...
酒店网站建设的重要性/营销方案推广
使用监听器:定时清除map缓存的key value . 配置web.xml:注意位置 <listener> <listener-class> com.my.common.listener.TimerListener </listener-class> </listener> 监听类: public class TimerListener implements Serv…...