View系列

news2025/1/12 12:14:59

掌握View核心知识体系,两大方向:View事件分发,自定义View。

文章目录

  • 一,基础知识
    • 1.1 页面创建
    • 1.2 页面管理
  • 二,View事件分发
    • 2.1 基本概念
    • 2.2 分发流程
    • 2.3 面试题
  • 三,View绘制
    • 3.1 measure(测量)
    • 3.2 layout(布局)
    • 3.3 draw(绘制)
  • 四,渲染机制
    • 4.1 基本概念
    • 4.2 android图形架构
    • 4.3 帧刷新概念
    • 4.4 SurfaceView与TextureView
  • 五,CPU与GPU
    • 5.1 CPU
    • 5.2 GPU
  • 总结

一,基础知识

在讲View之前,先来从源码端了解一下类。首先我们知道android中的入口在ActiivtyThread中的mian函数开始。其中有个方法performLaunchActivity(),通过activity.attach创建PhoneWindow。

1.1 页面创建

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;

        try {
            Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);


            // updatePendingActivityConfiguration() reads from mActivities to update
            // ActivityClientRecord which runs in a different thread. Protect modifications to
            // mActivities to avoid race.
            synchronized (mResourcesManager) {
                mActivities.put(r.token, r);
            }

            if (activity != null) {
             
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }

                // Activity resources must be initialized with the same loaders as the
                // application context.
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

                appContext.setOuterContext(activity);
                //attach中创建了PhoneWindow
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                        r.assistToken, r.shareableActivityToken);

     

       ...省略
    }
    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, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
  ...省略
    }

再来看看PhoneWindow的源码:其中installDecor()这个方法中创建了DecorView

    public void setContentView(View view, ViewGroup.LayoutParams params) {
        
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
       ...省略
    }
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        ...省略
   }

以上分析可以总结为页面的创建:Activity->PhoneWindow->DecorView。

继续往下走,在ActivityThread类中handleResumeActivity方法我们看到了ViewRootImpl。
(面试点:onResume方法中拿不到View的宽高,因为才开始绘制)

    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
  
        if (r.window == null && !a.mFinished && willBeVisible) {
       
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
   ...省略   
    }
   

继续往下找ViewRootImpl中的performTraversals方法中执行了三大流程。

    private void performTraversals() {
        //测量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        //位置
        performLayout(lp, mWidth, mHeight);
        //绘制
        performDraw();
    }

最终执行到了View层的方法:

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            //调用View中的measure
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

1.2 页面管理

我们知道WindowManager是负责管理页面的,具体的实现类是WindowManagerImpl 源码如下:

   //WindowManagerImpl.class
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

