Android代码解读之渲染机制揭秘

渲染机制(一):Android渲染机制的演进 在Android中,当我们谈到布局优化、卡顿优化时,通常通过减少布局层级、减少主线程耗时操作来减少丢帧。如果丢帧比较严重,那么界面可能会有明显的卡顿感。本文我们来探究下Android的渲染机制。 阅读详情

问题

1.vsync如何协调应用和SurfaceFlinger配合来完成UI渲染、显示,App接收vsync后要做哪些工作?

2.requestLayout和invalidate区别?

3.performTraversals到底是干什么了?

4.surfaceflinger怎么分发vsync信号的?

5.app需要主动请求vsync信号,sw sync才会分发给app?

6.app是如何构建起上面这套机制的?

如果对于上面的几个问题没有非常确认、清晰的答案可以继续看下去,本文通过详细介绍渲染机制解答上面的问题。

Vsync信号

Android在“黄油计划”中引入的一个重要机制就是:vsync,引入vsync本质上是要协调app生成UI数据和SurfaceFlinger合成图像,app是数据的生产者,surfaceflinger是数据的消费者。vsync信号有两个消费者,一个是app,一个是surfaceflinger,这两个消费者并不是同时接收vsync,而是他们之间有个offset。

systrace看UI渲染流程:

建议先参考《Android性能工具使用指南》systrace小节:https://www.atatech.org/articles/151035了解应用程序UI数据如何从UIThread->RenderThread->SurfaceFlinger的,整个数据流转链路清晰后再看下面的流程会更清晰。

vsync-offset引入原因

上面提到hw vsync信号在目前的Android系统中有两个receiver,App + SurfaceFlinger,hw sync会转化为sw sync分别分发给app和sf,分别称为vsync-app和vsync-sf。app和sf接收vsync会有一个offset,引入这个机制的原因是提升“跟手性”,也就是降低输入响应延。

注:图来自网络

如果app和sf同时接收hw sync,从上面可以看到需要经过2*vsync的时间画面才能显示道屏幕,如果合理的规划app和sf接收vsync的时机,想像一下,如果vsync-sf比vsync-app延迟一定时间,如果这个时间安排合理达到如下效果就能降低延迟:

注:图来自网络

SufaceFlinger工作机制

组成架构:

  1. EventControlThread: 控制硬件vsync的开关
  2. DispSyncThread: 软件产生vsync的线程
  3. SF EventThread: 该线程用于SurfaceFlinger接收vsync信号用于渲染
  4. App EventThread: 该线程用于接收vsync信号并且上报给App进程,App开始画图

从这4个线程,其实我们可以将vsync分为4种不同的类型

  • HW vsync, 真实由硬件产生的vsync信号
  • SW vsync, 由DispSync产生的vsync信号
  • vsync-sf, SF接收到的vsync信号
  • vsync-app, App接收到的vsync信号

应用程序基本架构

Android应用进程核心组成

上图列举了Android应用进程侧的几个核心类,PhoneWindow的构建是一个非常重要的过程,应用启动显示的内容装载到其内部的mDecor,Activity(PhoneWindow)要能接收控制也需要mWindowManager发挥作用。ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView、mSurface、Choregrapher,mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始渲染流程,下面介绍上图构建的流程。

应用启动流程图(下文称该图为P0)

进程启动

应用冷启动第一步就是要先创建进程,这跟linux类似C/C++程序是一致的,Android亦是通过fork来孵化应用进程,我们知道Linux fork的子进程继承父进程很多的资源,即所谓的COW。应用进程同样会从其父进程zygote处继承资源,比如art虚拟机实例、预加载的class/drawable资源等,以付出一些开机时间为代价,一来能够节省内存,二来能够加速应用性能,下面结合systrace介绍Android如何启动一个应用进程,应用启动第一个介入的管理者是AMS,应用启动过程中AMS发现没有process创建,就会请求zygote fork进程,下图就是AMS中创建进程的耗时:

AMS(ActivityManagerService)请求zygote创建进程的流程如下:

##ActvityManager:startProcessLocked

