Android Framework之Surface

基础概念梳理

个人理解

在android中编写的view,最终需要通过OpenGl Es/Skia(Q以后使用Skia)向物理屏幕输出,最终显示我们要显示的画面。

在这个过程中,android 需要提供一块画布,让我们可以编写自己要显示的东西,这里面就涉及到SF(SurfaceFlinger) 和Surface,FrameBuffer等。 FrameBuffer 则是帧缓存驱动,就是系统特地为显示保留的一块内存, 输出需要显示的内容就往这里写,屏幕需要显示的时候,也是从这里取,一般数量为2,也就是双缓冲,也有三缓冲。

Surface到底是什么? 对应内存里的一块区域,由SF分配,app会从SF那获取一块GraphicBuffer,通过OpenGL/Skia 将图形绘制到GraphicBuffer上,SF会把各个应用的GraphicBuffer进行合成,最终通过SF 输出到屏幕上。 简单说就是SF的给的一块内存。

正常一个页面,顶部状态栏是一个buffer,底部导航栏是一个,中间的页面是一个,最终他们会被合成为一帧画面。

基础类和方法

ViewRoot中的Surface 创建过程 涉及如下几个类和类里参数

ViewRootImp.java
属性
//空的构造 其实只是个占位后期需要copyFrom才能真正赋值 准确说是个壳子
public final Surface mSurface = new Surface();
//空构造 无用 后期也是赋值过来 
private final SurfaceControl mSurfaceControl = new SurfaceControl()
// mWindowSession 实际是Session的一个aidl的代理类  在当前类是唯一的 整个app有且只有一个
final IWindowSession mWindowSession;
//构造函数会调用到nativeCreate 会调用android_view_SurfaceSession.cpp里面的nativeCreate 是SF在C端的引用
private final SurfaceSession mSurfaceSession = new SurfaceSession();final W mWindow;
注意点
  • 为什么app里只有一个Session

    //构造函数获取Session
    public ViewRootImpl(Context context, Display display) {
            this(context, display, WindowManagerGlobal.getWindowSession(),
                    false /* useSfChoreographer */);
        }WindowManagerGlobal.java -->getWindowSession()
      //单例模式 跨进程 从WindowManagerService 获取 WindowManagerService 是继承实现了 IWindowManager.Stub
      public static IWindowSession getWindowSession() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowSession == null) {
                    try {
                     InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                        IWindowManager windowManager = getWindowManagerService();
                        sWindowSession = windowManager.openSession(
                                new IWindowSessionCallback.Stub() {
                                    @Override
                                    public void onAnimatorScaleChanged(float scale) {
                                        ValueAnimator.setDurationScale(scale);
                                    }
                                });
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
                return sWindowSession;
            }
        }
        
    
    
方法

入口

setView 会调用如下两个方法

requestLayout();->scheduleTraversals();->doTraversal()->  performTraversals()->
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
-->
  //这里调用了Session.relayout 传递参数给Session
  //注意这里的params 和宽高都传递过去了 因为在SF那边图层 需要宽高和format 类型 比如argb888之类的
  //实际调用是wms的relayoutWindow
  int relayoutResult = mWindowSession.relayout(mWindow, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,           frameNumber,
                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize);//这里是个重点 将mSurfaceControl里面的mNativeObject copy 过来 所以其实surface就是空壳子
    mSurface.copyFrom(mSurfaceControl);

//调用Session的addToDisplayAsUser  实际是调用了wms的addWindow
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                            mTempControls);

Surface.java
copyFrom()
    @UnsupportedAppUsage
    public void copyFrom(SurfaceControl other) {
        if (other == null) {
            throw new IllegalArgumentException("other must not be null");
        }long surfaceControlPtr = other.mNativeObject;
        if (surfaceControlPtr == 0) {
            throw new NullPointerException(
                    "null SurfaceControl native object. Are you using a released SurfaceControl?");
        }
        long newNativeObject = nativeGetFromSurfaceControl(mNativeObject, surfaceControlPtr);
        updateNativeObject(newNativeObject);
    }
Session.java
relayout
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
        int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
        ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
        SurfaceControl outSurfaceControl, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
    int res = mService.relayoutWindow(this, window, attrs,
            requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
            outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
            outActiveControls, outSurfaceSize);return res;
}