其中addView中又出现了WindowManagerGlobal,一直找到最终还是通过ViewRootImpl的身影。所以还是由ViewRootImpl执行的三大流程。

    //WindowManagerGlobal.class
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
    
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
          
            if (windowlessSession == null) {
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

二,View事件分发

2.1 基本概念

安卓中输入事件主要分为KeyEvent和MotionEvent两种。MotionEvent是个事件系列,分为:ACTION_DOWN,ACTION_MOVE,ACTION_UP。

输入事件的源头是位于/dev/input/下的设备节点。事件由Native层进入到Java层通过InputManagerService管理。

手指触摸屏幕时,产生了触摸信息。这个触摸信息由屏幕这个硬件产生,被系统底层驱动获取,交给Android的输入系统服务:InputManagerService,也就是IMS。IMS会对这个触摸信息进行处理,通过WMS找到要分发的window,随后发送给对应的viewRootImpl。

IMS从系统底层接收到事件之后,会从WMS中获取window信息,并将事件信息发送给对应的viewRootImpl。

流程图如下:
在这里插入图片描述
详细的过程这里不再讲了,只需要知道viewRootImpl是负责View的绘制和事件分发就行了。

ViewRootImpl

ViewRootImpl 对象是由 WMS 管理的。当创建一个新的窗口时,WMS 会创建一个新的 ViewRootImpl 对象,并将该对象与窗口关联。ViewRootImpl 对象中包含了一个 View 对象,用于表示窗口的根视图。在 ViewRootImpl 对象中,还包含了一些重要的属性和方法,用于实现 View 的绘制、事件处理和布局等功能,例如:

  • mThread:表示 ViewRootImpl 所在的线程,通常是 UI 线程。
  • mView:表示窗口的根视图。
  • mLayoutRequested:表示是否需要重新布局视图。
  • mSurface:表示窗口所在的 Surface 对象,用于绘制窗口内容。
  • performTraversals():用于执行 View 的布局、测量和绘制等操作,通常在 UI 线程中执行。
  • dispatchInputEvent():用于分发输入事件,通常在事件线程中执行。
  • invalidate():用于通知ViewRootImpl 重新绘制视图。

2.2 分发流程

核心三个方法:dispatchTouchEvent 、onInterceptTouchEvent、onTouchEvent。

(1)dispatchTouchEvent

  • 事件分发的核心方法,事件分发的逻辑都是在这个方法中实现。
  • true事件被消费不在传递,false调用父View的onTouchEvent方法。

(2)onInterceptTouchEvent

  • 方法在View中不存在,存在于ViewGroup中。在dispatchTouchEvent 中被调用。
  • true拦截事件,调用View本身的onTouchEvent,false不拦截向下分发到子View的dispatchTouchEvent。

(3)onTouchEvent

  • true消费事件,false不消费事件,调用父View的onTouchEvent方法。

在ViewGroup中的调用部分源码如下:

public boolean dispatchTouchEvent(Motion e){
     boolean result=false;
     if(onInterceptTouchEvent(e)){
     //如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()
        result=onTouchEvent(e);
     }else{
        //如果不截获那么交给其子View来分发
        result=child.dispatchTouchEvent(e);
     }
     return result;
}

流程如下图所示:

在这里插入图片描述

2.3 面试题

了解事件分发流程后,我们可以做什么,当然是处理滑动冲突了。由于开发中会涉及到各种View的嵌套,在嵌套中可能会存在滑动冲突问题。

onTouch 和onTouchEvent 的区别

onTouchEvent方法是专门用来处理事件分发的,它一定存在Activity、View和ViewGroup这三者中。onTouch方法是View设置了触摸监听事件后,需要重写的方法,是OnTouchListener接口中的方法。

各种点击事件理解

onTouch返回true就会消费手指抬起的事件,进而跳过onClick方法。onTouch返回false,OnClickListener才会收到这个事件。
优先级:OnTouchListener > OnLongClickListener > OnClickListener。

滑动冲突处理

首先理解滑动冲突的场景:

  • 外部和内部滑动方向不一致,处理办法一般根据坐标判断滑动方向。
  • 外部和内部滑动方向一致,ScrollView嵌套ListView。
  • 多层滑动嵌套引起的冲突。

不管是什么场景都有两种解决方案:

  • 外部拦截法:重写父类onInterceptTouchEvent方法进行拦截。注意:拦截ACTION_DOWN和ACTION_UP的影响(事件序列)。
  • 内部拦截法:内层控件的重写方法dispatchTouchEvent,需要结合requestDisllowInterceptTouchEvent。

requestDisallowInterceptTouchEvent

这个方法的作用是什么,我们知道一个手势的操作,会经历DOWN,MOVE,UP过程。方法源码如下:

    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

上边代码可以看到它改变了一个开关FLAG_DISALLOW_INTERCEPT,同时调用其parent的函数。

在dispatchTouchEvent方法中有这样一段代码:

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

上边代码可以看出,down事件分发过程中,如果有子view消费事件,则赋值给mFirstTouchTarget不为空,所以后续事件还会检查拦截。

因此如果down事件中没有子view消费事件,那么后续事件的拦截都为true。所以后续事件不会再遍历子View。

三,View绘制

不管是自定义View,还是View本身,绘制都会经历三大流程。measure、layout和draw。这里也可以分两种情况,单View和ViewGroup。

3.1 measure(测量)

View 会先计算自己的大小(MeasureSpec),确定自己的测量宽度(measuredWidth)和测量高度(measuredHeight)。在测量阶段中,View 会调用 onMeasure() 方法进行测量。在 onMeasure() 方法中,View 会根据自身的宽高属性和父布局的限制条件,计算出自己的测量宽度和测量高度。在计算测量宽高时,View 可能会调用子 View 的 measure() 方法,以便子 View 也能够计算自己的测量宽高。

MeasureSpec: 三种测量模式(UNSPECIFIED:不限制大小,EXACTLY:精确尺寸(dp或px)和match_parent,AT_MOST:尺寸由子View确定,对应wrap_content)。
LayoutParams: 用于描述 View 在布局中的位置和大小等信息。

测量过程:

  • View:measure()->onMeasure()->setMeasureDimension()。
  • ViewGroup:measure()->onMeasure()->measureChildren()->getChildMeasureSpec()->measureChild()->setMeasureDimension()。

3.2 layout(布局)

在布局阶段中,View 会根据自己的测量宽高以及父布局的限制条件,确定自己在父布局中的位置。在布局阶段中,View 会调用 onLayout() 方法进行布局。在 onLayout() 方法中,View 会计算自己在父布局中的位置,然后将子 View 摆放在正确的位置上。在布局阶段结束后,每个 View 都会确定自己在父布局中的位置。

布局过程:

  • View:layout():确定四个顶点位置。
  • ViewGroup:layout()->onLayout(遍历所有子View)->layout(子View的方法)->onLayout(子View方法)。

3.3 draw(绘制)

在绘制阶段中,View 会将自己绘制到屏幕上。在绘制阶段中,View 会调用 onDraw() 方法进行绘制。在 onDraw() 方法中,View 可以通过 Canvas 对象进行绘制。在绘制阶段结束后,所有的 View 都会被绘制到屏幕上,完成整个绘制流程。

绘制过程:

  • View:draw()->drawBackground()->onDraw(绘制自身)->onDrawForeground()。
  • ViewGroup:draw()->drawBackground()->onDraw(绘制自身)->dispatchDraw(遍历子View)->drawChild(绘制子View)->draw(子View四步骤)->onDrawForeground()。

四,渲染机制

对于android中的窗口管理我们也有一定的了解比如WindowManagerService,但最终系统是怎么把这些页面渲染显示的呢,这里又有哪些技术呢。

4.1 基本概念

Android 系统采用一种称为 Surface 的 UI 架构为应用程序提供用户界面,每一个窗口都对应有一个 Surface。 Surface 都会被统一提交给 Surface 管理服务 SurfaceFlinger 进行合成,最后显示在屏幕上面。

总之来说:Android 应用程序调用 SurfaceFlinger 服务把经过测量、布局和绘制后的 Surface 渲染到显示屏幕上。

再来看看这个过程都有哪些成员参与:

  • ViewRootImpl:用来控制窗口的渲染,以及用来与 WindowManagerService、SurfaceFlinger 通信。
  • WindowManager:WindowManager 会控制窗口对象,它们是用于容纳视图对象的容器。窗口对象始终由 Surface
    对象提供支持。WindowManager 会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z
    轴顺序以及窗口的许多其他方面。WindowManager 会将所有窗口元数据发送到 SurfaceFlinger,以便
    SurfaceFlinger 可以使用这些数据在屏幕上合成 Surface。
  • Surface:Android 应用的每个窗口对应一个画布(Canvas),即 Surface,可以理解为 Android
    应用程序的一个窗口。Surface 是一个接口,供生产方与使用方交换缓冲区。
  • SurfaceView:SurfaceView 是一个组件,可用于在 View 层次结构中嵌入其他合成层。SurfaceView
    采用与其他 View 相同的布局参数,因此可以像对待其他任何 View 一样对其进行操作,但 SurfaceView 的内容是透明的。当SurfaceView 的 View 组件即将变得可见时,框架会要求 SurfaceControl 从 SurfaceFlinger
    请求新的 Surface。
  • BufferQueue:BufferQueue类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方)。几乎所有在系统中移动图形数据缓冲区的内容都依赖于BufferQueue。
  • SurfaceFlinger:Android 系统服务,负责管理 Android 系统的帧缓冲区,即显示屏幕。 EGLSurface 和
    OpenGL ES:OpenGL ES (GLES) 定义了用于与 EGL 结合使用的图形渲染 API。EGI是一个规定如何通过操作系统创建和访问窗口的库(要绘制纹理多边形,请使用 GLES 调用;要将渲染放到屏幕上,请使用 EGL 调用)。
  • Vulkan:Vulkan 是一种用于高性能 3D 图形的低开销、跨平台 API。与 OpenGL ES 一样,Vulkan提供用于在应用中创建高质量实时图形的工具。