private final void startProcessLocked(ProcessRecord app, String hostingType,
  String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {

    boolean isActivityProcess = (entryPoint == null);
    if (entryPoint == null) entryPoint = "android.app.ActivityThread";
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +app.processName);
    checkTime(startTime, "startProcess: asking zygote to start proc");
    ProcessStartResult startResult;
    if (hostingType.equals("webview_service")) {
    startResult = startWebView(entryPoint,
      app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
      app.info.dataDir, null, entryPointArgs);
    } else {
        startResult = Process.start(entryPoint,
      app.processName, uid, uid, gids, debugFlags, mountExternal,
      app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
            app.info.dataDir, invokeWith, entryPointArgs);
    }
    checkTime(startTime, "startProcess: returned from zygote!");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}

前面systrace打印的proc创建时间就是来自与此,Process.start是请求zygote来创建创建进程,这其中有几个很重要问题,比如新建进程入口函数在哪?这个新建进程如何做到创建以后能够不退出,且能不断响应外部输入的等,接下来介绍下入口函数这个点,正如C/C++跑起来去找main函数一样,可以看到startProcess函数有个entrypoint参数:

if (entryPoint == null) entryPoint = "android.app.ActivityThread";

原来进程启动以后就会先去执行ActivityThread:main这个入口,应用自此开始了自己启动流程,这点systrace展示的非常清晰:

看到上面PostFork色块,很明显是Process创建成功后的打印,然后代码继续执行到ZygoteInit,ZygoteInit真正来查找entrypoint,应用程序跳转到ActivityThread.Main开始执行:

    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }

上面代码RuntimeInit.applicationInit内部执行findStaticMain查找入口函数:

    protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        // If the application calls System.exit(), terminate the process
        // immediately without running any shutdown hooks.  It is not possible to
        // shutdown an Android application gracefully.  Among other things, the
        // Android runtime shutdown hooks close the Binder driver, which can cause
        // leftover running threads to crash before the process actually exits.
        nativeSetExitWithoutCleanup(true);

        // We want to be fairly aggressive about heap utilization, to avoid
        // holding on to a lot of memory that isn't needed.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

        final Arguments args = new Arguments(argv);

        // The end of of the RuntimeInit event (see #zygoteInit).
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // Remaining arguments are passed to the start class's static main
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }

OK,至此进入systrace显示ActivityThread.main函数执行,也就是达到了P0的第3步骤。

ActivityThread对象

ActivityThread main执行的第一件事是调用AMS的attacApplicationLock(P0 :6)向大管家汇报:“进程已经启动好了,继往下启动吧”。AMS收到汇报就回调了(P0:7)ActvityThread的bindApplication,这里“绑定”理解起来比较抽象,到底是要把哪些东西跟应用程序“绑定”起来呢?其实是把app本身的“上下文(context)”信息跟刚刚创建的进程绑定起来,噢,又出来一个“上下文(context)”概念,用大白话讲就是应用的apk包包含应用的所有身家信息,这些个信息就可以称为是应用的“上下文(context)”,应用可以通过这个Context访问自己的家当,此处会创建Application Context(具体关于应用程序几种context区别自行google,此处不予展开)

    private void handleBindApplication(AppBindData data) {

        mBoundApplication = data;
        mConfiguration = new Configuration(data.config);
        mCompatConfiguration = new Configuration(data.config);

        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        updateLocaleListFromAppContext(appContext,
                mResourcesManager.getConfiguration().getLocales());
        if (ii != null) {
            final ApplicationInfo instrApp = new ApplicationInfo();
            ii.copyTo(instrApp);
            instrApp.initForUser(UserHandle.myUserId());
            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                    appContext.getClassLoader(), false, true, false);
            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
            final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
        } else {
            mInstrumentation = new Instrumentation();
        }

        Application app;
        try {
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } 
        }
    }

上面回调到应用程序Application.onCreate函数,很多应用会在此处做初始化动作,如果初始化模块过多可以考虑延迟加载,应用继续启动来到P0:12/P0:13

Activity对象

Activity的构建开始窗口显示之旅,上面“Android应用进程核心组成”架构图中可以看到Activity核心是PhoneWindow,P0图中步骤13 performLauncherActivity中包含了14/15两个重要的操作,attach函数创建了“PhoneWindow”,这个窗口具体承载了什么信息?用大白话来说点击启动一个应用以后,可以说是显示了一个"窗口"(Window),这个“窗口”至少要承载两个功能:

  • 显示内容
  • 可以操作。