addToDisplayAsUser
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls) {
  //wms 添加window
    return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
            requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
}

windowAddedLocked
void windowAddedLocked() {
    if (mPackageName == null) {
        final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid);
        if (wpc != null) {
            mPackageName = wpc.mInfo.packageName;
            mRelayoutTag = "relayoutWindow: " + mPackageName;
        } else {
            Slog.e(TAG_WM, "Unknown process pid=" + mPid);
        }
    }
    if (mSurfaceSession == null) {
        if (DEBUG) {
            Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
        }
      //创建新 的SurfaceSession  在他的构造函数里会和native 通信
        mSurfaceSession = new SurfaceSession(); 
        ProtoLog.i(WM_SHOW_TRANSACTIONS, "  NEW SURFACE SESSION %s", mSurfaceSession);
      //wms里面的 mSessions 开始管理该Session
        mService.mSessions.add(this);
        if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
            mService.dispatchNewAnimatorScaleLocked(this);
        }
    }
  // 窗口数量增加
    mNumWindow++;
}

WindowManagerService.java
addWindow()
//核心代码 创建WindowState 并调用 attach
final WindowState win = new WindowState(this, session, client, token, parentWindow,
        appOp[0], attrs, viewVisibility, session.mUid, userId,
        session.mCanAddInternalSystemWindow);...
 win.attach();

relayoutWindow
//申请宽高
if (viewVisibility != View.GONE) {  win.setRequestedSize(requestedWidth, requestedHeight);}
attrChanges = win.mAttrs.copyFrom(attrs);
//将viewRootimp 里面的 outSurfaceControl 传入方法开始创建
  result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
createSurfaceControl
//调用winAnimator 
surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);
// copyFrom 赋值给viewRootImp里的SurfaceControl
  surfaceController.getSurfaceControl(outSurfaceControl);

WindowState.java
attach()
void attach() {
    if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
    mSession.windowAddedLocked();
}

SurfaceSession.java
构造函数
public SurfaceSession() {
    mNativeClient = nativeCreate();
}

WindowStateAnimator.java
createSurfaceLocked
//如果不为null 则直接返回 
if (mSurfaceController != null) {
    return mSurfaceController;
}
//否则创建
  mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
                    height, format, flags, this, windowType);

WindowSurfaceController.java
构造函数(String name, int w, int h, int format,
    int flags, WindowStateAnimator animator, int windowType)

//会调用到SurfaceControl里的build()     
final SurfaceControl.Builder b = win.makeSurface()
                .setParent(win.getSurfaceControl())
                .setName(name)
                .setBufferSize(w, h)
                .setFormat(format)
                .setFlags(flags)
                .setMetadata(METADATA_WINDOW_TYPE, windowType)
                .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
                .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
                .setCallsite("WindowSurfaceController");final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags
                & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);if (useBLAST) {
            b.setBLASTLayer();
        }
​
        mSurfaceControl = b.build();

SurfaceControl.java
mNativeObject

这里的mNativeObject 其实指向的就是SF给开辟的内存地址,后期会赋值给ViewRootImp里的surface

多构造参数方法
mNativeObject = nativeCreate(session, name, w, h, format, flags,
    parent != null ? parent.mNativeObject : 0, metaParcel);

build()
public SurfaceControl build() {
            if (mWidth < 0 || mHeight < 0) {
                throw new IllegalStateException(
                        "width and height must be positive or unset");
            }
            if ((mWidth > 0 || mHeight > 0) && (isEffectLayer() || isContainerLayer())) {
                throw new IllegalStateException(
                        "Only buffer layers can set a valid buffer size.");
            }if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) {
                setBLASTLayer();
            }return new SurfaceControl(
                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata,
                    mLocalOwnerView, mCallsite);
        }

getSurfaceControl
void getSurfaceControl(SurfaceControl outSurfaceControl) {
    outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
}