4.2 android图形架构

在这里插入图片描述

主要组件如下所述:

  • 图像流生产方 图像流生产方可以是生成图形缓冲区以供消耗的任何内容。例如 OpenGL ES、Canvas 2D 和 mediaserver视频解码器。
  • 图像流消耗方 图像流的最常见消耗方是 SurfaceFlinger,该系统服务会消耗当前可见的Surface,并使用窗口管理器中提供的信息将它们合成到屏幕。SurfaceFlinger是可以修改所显示部分内容的唯一服务。SurfaceFlinger 使用 OpenGL 和 Hardware Composer 来合成一组Surface。
  • 其他 OpenGL ES 应用也可以消耗图像流,例如相机应用会消耗相机预览图像流。非 GL 应用也可以是使用方,例如 ImageReader 类。
  • 硬件混合渲染器 显示子系统的硬件抽象实现。SurfaceFlinger 可以将某些合成工作委托给硬件混合渲染器,以分担 OpenGL 和GPU 上的工作量。SurfaceFlinger 只是充当另一个 OpenGL ES 客户端。因此,在 SurfaceFlinger
    将一个或两个缓冲区合成到第三个缓冲区中的过程中,它会使用 OpenGL ES。这会让合成的功耗比通过 GPU 执行所有计算时更低。硬件混合渲染器 HAL 则进行另一半的工作,是所有 Android 图形渲染的中心点。Hardware Composer 必须支持事件,其中之一是 VSYNC(另一个是支持即插即用 HDMI 的热插拔)。
  • Gralloc 需要使用图形内存分配器 (Gralloc) 来分配图像生产方请求的内存。