窗口显示的内容就是android的布局(layout),布局信息需要有个“房间”存放,PhoneWindow:mDecor就是这个“房间”,attach首先将布局的“房间”建好,等到后续15 onCreate调用到就会调用setContentView使用应用程序开发者提供的布局(layout)“装饰、填充”这个“房间”。

“房间”填充、装饰好后,还需要能够接收用户的操作,这就要看PhoneWindow中mWindowManager对象,这个对象最终包含一个ViewRootImpl对象,“窗口”正是因为构建了ViewRootImpl才安装上了发动机。

attach函数
final void attach(...) {
  mWindow = new PhoneWindow(this, window, activityConfigCallback);
  mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken,  
  mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}

mWindowManager最后是一个WindowManagerImpl对象,WindowManagerImpl对象的mParentWindow对应了Activity中的PhoneWindow对象。

setWindowManager函数
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean                     hardwareAccelerated) {
       mAppToken = appToken;
       mAppName = appName;
       mHardwareAccelerated = hardwareAccelerated
               || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
       if (wm == null) {
           wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
       }
       //this对象对应Activity中的PhoneWindow对象
       mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
   }

OK,上面的perfomrLaunchActivity一顿操作已经完成两个“窗口(Activity)”中两个重要变量的初始化,流程走到15 Activity:onCreate函数

onCreate函数
@Overrideprotected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

空的HellWorld工程都默认包含上面两行代码,setContentView就是操作系统给开发机会告诉系统“到底让我显示什么?”就是这么简单的一行代码很可能就是导致应用性能卡顿,那么setContentView干啥了?

setContentView函数

该函数的作用就是使用布局文件填充“房间”mDecor,如果布局文件非常复杂会导致“房间”装饰的费时费力

(豪装),装修过程中从原理说就是讲布局文件activity_main中的控件实例化,Android这个过程称作inflate,systrace展示如下:

上面只是操作系统从让开发给填充、装饰了房间,但是这个房间还没“开灯”,看不见,也没开门(窗口无法操作),因为需要真正把这个窗口注册到WindowManagerService后,WMS同SurfaceFlinger取得联系才能看到,后面我们来分析这个窗口是如何开灯显示,并且能开门迎客接收按键消息的。

随后应用启动流程来到handleResumeActivity:

handleResumeActivity函数
final void handleResumeActivity(IBinder token,
     boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
       ...;
       //回调应用程序的onResume
       r = performResumeActivity(token, clearHide, reason);
       ...;
       if (r.window == null && !a.mFinished && willBeVisible) {
          r.window = r.activity.getWindow();
           View decor = r.window.getDecorView();
           decor.setVisibility(View.INVISIBLE);
           ViewManager wm = a.getWindowManager();
           WindowManager.LayoutParams l = r.window.getAttributes();
           a.mDecor = decor;
           l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
          ...
           if (a.mVisibleFromClient) {
           if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
             }
         }
   }

上面performResumeActivity会回调应用程序的onResume函数,从这里可以看到onResume被回调时用户是看不到窗口的。wm.addView是重点,这一步就要把“房间”亮灯,也就是把窗口注册到wms中着手显示出来,并且开门接收用户操作,这里是调用的WindowManagerImpl.java:addView:

addView函数
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
       ...;
       ViewRootImpl root;
       View panelParentView = null;
       synchronized (mLock) {
           root = new ViewRootImpl(view.getContext(), display);
           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);
           } catch (RuntimeException e) {
               // BadTokenException or InvalidDisplayException, clean up.
               if (index >= 0) {
                   removeViewLocked(index, true);
               }
               throw e;
           }
       }
   }

从这里开始创建应用进程最核心的:ViewRootImpl类,它负责与WMS通信,负责管理Surface,负责触发控件的测量、布局、绘制,同时也是输入事件的中转站,可以说ViewRootImpl是整个控件系统运转的中枢,应用进程中最为重要的一个组件,有了ViewRootImpl这个窗口才能开始渲染被用户看到,并且接受用户操作(开灯、开门)。

ViewRootImpl剖析

上面的框架图提到ViewRootImpl有个非常重要的对象Choreographer,整个应用布局的渲染依赖这个对象发动,应用要求渲染动画或者更新画面布局时都会用到Choreographer,接收vsync信号也依赖于Choreographer,我们以一个View控件调用invalidate函数来分析应用如何接收vsync、以及如何更新UI的。