android_view_surfaceControl.cpp
nativeCreate()
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
        jobject metadataParcel) {
    ScopedUtfChars name(env, nameStr);
    sp<SurfaceComposerClient> client;
  //创建sf 在C端的代理
    if (sessionObj != NULL) {
      //从session拿 其实调用的是android_view_SurfaceSession.cpp里面的mNativeClient
      //然后从SurfaceSession 获取 mNativeClient  其实就是一个 SurfaceComposerClient
        client = android_view_SurfaceSession_getClient(env, sessionObj);
    } else {
      //获取默认的cliden 返回一个 SurfaceComposerClient
        client = SurfaceComposerClient::getDefault();
    }
    SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
    sp<SurfaceControl> surface;
    LayerMetadata metadata;
    Parcel* parcel = parcelForJavaObject(env, metadataParcel);
    if (parcel && !parcel->objectsCount()) {
        status_t err = metadata.readFromParcel(parcel);
        if (err != NO_ERROR) {
          jniThrowException(env, "java/lang/IllegalArgumentException",
                            "Metadata parcel has wrong format");
        }
    }
​
    sp<IBinder> parentHandle;
    if (parent != nullptr) {
        parentHandle = parent->getHandle();
    }//sf 创建Surface
    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, NULL);
        return 0;
    }
​
    surface->incStrong((void *)nativeCreate);
    return reinterpret_cast<jlong>(surface.get());
}

android_view_SurfaceSession.cpp
nativeCreate()
static jlong nativeCreate(JNIEnv* env, jclass clazz) {
    SurfaceComposerClient* client = new SurfaceComposerClient();
    client->incStrong((void*)nativeCreate);
    return reinterpret_cast<jlong>(client);
}

register_android_view_SurfaceSession
//其实从SurfaceSession.java去获取mNativeClient  赋值给 自己内部的mNativeClient
int register_android_view_SurfaceSession(JNIEnv* env) {
    int res = jniRegisterNativeMethods(env, "android/view/SurfaceSession",
            gMethods, NELEM(gMethods));
    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
​
    jclass clazz = env->FindClass("android/view/SurfaceSession");
    gSurfaceSessionClassInfo.mNativeClient = env->GetFieldID(clazz, "mNativeClient", "J");
    return 0;
}

SurfaceComposerClient.cpp
createSurfaceChecked()
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
                                                     PixelFormat format,
                                                     sp<SurfaceControl>* outSurface, uint32_t flags,
                                                     const sp<IBinder>& parentHandle,
                                                     LayerMetadata metadata,
                                                     uint32_t* outTransformHint) {
    sp<SurfaceControl> sur;
    status_t err = mStatus;if (mStatus == NO_ERROR) {
        sp<IBinder> handle;
        sp<IGraphicBufferProducer> gbp;
​
        uint32_t transformHint = 0;
        int32_t id = -1;
         //创建surface
        err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
                                     &handle, &gbp, &id, &transformHint);if (outTransformHint) {
            *outTransformHint = transformHint;
        }
        ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err));
        if (err == NO_ERROR) {
            *outSurface =
                    new SurfaceControl(this, handle, gbp, id, w, h, format, transformHint, flags);
        }
    }
    return err;
}

构造函数
SurfaceComposerClient::SurfaceComposerClient()
    : mStatus(NO_INIT)
{
}

onFirstRef
//构造调用
void SurfaceComposerClient::onFirstRef() {
    //这里就是binder 调用 SF
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if (sf != nullptr && mStatus == NO_INIT) {
      //跨进程调用SF的createConnection
        sp<ISurfaceComposerClient> conn;
        conn = sf->createConnection(); //调用到 SurfaceFlinger 的 createConnection
        if (conn != nullptr) {
            mClient = conn;
            mStatus = NO_ERROR;
        }
    }
}

总体串联