数据流

有关 Android 图形管道的描述,请参见下图:

在这里插入图片描述
左侧的对象是生成图形缓冲区的渲染器,如主屏幕、状态栏和系统界面。SurfaceFlinger 是合成器,而硬件混合渲染器是混合渲染器。

BufferQueue

BufferQueues 是 Android 图形组件之间的粘合剂。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。

有关 BufferQueue 通信过程,请参见下图。
在这里插入图片描述
BufferQueue 通信过程:

BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。图像生产方的一些示例包括由相机 HAL 或 OpenGL ES 游戏生成的相机预览。图像消耗方的一些示例包括 SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。

BufferQueue 是将缓冲区池与队列相结合的数据结构,它使用 Binder IPC 在进程之间传递缓冲区。生产方接口,或者您传递给想要生成图形缓冲区的某个人的内容,即是 IGraphicBufferProducer(SurfaceTexture 的一部分)。BufferQueue 通常用于渲染到 Surface,并且与 GL 消耗方及其他任务一起消耗内容。

BufferQueue 可以在三种不同的模式下运行:

  • 类同步模式 - 默认情况下,BufferQueue 在类同步模式下运行,在该模式下,从生产方进入的每个缓冲区都在消耗方那退出。在此模式下不会舍弃任何缓冲区。如果生产方速度太快,创建缓冲区的速度比消耗缓冲区的速度更快,它将阻塞并等待可用的缓冲区。
  • 非阻塞模式 - BufferQueue 还可以在非阻塞模式下运行,在此类情况下,它会生成错误,而不是等待缓冲区。在此模式下也不会舍弃缓冲区。这有助于避免可能不了解图形框架的复杂依赖项的应用软件出现潜在死锁现象。
  • 舍弃模式 - 最后,BufferQueue 可以配置为丢弃旧缓冲区,而不是生成错误或进行等待。例如,如果对纹理视图执行 GL 渲染并尽快绘制,则必须丢弃缓冲区。

4.3 帧刷新概念

每秒的帧数(fps)或者说帧率是以帧为单位的位图图像每秒连续出现在显示器上的次数(速率)。简单来说就是一秒钟,屏幕显示多少张画面。

Android 在设计的时候,把帧频限定在了每秒 60 帧,当我们的 APP 的帧频 60fps 时,画面就会非常的流畅。为什么是60fps,人类视觉的时间敏感度和分辨率根据视觉刺激的类型和特征而变化,并且在个体之间不同,设置60fps比较合理。

VSYNC
VSYNC 信号可同步显示流水线。显示流水线由应用渲染、SurfaceFlinger 合成以及用于在屏幕上显示图像的硬件混合渲染器 (HWC) 组成。VSYNC 可同步应用唤醒以开始渲染的时间、SurfaceFlinger 唤醒以合成屏幕的时间以及屏幕刷新周期。这种同步可以消除卡顿,并提升图形的视觉表现。