Activity中的某个控件调用invalidate以后,会逆流到根控件,最终到达调用到ViewRootImpl.java : Invalidate

invalidate函数
void invalidate() {
  mDirty.set(0, 0, mWidth, mHeight);
  if (!mWillDrawSoon) {
  scheduleTraversals();
  }
}
scheduleTraversals函数
void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;
           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           mChoreographer.postCallback(
                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           if (!mUnbufferedInputDispatch) {
               scheduleConsumeBatchedInput();
           }
           notifyRendererOfFramePending();
           pokeDrawLockIfNeeded();
       }
   }

从上面的代码看到Invalidate最终调用到mChoreographer.postCallback,这代码的含义:应用程序请求vsync信号,收到vsync信号以后会调用mTraversalRunnable,接下来看下应用程序如何通过Choreographer接收vsync信号:

//Choreographer.java    
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource);
        }

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            //应用请求vsync信号以后,vsync信号分发就会回调到这里
            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                Log.d(TAG, "Received vsync from secondary display, but we don't support "
                        + "this case yet.  Choreographer needs a way to explicitly request "
                        + "vsync for a specific display to ensure it doesn't lose track "
                        + "of its scheduled vsync.");
                scheduleVsync();
                return;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }

上面onVsync会往消息队列放一个消息,通过下面的FrameHandler进行处理:

private final class FrameHandler extends Handler {
       public FrameHandler(Looper looper) {
           super(looper);
       }
       @Override
       public void handleMessage(Message msg) {
           switch (msg.what) {
               case MSG_DO_FRAME:
                   doFrame(System.nanoTime(), 0);
                   break;
               case MSG_DO_SCHEDULE_VSYNC:
                   doScheduleVsync();
                   break;
               case MSG_DO_SCHEDULE_CALLBACK:
                   doScheduleCallback(msg.arg1);
                   break;
           }
       }
   }

从systrace中我们经常看到doFrame就是从上面的doFrame打印,这说明应用程序收到了vsync信号要开始渲染布局了,图示如下:

doFrame函数就开始一次处理input/animation/measure/layout/draw,doFrame代码如下:

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

比如上面调用Invalidate的时候已经post了一个CALLBACK_TRAVERSAL类型的Runnable,这里就会执行到那个Runnable也就是mTraversalRunnable:

final class TraversalRunnable implements Runnable {
       @Override
       public void run() {
           doTraversal();
       }
   }
performTraversal函数

doTraversal内部会调用大名鼎鼎的:performTraversal,这里app就可以进行measure/layout/draw三大流程。需要注意的时候Android在5.1引入了renderthread线程,可以讲draw操作从UIThread解放出来,这样做的好处是,UIThread将绘制指令sync给renderthread以后可以继续执行measure/layout操作,非常有利于提升设备操作体验,如下:

上面就是应用进程收到vsync信号之后的渲染UI的大概流程,可以看到app进程收到vsync信号以后就开始其measure/layout/draw三大流程,这里面就会回调应用的应用各个空间的onMeasure/onLayout/onDraw,这个部分是在UIThread完成的。UIThread在完成上述步骤以后会绘制指令(DisplayList)同步(sync)给RenderThread,RenderThread会真正的跟GPU通信执行draw动作,systrace图示如下:

 

上图中看到doFrame下面会有input/anim(时间短色块比较小)、measure、layout、draw,结合上面的代码分析就清楚了app收到vsync信号的行为,measure/layout/draw的具体分析就涉及到控件系统相关的内容,这块内容本文不作深入分析,提一下draw这个操作,使用硬件加速以后draw部分只是在UIThread中收集绘制命令而已,不做真正的绘制操作,该部分后续开一篇介绍硬件加速和hwui的文章做介绍。

APP为什么滑动卡顿、不流畅

这里我们指UI/Render线程里面的卡顿,因为这里才涉及Android的核心原理,非UIThread的执行逻辑导致的卡顿需要根据具体业务场景分析,比如影视播放卡顿可能是播放器原因,可能是网络原因等等。UIThread的卡顿有如下几类的原因:

后台进程CPU消耗高