串联Activity->ViewRootImp
ActivityThread.handleResumeActivity()
-->wm.addView(decor, l);
-->WindowManagerImp.addView()
-->mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
-->WindowManagerGlobal.addView()
-->  root = new ViewRootImpl(view.getContext(), display);//创建ViewRootImo
-->root.setView()
...
surface的创建流程
ViewRootImp-->
//(详情看ViewRootImp的方法和属性)
-->setView()
   --> requestLayout();
      -->...doTraversal()-->performTraversals();
      --> relayoutResult = relayoutWindow(mWindow, params,widhth,height..mSurfaceControl)
        //将内部surfaceControl,宽高,参数 传递给wms
      -->mWindowSession.relayout(...)
      -->Aidl调用Session.relayout(...)
       -->WindowManagerService.relayoutWindow(...attrs,..outSurfaceControl,)
       --> createSurfaceControl(outSurfaceControl, result, win, winAnimator)
       -->surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);
          -->WindowStateAnimator.createSurfaceLocked//WindowStateAnimator 对应方法
           --> 不为null返回mSurfaceController 
             -->null调用 返回 new WindowSurfaceController() 
               --> 调用SurfaceControl.Builder.build()
               --> new SurfaceControl(
                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata,
                    mLocalOwnerView, mCallsite);
               -->  mNativeObject = nativeCreate(session, name, w, h, format, flags,
                    parent != null ? parent.mNativeObject : 0, metaParcel);
                   -->android_view_SurfaceControl.cpp.nativeCreate()
                     -->client->createSurfaceChecked()//具体看android_view_SurfaceControl.cpp
                     --> 返回
        -->surfaceController.getSurfaceControl(outSurfaceControl)//c++层创建的mSurfaceControl   拷贝到ViewTRootImp 传递过来的outSurfaceControl
         
     
---------------------------------------------------------
   --> mWindowSession.addToDisplayAsUser()
       -->Aidl调用Session.addToDisplayAsUser()
       --> mService.addWindow()
       --> 创建windowState 调用attach() 
       -->Session.windowAddedLocked();//将surfaceSession 创建 并 wms管理当前Session 也就是当前app唯一的Session
       --> mSurfaceSession = new SurfaceSession();
           mService.mSessions.add(this);
           mNumWindow++;
          --> SurfaceSession 构造函数   mNativeClient = nativeCreate();
          -->android_view_SurfaceSession.cpp  -->nativeCreate()//创建SurfaceComposerClient 返回 可以理解为 SF的在APP端的代理  这里会有疑问,为什么这个代码在下面,因为上面的 performTraversals 需要监听到 SF 的callBack 才会执行,这会就已经初始化完成了
           
    -->     
        

SF在C端的创建流程

SF的创建在 SurfaceSession 的构造函数里,Session就是会话的意思

android_view_SurfaceSession.cpp
  -->nativeCreate()
  ---> SurfaceComposerClient* client = new SurfaceComposerClient();...
   --> 首次会调用 SurfaceComposerClient的onFirstRef 方法  调用sf->createConnection()
   --> SurFaceFlinger.createConnection()--> 创建一个 Client 返回
   sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
    const sp<Client> client = new Client(this);
    return client->initCheck() == NO_ERROR ? client : nullptr;
}

view 如何转化到屏幕上的?

核心类
  • ThreadedRenderer
ViewRootImp.draw()
->mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
-->ThreadRenderer.draw()
   -->updateRootDisplayList(view, callbacks);
       --> updateViewTreeDisplayList(view);
       --> view.updateDisplayListIfDirty();//渲染自己的Node View方法 最终返回RenDerNode
          --> if (renderNode.hasDisplayList(){  dispatchGetDisplayList(); //遍历子节点 ViewGroup }
                                              
   -->registerAnimatingRenderNode()//方法注册动画 Render Node
   -->syncAndDrawFrame()//通知RenderThread 渲染下一帧

  • RecordingCanvas
//真正的绘制 RecordingCanvas  绘图指令员
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
                final int saveCount = canvas.save();
                canvas.translate(mInsetLeft, mInsetTop);
                callbacks.onPreDraw(canvas);
​
                canvas.enableZ();
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                canvas.disableZ();
​
                callbacks.onPostDraw(canvas);
                canvas.restoreToCount(saveCount);
                mRootNodeNeedsUpdate = false;
            } finally {
                mRootNode.endRecording();
            }
        }

  • RenderNode

会包含DisPlayList ,DisPlayList里面也可能包含RenderNode,套娃。

真正的绘制者
performTraversals()-->performDraw();
-->....-> CanvasContext.draw()
  • CanvasContext.cpp
Frame frame = mRenderPipeline->getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
​
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                  mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                  &(profiler()));

  • 总结

    Surface 拥有一个buffer,但是buffer不能直接给屏幕用,就需要提交给SF的队列,SF处理后再给屏幕。 在这个过程中,屏幕用完这个buffer 会再次还给SF,避免消耗。