帧同步

帧同步是指游戏的逻辑和渲染循环与操作系统的显示子系统和底层显示硬件之间的同步。Android 显示子系统旨在避免某些视觉伪影,例如画面撕裂。显示子系统可通过执行以下操作避免画面撕裂:

  • 在内部缓冲之前的帧
  • 检测延迟帧的提交情况
  • 当检测到延迟帧时继续显示当前帧

帧展示时间不一致是因为游戏渲染循环的运行速率与本机显示硬件支持的速率不同。如果底层显示硬件的游戏渲染循环运行速度过慢,会产生问题,导致展示时间不一致。例如,当运行速度为 30 FPS 的游戏尝试在原生支持 60 FPS 的设备上渲染时,游戏的渲染循环会导致同一帧在屏幕上又重复显示 16 毫秒。这种类型的中断会导致帧时间严重不一致,例如帧时间可能为 33 ms、16 ms、49 ms 等。过于复杂的场景会让该问题更复杂,因为它们会导致丢帧。

多种刷新率

Android 11 增加了对具有多种刷新率的设备的支持。此功能包含三个主要组成部分:

  • android.hardware.graphics.composer@2.4 中引入的新 HAL API
  • 平台代码,用于解析不同刷新率的设备配置并设置所需的刷新率
  • 新增的 SDK 和 NDK API,使应用可以设置所需的帧速率

配置群组

IComposerClient::Attribute 中添加了新属性 CONFIG_GROUP,您可以使用 getDisplayAttribute_2_4 API 对其进行查询。通过此属性,供应商可以将屏幕配置组合在一起。在大多数情况下,同一组中的配置允许无缝切换这些配置。平台使用配置群组来区分可以相互切换的不同配置,用以切换刷新率而不是其他配置属性。

下面的示例演示了在支持 4 种屏幕配置的设备上使用群组的好处:

  • 1080p 60Hz
  • 1080p 90Hz
  • 1080i 72Hz
  • 1080i 48Hz

尽管设备支持 48Hz、60Hz、72Hz 和 90Hz 的刷新率,但屏幕会在不同模式下运行,并且从 60Hz 切换到 72Hz 时,会导致屏幕配置从 1080p 更改为 1080i,而这可能并不是想要的行为。这一点可以通过配置群组来解决。将 60Hz 和 90Hz 放到一个配置群组中,而将 48Hz 和 72Hz 放入另一个配置群组中。平台知道,它可以在 60Hz 到 90Hz 之间切换,还可以在 48Hz 和 72Hz 之间切换,但不能在 60Hz 和 72Hz 之间切换,因为这会导致配置更改,而不仅仅是简单地变换刷新率。

在这里插入图片描述

4.4 SurfaceView与TextureView

SurfaceView 是在屏幕外创建一个单独的缓冲区来渲染图形,然后再将其绘制到屏幕上。SurfaceView 通常被用于实现视频播放、游戏动画等应用场景,因为它可以很好地处理大量的绘制操作和帧率的变化。另外,SurfaceView 支持多线程绘制,可以将 UI 和渲染分离,以提高应用的响应速度。

TextureView 是一种用于在 Android 应用程序中显示硬件加速渲染内容的控件。与 SurfaceView 不同,TextureView 的内容可以被任意的视图层级布局所包含,并且支持动画、滑动、缩放等常规的 View 操作,因为它与普通的 View 一样是基于 View 体系结构实现的。TextureView 可以用于显示视频、OpenGL ES 场景等应用场景,但它的性能通常比 SurfaceView 稍差。

主要区别:

  • 绘制方式不同:SurfaceView 在一个独立的线程中绘制,而 TextureView 在 UI 线程中绘制。
  • 可包含的视图层级不同:SurfaceView 不能在其他视图层级中使用,而 TextureView 可以在任何视图层级中使用。
  • 适用场景不同:SurfaceView 适用于实现大量的绘制和动画,而 TextureView 适用于与普通 View
    类似的场景,例如视频播放和 OpenGL ES 场景。
  • 性能表现不同:SurfaceView 的性能通常比 TextureView 更好,但也更加复杂。TextureView 的性能虽然比
    SurfaceView 差,但其使用方式更加简单和灵活。

五,CPU与GPU