如果CPU被后台进程或者线程消耗,前台的应用流畅性势必会受影响,这点也是很容易被忽略的。

复杂的控件树 

复杂的布局不仅会导致inflate时间变长,同时也会导致traversal时间边长,如果traversal + renderthread 的渲染部分不能在16ms内完成就出现掉帧现象,布局优化可以参考前面启动性能文章。

不合理requestLayout 

requestLayout顾名思义就是应用布局发生变化,需要重新进行measure/layout/draw的流程,比invalidate调用更重,invalidate只是标记一个“脏区域”,不需要执行meausre/layout调用,只需要重绘即可。requestLayout调用意味着频繁的traversal动作,此时肯定会导致卡顿掉帧问题。

UIThread block 

UIThread被block的因素多种多样,有binder block、IO block等等,具体见应用启动性能分析文章;前面问题小结总提到了一个问题:surfaceview刷新为什么用户界面没有卡顿?原因是surfaceview拥有独立的surface画布(从surfaceview这个名字就能知道),所以surfaceview可以在开发者自建的thread中刷新,这样视频刷新就不会影响到uithread。GLSurfaceView更高级一些,控件本身就会创建子线程。理解这个以后其实可以更多的扩展思路,比如GLSurfaceView本质上就是将UI数据当成纹理,放在子线程中传入GPU,按照此思路我们是否有办法将Bitmap等数据也放到子线程传入GPU,其实也是可以的,也就是下文提到的“异步纹理”,可以将图片数据放在开发者自定义的线程中渲染,Android有很多好玩的控件,比如TextureView,SurfaceTexture,把所有这些控件原理都理解以后对扩展优化思路有很大帮助,本文不再纤细介绍了。

RenderThread block

这个原因很少有文章提到,流畅的应用渲染需要16ms,但是具体这个16ms要做哪些事情,如下图:

可以看到一个vsync的16ms要UIThread + RenderThread配合完成才能保证流畅的体验,UIThread是执行traversal调用,RenderThread其中很重要的一个操作是跟GPU通信将图片上传GPU,上传图片期间UI Thread也是block状态,所以魔盒、TV瀑布流的桌面、影视无法实现边滑动边上传渲染图片,实现过了异步渲染的机制将图片非UI/RT Thread,实现边滑动边出图的效果。

总结和展望

本文从代码层面,把应用进程启动和渲染的流程走读了一遍,理解了Android的渲染原理对于理解其他UI框架或者引擎有比较好的借鉴意义,比如研究google的flutter框架时会更轻松:

 flutter的渲染流程(图来自网络)

上图从网络上搜到的flutter 框架的流程图,这个流程是不是有点像套娃战术,同样是vsync信号、UI线程,GPU线程(也就是android的renderthread)两线程加速性能。Android的UI 线程的draw最终只负责将绘制操作转化为绘制指令(DisplayList),真正负责和GPU交互来绘制的是RenderThread,flutter其实看到也是同样的思路,UI线程绘制构建LayerTree同步给GPU线程,GPU线程通过Skia库跟GPU交互,相信随着flutter框架在开发工具和稳定性方面会不断演进,这个框架将会流行起来。

本文主要介绍了应用UIThread相关的渲染流程,有两个模块对理解Android图形系统也同样重要:一是控件系统,二是RenderThread机制,涉及HWUI和OpenGL,这是非常有趣的模块,我们都知道Android的硬件加速这个概念,那么到底是怎么加速的?后续会写一篇介绍HWUI相关的文章作为该系列文章的第二篇。

Android软件渲染流程 生产消费模型中的图像缓存Surface中管理的图像缓存Surface暴露给Canvas的图像缓存提交给消费者处理的图像缓存Canvas是对Surface提供的图像缓存的抽象封装。Surface对Canvas初始化的过程,就是Surface从生产消费模型中为Canvas分配一块图像缓存的过程。一个Surface同一时间只能提供一个图像缓存。 阅读详情

相关推荐

Android图形渲染框架

Android图形渲染框架包括:Gralloc分配缓冲区,BufferQueue作为缓冲队列连接生产者与消费者,可以使用TextureView、SurfaceView和GlSurfaceView

徐福记456 2万+

Android View的渲染过程