常见问题题梳理

  • 图层面板是什么?

android 里每个Activity都有一个独立的画布,应用端叫surface 在

SF里叫layer, 无论多么复杂的view结构 其实都是画在了所在的Activity上。

  • 为什么设计成C/S架构

避免崩溃 影响系统服务。

  • buffer是什么

buffer缓存了图形的内存空间,被surface管理

  • Surface 对其他逻辑的影响

Surface画布的影响 到Window窗口的大小,影响wms 服务的控制大小,(更新window)

如何学习Framework?

由于许多Android开发者日常工作主要集中在业务层面,大量时间用于编写基础代码、应用现成框架,导致对底层技术如Framework、Handler源码、Binder机制等了解不足,仅停留在表面认知阶段。

为了帮助大家学习Framework,特地准备了一份超级详细的Android Framework内核源码知识体系图解,以及配套的《Android Framework源码开发解析》学习笔记,旨在引导大家系统性地攻克Android Framework领域的核心技术,从而提升自身的竞争力,从容应对金三银四的求职挑战。

【有需要的朋友,扫描下方二维码即可领取!!】👇👇

在这里插入图片描述

《Android Framework源码开发揭秘》

第一章 系统启动流程分析
  • 第一节 Android启动概括
  • 第二节 init.rc解析
  • 第三节 Zygote
  • 第四节 面试题
    在这里插入图片描述
第二章 跨进程通信IPC解析
  • 第一节 Service还可以这么理解
  • 第二节 Binder基础
  • 第三节 Binder应用
  • 第四节 AIDL应用(上)
  • 第五节 AIDL应用(下)
  • 第六节 Messenger原理及应用
  • 第七节 服务端回调
  • 第八节 获取服务(IBinder)
  • 第九节 Binder面试题全解析
    在这里插入图片描述
第三章 Handler源码解析
  • 第一节 源码分析
  • 第二节 难点问题
  • 第三节 Handler常问面试题在这里插入图片描述
第四章 AMS源码解析
  • 第一节 引言
  • 第二节 Android架构
  • 第三节 通信方式
  • 第四节 系统启动系列
  • 第五节 AMS
  • 第六节 AMS面试题解析在这里插入图片描述
第五章 WMS源码解析
  • 第一节 WMS与activity启动流程
  • 第二节 WMS绘制原理
  • 第三节 WMS角色与实例化过程
  • 第四节 WMS工作原理在这里插入图片描述
第六章 Surface源码解析
  • 第一节 创建流程及软硬件绘制
  • 第二节 双缓冲及Surface View解析
  • 第三节 Android图形系统综述在这里插入图片描述
第七章 基于Android12.0的SurfaceFlinger源码解析
  • 第一节 应用建立和SurfaceFlinger的沟通桥梁
  • 第二节 SurfaceFlinger的启动和消息队列处理机制
  • 第三节 SurfaceFlinger之VSyns(上)
  • 第四节 SurfaceFlinger之VSyns(中)
  • 第五节 SurfaceFlinger之VSyns(下)在这里插入图片描述
第八章 PKMS源码解析
  • 第一节 PKMS调用方式
  • 第二节 PKMS启动过程分析
  • 第三节 APK的扫描
  • 第四节 APK的安装
  • 第五节 PKMS之权限扫描
  • 第六节 静默安装
  • 第七节 requestPermissions源码流程解析
  • 第八节 PKMS面试题在这里插入图片描述
第九章 InputManagerService源码解析
  • 第一节 Android Input输入事件处理流程(1)
  • 第二节 Android Input输入事件处理流程(2)
  • 第三节 Android Input输入事件处理流程(3)在这里插入图片描述
第十章 DisplayManagerService源码解析
  • 第一节 DisplayManagerService启动
  • 第二节 DisplayAdepter和DisplayDevice的创建
  • 第三节 DMS部分亮灭屏流程
  • 第四节 亮度调节
  • 第五节 Proximity Sensor灭屏原理
  • 第六节 Logical Display和Physical Display配置的更新在这里插入图片描述
【有需要的朋友,扫描下方二维码即可领取!!】👇👇
  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值