由于 CPU 和 GPU 的设计不同,CPU 更擅长复杂逻辑控制,而 GPU 得益于大量 ALU 和并行结构设计,更擅长数学运算。

在 Android 系统中,CPU 与 GPU 的分工不同,CPU 主要负责包括 Measure,Layout,Record,Execute 的计算操作,GPU 主要负责 Rasterization(栅格化)操作。栅格化是指将向量图形格式表示的图像转换成位图(像素)以用于显示设备输出的过程,简单来说就是将我们要显示的视图,转换成用像素来表示的格式。

5.1 CPU

CPU 负责把 UI 组件计算成 Polygons,Texture 纹理,然后交给 GPU 进行栅格化渲染。

每次从 CPU 转移到 GPU 是一件很麻烦的事情,所幸的是 OpenGL ES 可以把那些需要渲染的纹理 Hold 在 GPU Memory 里面,在下次需要渲染的时候直接进行操作。

在 Android 里面那些由主题所提供的资源,例如 Bitmaps,Drawables 都是一起打包到统一的 Texture 纹理当中,然后再传递到 GPU 里面,这意味着每次你需要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。当然随着 UI 组件的越来越丰富,有了更多演变的形态。例如显示图片的时候,需要先经过 CPU 的计算加载到内存中,然后传递给 GPU 进行渲染。文字的显示比较复杂,需要先经过 CPU 换算成纹理,然后交给 GPU 进行渲染,返回到 CPU 绘制单个字符的时候,再重新引用经过 GPU 渲染的内容。动画则存在一个更加复杂的操作流程。

5.2 GPU

Resterization 栅格化是绘制那些 Button,Shape,Path,String,Bitmap 等组件最基础的操作。它把那些组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU 的引入就是为了加快栅格化的操作。

总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#+asp.net基于web的大学社团管理信息系统

本系统的模块是分为用户模块和管理员模块,管理员负责学生管理模块、社团管理模块、公告管理模块、留言管理模块、加入社团管理模块、活动管理模块、管理员管理模块。社团管理员则负责预约管理模块、活动报名的审核等。。 系统具有三类系统用户分别是:系统…

linux网络

查看网络接口信息 ifconfig mtu 最大传输单元 mtu和mss区别 hostname命令 永久修改 hostnamectl set-hostname 切换shell环境生效 或者vi hostname 编辑完重启生效 查看路由表条目route route查看或设置主机中路由表信息 route -n将路由记录中的地址信息显示为数字形式 …

MTU 网卡bond 简介

MTU 最大传输单元MTU(Maximum Transmission Unit,MTU),是指网络能够传输的最大数据包大小,以字节为单位。MTU的大小决定了发送端一次能够发送报文的最大字节数。如果MTU超过了接收端所能够承受的最大值,或者…

App Inventor 2 开发问答App

应用介绍 一个最基本的问答App开发,问答数据源来自csv文件格式,方便后续拓展成网络版的问答App。 事先出好题目、ABCD选择项及正确答案,先存在列表中,然后按顺序出题,答对则继续下一题,答错则Game over。 …

人工智能的前沿信息获取之使用谷歌学术搜索

谷歌学术是谷歌公司开发的一款专门针对学术搜索的在线搜索引擎[4],谷歌学术的网址为https://scholar.google.com,界面如图 6‑1所示。使用谷歌学术搜索可以检索会议或者期刊论文。只需要在检索框中输入关键字,然后点搜索按钮即可,…

了解Transformer架构的前奏_什么是预训练_理解预训练---人工智能工作笔记0034

我们会先来说预训练有什么用,其实 之前说的机器学习,其实都是跟数学相关性很大的,比如,支持向量机,回归算法, 1.最早的时候,做机器学习,就是偏数学的,比如用的决策树,支持向量机,线性回归,逻辑回归等算法. 这种是偏向数学的,偏向统计的. 然后这个深度学习,其实就是偏大数据的…

奥艺大会 | “OLYMP‘ARTS中国设计奖”在2023米兰设计周发布