如果是一个具体的View,那就相当简单了,默认的实现就可以了。如果是ViewGroup会相对复杂一些,取决于如何从水平和垂直方向上约束子view,然后进行遍历,并把约束考虑进去。可以参考LinearLayout和RelativeLayout的onMeasure实现。如果是自定义View的话,不需要做什么。如果是自定义的ViewGroup的话,要看设计的目的,是如何排列子view的。

u010345983的博客 530

Android屏幕刷新机制—VSync、Choreographer-全面理解!,安卓ndk开发书籍

上面讲到,Google在Android 4.1系统中对Android Display系统进行了优化:在收到VSync pulse后,将马上开始下一帧的渲染。即一旦收到VSync通知,CPU和GPU就立刻开始计算然后把数据写入buffer。本节就来讲 “drawing with VSync” 的实现——。Choreographer,意为 舞蹈编导、编舞者。在这里就是指 对CPU/GPU绘制的指导—— 收到VSync信号 才开始绘制,保证绘制拥有完整的16.6ms,避免绘制的随机性。

2401_83692985的博客 1246

android studio放置在函数上面看_史上最全Android渲染机制讲解(长文源码深度剖析)...

新年快乐!年底不忘学习。文章的作者德柏同学写了五千字长文,图文并茂,结合系统源码深入讲解Android操作系统的渲染机制。vsync、SurfaceView、layout,这些经常听说的概念背后是什么原理?任何Android开发者都不容错过,赶紧收藏啦~前言渲染机制是Android操作系统很重要的一环,本系列通过介绍应用从启动到渲染的流程,揭秘Android渲染原理。问题1.vsyn...

weixin_39671405的博客 499

渲染机制(三):Vsync信号

本文主要介绍了 VSync 相关的内容。

Elson的博客 858

[Android禅修之路] 解读Vsync(一)

一 概述 说到 Vsync,大家首先想到的肯定是游戏中的垂直同步。可能会有人疑惑,游戏的垂直同步和 Android 的图形系统的关系。其实在 Android 中,同样有着 Vsync 机制。 在 Android 的刷新过程中,Vsync 的作用就是同步的唤醒应用以开始渲染、唤醒 SurfaceFlinger 开始屏幕的合成以及屏幕刷新周期。 在 SurfaceFlinger 渲染的过程中,画面的显示会经过应用程序的绘制,SurfaceFlinger 的合成,以及硬件的显示。这些过程分别处于三个不同的进程中,

梅花寺 2087

Android 屏幕刷新机制与Choreographer全面解读

在 Android 开发过程中,我们经常谈到布局优化、卡顿优化时,通常都知道要减少布局层级、避免主线程做耗时操作等等,这样可以减少丢帧,如果丢帧比较严重的话就会发生明显的卡顿现象。这篇文章我们来详细分析 Android 屏幕刷新机制,便于优化我们的项目。

lixiong0713的博客 1万+

【系统稳定性 - 调优】3.4 常见Binder相关异常调优总结

binder通信是Android特有的一种通信机制,常见binder相关的异常包括卡binder对端处理超时,binder本端处理超时(导致主线程block)以及binder耗尽三类问题。无论是应用,Framework,Hal中间件,还是BSP驱动,熟悉这三类binder相关case的调试和分析,能够帮助我们快速地找到和解决问题。

从0到1,突破自己 6810

Android UI渲染机制与性能优化实践

UI渲染是移动应用开发的核心技术之一,涉及视图树的构建、布局计算和图形绘制等关键流程。Android系统通过VSYNC信号同步渲染管线,确保60FPS的流畅体验,其中SurfaceFlinger负责管理图形缓冲区,实现高效的界面合成。性能优化需重点关注过度绘制问题,通过减少视图层级、优化自定义绘制和使用GPU渲染分析工具等手段,可显著提升界面流畅度。本文深入解析Android渲染机制,结合View Hierarchy优化和GPU性能调优等热词,为开发者提供从原理到实践的完整解决方案。

weixin_33806300的博客 324

Android操作系统的图形渲染机制揭秘

本文章的目的是全面深入地剖析Android操作系统的图形渲染机制。范围涵盖了从基本的图形渲染概念到Android系统中具体的实现细节,包括图形渲染的核心算法、数学模型,以及实际应用中的代码案例和性能优化等方面。通过对这些内容的详细讲解,帮助读者理解Android图形渲染的工作原理和机制,从而能够在实际开发中更好地运用和优化图形渲染相关的功能。本文将按照以下结构进行阐述:首先介绍Android图形渲染机制的核心概念和它们之间的联系,包括相关的架构和流程图;

操作系统内核探秘的博客 853

python进行windows系统UI自动化之【pyautoit】

一般以我们平时操作,会从下图上方的地址栏进行操作,这里注意可以直接从下方的文件名处,输入对应的绝对地址,如:F:\程序设计书籍\《啊哈!pyautoit 是一个用python写的基于AutoItX3.dll的接口库,用来进行windows窗口的一系列操作,也支持鼠标键盘的操作。点击上访的图标,进行拖拉,到想访问的位置,注意下图两个红框中的数值,上面的框表示页面的标题和类,下面的框表示控件的类和序号(一般连起来使用)打开Auto Window Info(x64)或Auto Window Info(x86)

weixin_43936332的博客 9314

图形渲染机制

一. 参考 Android应用程序UI硬件加速渲染技术简要介绍和学习计划 https://blog.youkuaiyun.com/luoshengyang/article/details/45601143

ly969434341的专栏 249

3D视觉-激光三角测量法的概述和原理

激光三角法位移测量的原理是,用一束激光以某一角度聚焦在被测物体表面,然后从另一角度对物体表面上的激光光斑进行成像,物体表面激光照射点的位置高度不同,所接受散射或反射光线的角度也不同,用。激光三角测量法是一种通过图像采集系统采集被测物表面漫反射的激光轮廓,进而基于设备已知的相对位置通过算法处理,获得被测物表面的三维轮廓数据,进而还原目标物体三维空间信息的方法。采用激光三角原理和回波分析原理进行非接触位置、位移测量的精密传感器, 广泛应用于位置、位移 、厚度、半径、形状、振动、距离等几何量的工业测量。

qq_20660115的博客 3919

StickySwitch源码深度剖析:从Kotlin实现到渲染机制的完整解读

StickySwitch是一个专为Android平台设计的粘性动画开关控件,它通过流畅的动画效果和直观的用户交互,为应用界面增添了生动的视觉体验。这款开源组件采用Kotlin语言编写,实现了独特的粘性动画效果,让开关切换过程更加自然和有趣。在本文中,我们将深入剖析StickySwitch的源码实现,从核心架构到渲染机制,为您提供完整的源码解读指南。 ## 项目架构概览 StickySwitch

gitblog_00910的博客 1052

07 Zabbix 微信告警

Zabbix 微信告警 1.注册企业微信 企业微信注册地址:https://work.weixin.qq.com 2.进入企业微信后台管理 2.1 添加微信成员 2.2创建应用 2.3 获取四项数据 (1)AgentID (2) Secret (3) 企业ID (4)部门ID 3.Zabbix Server配置 3.1确认脚本目录 # grep alertscripts /etc/zabbix/zabbix_server....

Jian Sun_的博客 1297

Pytorch图像去噪实战(七):Noise2Noise自监督图像去噪实战,没有干净图也能训练模型

PyTorch图像去噪实战:Noise2Noise自监督去噪方法 本文介绍了在没有干净图像的情况下使用Noise2Noise方法进行图像去噪的实战方案。主要内容包括: 问题场景:真实项目中经常只有带噪图像而没有对应的干净图像,如夜间监控、医学影像等场景。 Noise2Noise核心思想:使用同一场景的两张不同噪声版本的图像进行训练(noisy_image_a → noisy_image_b),让模型学习恢复共同的干净结构而非随机噪声。 技术原理:模型通过最小化两个独立噪声图像之间的差异,最终会收敛到真实干净

ai技术增长的博客 500

Android Systrace 基础知识(8) - Vsync-App :基于 Choreographer 的渲染机制详解

本文是 Systrace 系列文章的第八篇,主要是对 Systrace 中的 Choreographer 进行简单介绍 本系列的「目的」是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。 本文介绍了 App 开发者不经常接触到但是在 Android Fram

Gracker的专栏 3248

LVGL 磁罗盘效果