由国际奥艺委员会、北京国际设计周和中国科学院大学魏桥国科联合实验室共同发起的“OLYMPARTS中国设计奖”(OlympArts China Design Awards)于当地时间2023年4月19日,在2023米兰设计周“中国日”活动中举行宣传推介活动。 (由左至…

缺失msvcp140.dll怎么办?msvcp140.dll下载

缺失msvcp140.dll怎么办?msvcp140.dll下载,作为Windows操作系统中必备的组件之一,msvcp140.dll是一款Microsoft Visual C Runtime的动态链接库文件,旨在提供必要的C运行环境支持,以让软件应用程序得以在Windows平台上可…

道可维斯|益企行动,点亮星空数字化转型峰会

2023年,“烟火气”回归,但企业挑战仍在继续。找寻企业增长转型的内生动力,仍是中小企业不变的探索话题。如何寻找穿越周期的高成长机会?4月21日,佛山金蝶软件科技有限公司主办的主题为“益企行动,点亮星空”…

MFC转QT踩坑记录

1、中文乱码 QT msvc编译器版本默认编译的是字符串编码是ANSI, 而QTCreator默认创建的cpp字符串编码是UTF-8,然后msvc还是按ANSI去解析字符串常量,所以导致了中文乱码 解决方案: 使用notepad把cpp编码从UTF-8转成 UTF-8带BOM…

ChatGPT 之父承认 GPT-5 并不存在,为什么 OpenAI 总是这么实诚?|万字详述

ChatGPT 诞生前传 来源: 爱范儿 微信号:ifanr 最近,OpenAI 的 CEO Sam Altman 在一场公开会议上为 GPT-5 辟谣。 他声称 OpenAI 并没有在训练 GPT-5,而是一直基于 GPT-4 做别的工作。 OpenAI 是一家非常有趣的机构,和微软、Go…

用SQL语句操作Oracle数据库——数据更新

数据更新 数据库中的数据更新操作有3种:1)向表中添加若干行数据(增);2)删除表中的若干行数据(删);3)修改表中的数据(改)。对于这3种操作&#xf…

提升项目沟通效果的核心方法

项目沟通是项目管理中的核心之一,项目成败的关键因素之一就是项目团队之间的沟通效果。良好的项目沟通可以增强团队的合作力和凝聚力,确保项目按时完成,达成项目目标。那么提升项目沟通效果的方法有哪些呢?。1、制定沟通计划 在项…

JAVAWeb06-动态WEB开发核心Servlet-01

1. 概述 1.1 官方文档 地址: https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html 1.2 Servlet 和 Tomcat 的关系 一句话, Tomcat 支持 Servlet(谁也不能离开谁) 1.3 为什么会出现 Servlet 提出需求: 请用你现有的html css javascrip…

PS封装格式:GB28181协议RTP传输

在安防行业,有个协议是无论如何都要适配的,因为公安监控网络用的就是它,它就是:GB28181。而这份协议主要由海康制定,所以除了海康其他厂商想要适配都会少许有点儿麻烦。 1. GB28181要求的RTP流格式     首先&…

Ansible 进阶

模块应用 firewalld模块 可以配置防火墙策略 [rootcontrol ~]# vim ~/ansible/firewall.yml --- - hosts: test #hosts定义需要远程的主机tasks: #tasks定义需要执行哪些任务- name: install firewalld. …

ChatGPT已过时?Auto-GPT迅速走红,无需人类插手自主解决复杂任务,GitHub标星5万

来源: AI前线 微信号:ai-front 作者 | Luke Larsen ChatGPT 之所以能风靡全球,很大程度上要归功于其简单的功能框架。作为一款 AI 聊天机器人,它唯一的作用就是生成令人信服的自然语言文本、顺畅回应用户的提问。 但 AI 聊天机器人的使用体…

大数据分析案例-基于XGBoost算法预测航空机票价格

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集…

实在智能获评十大数字经济风云企业,2022余杭数字经济“群英榜”发布

4月17日,经专家评审、公开投票,由中共杭州市余杭区委组织部(区委两新工委)、中共杭州市余杭区经济和信息化局委员会主办评选的2022年度余杭区数字经济“群英榜”正式公示。其中,实在智能成功获评十大数字经济风云企业之…

cocoscreator性能优化4-Sprite颜色数据去除

前言 Sprite是游戏内容的一个基本组成元素,包括ui、道具、立绘等各种地方都会用到。大部分情况下美术会帮我们调好图片颜色,我们只要把图片直接放到游戏里就行了。Sprite默认的渲染顶点数据中包含了颜色数据,由于我们并不需要去修改颜色&…