环境:LVGL 9.x + SDL2,窗口 1280×800。界面文案用英文(默认 Montserrat,无 CJK)。本文按实现顺序讲:先建立两个耦合转动体,再把磁倾角误差从卡面倾斜里推出来,最后用同一套投影把刻度、数字和目标画到卡上。画面上的迟滞、涡流、摆动和读数偏差都来自方程,没有补间动画。

摸鱼专家 85

安卓卡顿优化方法,带你一起探究Android事件分发机制,深夜思考

前言 马爸爸总结了一句话:跳槽,要么是钱不到位,要么是受了委屈。 我给自己这次的跳槽经历做了一个分析,希望能对那些想换工作的朋友有所帮助。 许多朋友想换工作,但是对“换工作”的理解可能仅限于写简历、投简历、面试。在跳槽之前,我也是这么想的。 当时,工作并不顺利,我的专业技能并没有很好的发挥,公司也给不了什么发展前景,就有了跳槽的打算。 我看到一家行业内还不错的上市公司在招聘这个岗位,于是我发了简历。面试后我感觉很好,但我拒绝了,因为工资没有达到预期。 经过仔细思考,我发现我准备得太匆忙了,或者根本没有准备好

m0_57081438的博客 138

Jetson 平台编译安装 FFmpeg 7.1(支持 Nvmpi + SRT + x265)

摘要: 本文详细介绍了在NVIDIA Jetson平台上编译FFmpeg 7.1并集成关键功能的全过程。通过补丁方式加入Nvmpi实现硬件加速支持,同时编译集成x265编码器和SRT低延迟传输协议。步骤涵盖:1)安装x265编码器;2)配置SRT协议支持;3)编译jetson-ffmpeg的Nvmpi接口;4)定制化编译FFmpeg 7.1;5)验证模块集成情况。最终实现支持硬件编解码、高效HEVC压缩和可靠推流的增强版FFmpeg,为Jetson平台的音视频处理提供完整解决方案。

J。 345

Python爬虫实战:链家/贝壳租房数据抓取与可视化分析平台

本文详细介绍了如何使用Python爬虫技术抓取链家/贝壳网站的租房信息,并构建一个完整的数据可视化分析平台。文章涵盖了从数据抓取、数据清洗、数据存储到数据可视化的全流程,使用最新的Python技术栈,包括AsyncIOPandasMongoDB和Pyecharts等库。本系统可作为毕业设计项目,提供了完整的代码实现和架构设计。关键词:Python爬虫、数据抓取、数据可视化、链家、贝壳、租房信息、毕业设计。

2201_76125261的博客 1206

Collabora Online架构揭秘:WebSocket实时通信与文档渲染技术

Collabora Online是一款基于LibreOffice技术构建的协作式在线办公套件,支持多用户实时编辑文档。本文将深入剖析其核心架构,重点解读WebSocket实时通信机制与高效文档渲染技术,带您了解这款开源协作办公工具如何实现流畅的多人协作体验。 ## WebSocket实时通信:协作编辑的神经中枢 在Collabora Online中,WebSocket技术是实现实时协作的核心。

gitblog_00615的博客 1020

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Android Systrace 基础知识(9)-MainThread 和 RenderThread 解读

本文是 Systrace 系列文章的第九篇,主要是是介绍 Android App 中的 MainThread 和 RenderThread,也就是大家熟悉的「主线程」和「渲染线程」。文章会从 Systrace 的角度来看 MainThread 和 RenderThread 的工作流程,以及涉及到的相关知识:卡顿、软件渲染、掉帧计算等 本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲

Gracker的专栏 2205

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Android 源码 图形系统之硬件渲染器同步和绘制帧

在《Android 源码 图形系统之硬件渲染器绘制》一节中没有分析 nSyncAndDrawFrame(…) 同步和绘制帧,这一节继续分析。 frameworks/base/core/java/android/view/ThreadedRenderer.java public class ThreadedRenderer extends HardwareRenderer { ...... @Override void draw(View view, AttachInfo attach

洪伟的专栏 4071

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

android高级开发面试!带你一起探究Android事件分发机制,算法太TM重要了_安卓开发面试分发事件

1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。:测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。OnLayout():确定View位置,进行页面布局。

2401_84558590的博客 442
上一篇: Ext4文件系统介绍 - 理论篇
下一篇: Ext4文件系统介绍 - 实战篇
OS技术解密
博客等级 码龄16年 3760粉丝 189原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值