文档章节

Android性能优化之GraphicsStatsService(2)

西皇小明
 西皇小明
发布于 2017/07/10 09:39
字数 3589
阅读 65
收藏 0
点赞 0
评论 0

这篇博客参考老罗的文章:http://blog.csdn.net/luoshengyang/article/details/45601143。

1.JankTracker的初始化流程

上一篇博客详细分析了GraphicsStatsService的工作流程,还遗留了一个问题就是各种卡顿类型的信息具体是怎么统计的。在回答这个问题之前我们先来看另外一个疑问:

上一篇博客中说过,JankTracker是在initThreadLocal()中被初始化的:

void RenderThread::initThreadLocals() {
    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps);
    mTimeLord.setFrameInterval(frameIntervalNanos);
    initializeDisplayEventReceiver();
    mEglManager = new EglManager(*this);
    mRenderState = new RenderState(*this);
    mJankTracker = new JankTracker(frameIntervalNanos);
}

这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。

那么这initThreadLocal()怎么被调用的呢?让我们从硬件渲染的初始化流程开始说起

根据老罗的博客,Activity组件在创建的过程中,也就是在其生命周期函数onCreate的调用过程中,一般会通过调用另外一个成员函数 setContentView创建和初始化关联的窗口视图,最后通过调用ViewRoot类的成员函数setView完成这一过程。到了Android 4.0之后,ViewRoot类的名字改成了ViewRootImpl,它们的作用仍然一样的。       

Android应用程序UI硬件加速渲染环境的初始化过程是在ViewRootImpl类的成员函数setView开始,如下:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    ......

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......

                if (view instanceof RootViewSurfaceTaker) {
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                    if (mSurfaceHolderCallback != null) {
                        mSurfaceHolder = new TakenSurfaceHolder();
                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                    }
                }

                ......

                // If the application owns the surface, don't enable hardware acceleration
                if (mSurfaceHolder == null) {
                    enableHardwareAcceleration(attrs);
                }

                ......
            }
        }
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。

参数view描述的是当前正在创建的Activity窗口的顶级视图。如果它实现了RootViewSurfaceTaker接口,并且通过该接口的成 员函数willYouTakeTheSurface提供了一个SurfaceHolder.Callback2接口,那么就表明应用程序想自己接管对窗口 的一切渲染操作。这样创建出来的Activity窗口就类似于一个SurfaceView一样,完全由应用程序自己来控制它的渲染。

       基本上我们是不会将一个Activity窗口当作一个SurfaceView来使用的,因此在ViewRootImpl类的成员变量 mSurfaceHolder将保持为null值,这样就会导致ViewRootImpl类的成员函数 enableHardwareAcceleration被调用为判断是否需要为当前创建的Activity窗口启用硬件加速渲染。

private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
        mAttachInfo.mHardwareAccelerated = false;
        mAttachInfo.mHardwareAccelerationRequested = false;

        // Don't enable hardware acceleration when the application is in compatibility mode
        if (mTranslator != null) return;

        // Try to enable hardware acceleration if requested
        final boolean hardwareAccelerated =
                (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;

        if (hardwareAccelerated) {
            if (!HardwareRenderer.isAvailable()) {
                return;
            }

            // Persistent processes (including the system) should not do
            // accelerated rendering on low-end devices.  In that case,
            // sRendererDisabled will be set.  In addition, the system process
            // itself should never do accelerated rendering.  In that case, both
            // sRendererDisabled and sSystemRendererDisabled are set.  When
            // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
            // can be used by code on the system process to escape that and enable
            // HW accelerated drawing.  (This is basically for the lock screen.)

            final boolean fakeHwAccelerated = (attrs.privateFlags &
                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
            final boolean forceHwAccelerated = (attrs.privateFlags &
                    WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;

            if (fakeHwAccelerated) {
                // This is exclusively for the preview windows the window manager
                // shows for launching applications, so they will look more like
                // the app being launched.
                mAttachInfo.mHardwareAccelerationRequested = true;
            } else if (!HardwareRenderer.sRendererDisabled
                    || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
                if (mAttachInfo.mHardwareRenderer != null) {
                    mAttachInfo.mHardwareRenderer.destroy();
                }

                final boolean translucent = attrs.format != PixelFormat.OPAQUE;
                mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
                if (mAttachInfo.mHardwareRenderer != null) {
                    mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
                    mAttachInfo.mHardwareAccelerated =
                            mAttachInfo.mHardwareAccelerationRequested = true;
                }
            }
        }
    }

这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。

这里面的大部分操作都是在处理是否开始硬件加速。虽然硬件加速渲染是个好东西,但是也不是每一个需要绘制UI的进程都必需的。这样做是考虑到两个因素。第一个因素是并不是所有的Canvas API都可以被GPU支持。如果应用程序使用到了这些不被GPU支持的API,那么就需要禁用硬件加速渲染。第二个因素是支持硬件加速渲染的代价是增加了 内存开销。例如,只是硬件加速渲染环境初始化这一操作,就要花掉8M的内存。所以有的进程就不适合开启硬件加速,主要是Persistent进程和System进程,详细看老罗博客。

上述代码中重点的是这句:

mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);

如果当前创建的窗口支持硬件加速渲染,那么就会调用HardwareRenderer类的静态成员函数create创建一个 HardwareRenderer对象,并且保存在与该窗口关联的一个AttachInfo对象的成员变量mHardwareRenderer 中。这个HardwareRenderer对象以后将负责执行窗口硬件加速渲染的相关操作。

那么这个HardwareRenderer是何方神圣呢?让我们继续往下看。

public abstract class HardwareRenderer {
    ......

    static HardwareRenderer create(Context context, boolean translucent) {
        HardwareRenderer renderer = null;
        if (GLES20Canvas.isAvailable()) {
            renderer = new ThreadedRenderer(context, translucent);
        }
        return renderer;
    }

    ......
}

这个函数定义在文件frameworks/base/core/java/android/view/HardwareRenderer.java。

可以看到,如果当前设备支持GLES2.0,才能开启硬件加速,通过ThreadedRenderer来完成这个加速任务。这个类继承于HardwareRenderer。这意思着Android硬件加速目前只支持GLES?

接下来我们就继续分析ThreadedRenderer对象的创建过程,如下所示:

public class ThreadedRenderer extends HardwareRenderer {
    ......

    private long mNativeProxy;
    ......
    private RenderNode mRootNode;
    ......

    ThreadedRenderer(Context context, boolean translucent) {
        ......

        long rootNodePtr = nCreateRootRenderNode();
        mRootNode = RenderNode.adopt(rootNodePtr);
        ......
        mNativeProxy = nCreateProxy(translucent, rootNodePtr);

        AtlasInitializer.sInstance.init(context, mNativeProxy);

        ......
    }

    ......
}

可以看到,ThreadedRenderer在构造函数中主要干了三件事,新建了一个RootRenderNode,一个ThreadProxy和调用AtlasInitializer进行资源集的初始化。

这里我们先看看第二个,因为这玩意儿上一篇博客我们好像看过。很明显这是一个JNI调用,Native层对应的实现是:

static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
        jboolean translucent, jlong rootRenderNodePtr) {
    RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
    ContextFactoryImpl factory(rootRenderNode);
    return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
}

这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。

这里利用了上面生成的rootRenderNode来生成了一个RenderProxy,看来这个rootRenderNode还是挺重要的,以后有机会再看吧,先看看RenderProxy的构造函数:

RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, 
     IContextFactory* contextFactory)
        : mRenderThread(RenderThread::getInstance())
        , mContext(0) {
    SETUP_TASK(createContext);
    args->translucent = translucent;
    args->rootRenderNode = rootRenderNode;
    args->thread = &mRenderThread;
    args->contextFactory = contextFactory;
    mContext = (CanvasContext*) postAndWait(task);
    mDrawFrameTask.setContext(&mRenderThread, mContext);
}

这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。

RenderProxy类有三个重要的成员变量mRenderThread、mContext和mDrawFrameTask,它们的类型分别为 RenderThread、CanvasContext和DrawFrameTask。其中,mRenderThread描述的就是Render Thread,mContext描述的是一个画布上下文,mDrawFrameTask描述的是一个用来执行渲染任务的Task。接下来我们先重点分析RenderThread的初始化过程。

从构造函数中可以看出,mRenderThread的默认值是RenderThread::getInstance(),这个应该是个单例模式,也就是说在一个Android应用程序进程中,只有一个Render Thread存在。

继续看RenderThread的构造过程:

RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
        ...... {
    mFrameCallbackTask = new DispatchFrameCallbacks(this);
    mLooper = new Looper(false);
    run("RenderThread");
}

这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。

这里同样也干了三件事情,新建了一个DispatchFrameCallbacks,一个Looper和调用了run函数。

 DispatchFrameCallbacks对象,用来描述一个帧绘制任务。下面描述RenderThread的运行模型时,我们再详细分析。RenderThread类的成员变量mLooper指向一个Looper对象,RenderThread通过它来创建一个消息驱动运行模型,类似于Main Thread的消息驱动运行模型。

RenderThread类是从Thread类继承下来的,当我们调用它的成员函数run的时候,就会创建一个新的线程。这个新的线程的入口点函数为RenderThread类的成员函数threadLoop,它的实现如下所示:

bool RenderThread::threadLoop() {
    .......
    initThreadLocals();

    int timeoutMillis = -1;
    for (;;) {
        int result = mLooper->pollOnce(timeoutMillis);
        ......

        nsecs_t nextWakeup;
        // Process our queue, if we have anything
        while (RenderTask* task = nextTask(&nextWakeup)) {
            task->run();
            // task may have deleted itself, do not reference it again
        }
        if (nextWakeup == LLONG_MAX) {
            timeoutMillis = -1;
        } else {
            nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
            if (timeoutMillis < 0) {
                timeoutMillis = 0;
            }
        }

        if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
            drainDisplayEventQueue(true);
            mFrameCallbacks.insert(
                    mPendingRegistrationFrameCallbacks.begin(), 
             mPendingRegistrationFrameCallbacks.end());
            mPendingRegistrationFrameCallbacks.clear();
            requestVsync();
        }
    }

    return false;
}

这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderThread.cpp中。

在这里终于看了我们索要找的initThreadLocals()!很激动有木有~也就是说在硬件初始化渲染的时候,即当一个窗口视图初始化的时候,其对应的JankTracker就已经被建立起来了,等到窗口视图真正渲染的时候,用来统计渲染信息。

2.渲染统计信息的收集过程

接上一篇博客,要弄懂渲染信息的收集过程,我们就得知道JankTracker::addFrame是在哪里被调用的。首先让我们回头去看看这个函数:

void JankTracker::addFrame(const FrameInfo& frame) {
    mData->totalFrameCount++;
    using namespace FrameInfoIndex;
    // Fast-path for jank-free frames
    int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync];
    uint32_t framebucket = frameCountIndexForFrameTime(
            totalDuration,  (sizeof(mData->frameCounts) / sizeof(mData->frameCounts[0])) );
    //keep the fast path as fast as possible
    if (CC_LIKELY(totalDuration < mFrameInterval)) {
        mData->frameCounts[framebucket]++;
        return;
    }

    //exempt this frame, so drop it
    if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) {
        return;
    }

    mData->frameCounts[framebucket]++;
    mData->jankFrameCount++;

    for (int i = 0; i < NUM_BUCKETS; i++) {
        int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start];
        if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
            mData->jankTypeCounts[i]++;
        }
    }
}

我们可以发现,这个函数有一个重要的参数FrameInfo类型的frame!这好像还是个数组。统计信息就是直接从它身上取出来的。赶紧地,我们去看看这个类:

class FrameInfo {
public:
    void importUiThreadInfo(int64_t* info);

    void markSyncStart() {
        mFrameInfo[FrameInfoIndex::kSyncStart] = systemTime(CLOCK_MONOTONIC);
    }

    void markIssueDrawCommandsStart() {
        mFrameInfo[FrameInfoIndex::kIssueDrawCommandsStart] = systemTime(CLOCK_MONOTONIC);
    }

    void markSwapBuffers() {
        mFrameInfo[FrameInfoIndex::kSwapBuffers] = systemTime(CLOCK_MONOTONIC);
    }

    void markFrameCompleted() {
        mFrameInfo[FrameInfoIndex::kFrameCompleted] = systemTime(CLOCK_MONOTONIC);
    }

    int64_t operator[](FrameInfoIndexEnum index) const {
        if (index == FrameInfoIndex::kNumIndexes) return 0;
        return mFrameInfo[static_cast<int>(index)];
    }

    int64_t operator[](int index) const {
        if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0;
        return mFrameInfo[static_cast<int>(index)];
    }

private:
    int64_t mFrameInfo[FrameInfoIndex::kNumIndexes];
};

这个类定义在frameworks/base/libs/hwui/FrameInfo.h文件中

可以看到,FrameInfo里面有很多markXXXStart的函数,这些函数的功能都是同样的,就是记录系统现在的时间放在mFrameInfo的不同位置中!很明显,这是在记录一个帧每一个渲染阶段的开始时间,以便后来做卡顿的统计。

同时在同一个文件中,还有另外一个类UiFrameInfoBuilder

class ANDROID_API UiFrameInfoBuilder {
public:
    UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
        memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
    }

    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
        mBuffer[FrameInfoIndex::kVsync] = vsyncTime;
        mBuffer[FrameInfoIndex::kIntendedVsync] = intendedVsync;
        return *this;
    }

    UiFrameInfoBuilder& addFlag(FrameInfoFlagsEnum flag) {
        mBuffer[FrameInfoIndex::kFlags] |= static_cast<uint64_t>(flag);
        return *this;
    }

private:
    int64_t* mBuffer;
};

同样这里的setVsync函数记录了kVsync和kIntendedVsync的时间。因此,现在的问题就变成了这个函数和上面的那些markXXXStart函数是在什么时候被调用的?

通过搜索,我们可以发现,这些函数都是在同一个类中被调用的,那就是CanvasContext!主要涉及到两个函数:CanvasContext::prepareTree和CanvasContext::draw。下面我们看看这个三个函数,我们按时间的先后一个个看:

// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
   ...
    int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
    UiFrameInfoBuilder(frameInfo)
        .addFlag(FrameInfoFlags::kRTAnimation)
        .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
                mRenderThread.timeLord().latestVsync());

    TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
    prepareTree(info, frameInfo);
    if (info.out.canDrawThisFrame) {
        draw();
    }
}

这个doFrame函数定义在frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。

该函数首先新建了一个frameInfo数组来存放帧的各种渲染信息,大小是UI_THREAD_FRAME_INFO_SIZE,其实是9,具体的定义如下:

#define UI_THREAD_FRAME_INFO_SIZE 9

HWUI_ENUM(FrameInfoIndex,
    kFlags = 0,
    kIntendedVsync,
    kVsync,
    kOldestInputEvent,
    kNewestInputEvent,
    kHandleInputStart,
    kAnimationStart,
    kPerformTraversalsStart,
    kDrawStart,
    // End of UI frame info

    kSyncStart,
    kIssueDrawCommandsStart,
    kSwapBuffers,
    kFrameCompleted,

    // Must be the last value!
    kNumIndexes
);

这个枚举定义在frameworks/base/libs/hwui/FrameInfo.h中

这个9其实就是下面的HWUI_ENUM枚举类型前9个的意思,可以从注释中看到,这9个是UI帧的信息。

然后doFrame调用了UiFrameInfoBuilder来添加两个重要的时间,kVsync和kIntendedVsync,这两个是都是通过调用mRenderThread.timeLord()的相关函数来获得。再接着定义了一个TreeInfo类型的info,并和frameInfo传给了prepareTree。下面我们看看prepareTree。

void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo) {
    mRenderThread.removeFrameCallback(this);

    mCurrentFrameInfo = &mFrames.next();
    mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
    mCurrentFrameInfo->markSyncStart();

    info.damageAccumulator = &mDamageAccumulator;
    info.renderer = mCanvas;
    if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) {
        info.canvasContext = this;
    }
    mAnimationContext->startFrame(info.mode);
    mRootRenderNode->prepareTree(info);
    mAnimationContext->runRemainingAnimations(info);

    if (info.canvasContext) {
        freePrefetechedLayers();
    }
    ...
    int runningBehind = 0;
    // TODO: This query is moderately expensive, investigate adding some sort
    // of fast-path based off when we last called eglSwapBuffers() as well as
    // last vsync time. Or something.
    mNativeWindow->query(mNativeWindow.get(),
            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
    info.out.canDrawThisFrame = !runningBehind;

    if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
        if (!info.out.requiresUiRedraw) {
            // If animationsNeedsRedraw is set don't bother posting for an RT anim
            // as we will just end up fighting the UI thread.
            mRenderThread.postFrameCallback(this);
        }
    }
}

这个函数定义在frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中,

这个函数首先调用mFrames.next()取出了一个mCurrentFrameInfo,然后通过importUiThreadInfo导入传过来的那个uiFrameInfo,然后调用markSyncStart()记录了SyncStart的时间点,表明Sync已经开始了,紧接着才开始进行真正的业务处理,例如处理一些相关的动画,和调用mRootRenderNode->prepareTree(info)来真正准备ViewTree,这应该是一个上传视图到GPU的过程。

prepareTree完成了以后,返回到frameInfo中,如果这个准备是成功的,下面就可以调用Draw函数马上开始绘制啦:

void CanvasContext::draw() {
    ...
    mCurrentFrameInfo->markIssueDrawCommandsStart();

    SkRect dirty;
    mDamageAccumulator.finish(&dirty);

    EGLint width, height;
    mEglManager.beginFrame(mEglSurface, &width, &height);
    if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
        mCanvas->setViewport(width, height);
        dirty.setEmpty();
    } else if (!mBufferPreserved || mHaveNewSurface) {
        dirty.setEmpty();
    } else {
        if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
            ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
                    SK_RECT_ARGS(dirty), width, height);
            dirty.setEmpty();
        }
        profiler().unionDirty(&dirty);
    }

    status_t status;
    if (!dirty.isEmpty()) {
        status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
                dirty.fRight, dirty.fBottom, mOpaque);
    } else {
        status = mCanvas->prepare(mOpaque);
    }

    Rect outBounds;
    status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);

    profiler().draw(mCanvas);

    mCanvas->finish();

    profiler().markPlaybackEnd();

    // Even if we decided to cancel the frame, from the perspective of jank
    // metrics the frame was swapped at this point
    mCurrentFrameInfo->markSwapBuffers();

    if (status & DrawGlInfo::kStatusDrew) {
        swapBuffers();
    } else {
        mEglManager.cancelFrame();
    }

    // TODO: Use a fence for real completion?
    mCurrentFrameInfo->markFrameCompleted();
    mJankTracker.addFrame(*mCurrentFrameInfo);
    mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
    profiler().finishFrame();
}

这个函数定义在frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中,

一进来,就调用了markIssueDrawCommandsStart()记录绘制命令开始的时间,然后做了一大堆的工作,都是在设置一个dirty和mCanvas,弄好了以后调用mCanvas->drawRenderNode、profiler().draw(mCanvas),mCanvas->finish()标志着绘制工作的完成。

随后,调用markSwapBuffers()来记录开始交换缓冲区的时间,这是要开始显示了么?接着调用swapBuffers()来进行真正的缓冲交换工作,交换结束以后,这一个帧的绘制就全部完成了,调用markFrameCompleted()来记录绘制的结束时间,最后这些记录在mCurrentFrameInfo的帧信息添加到我们的mJankTracker中,这样,JankTracker就能统计渲染信息啦。

这样,大部分的时间节点的记录已经是清除的了,除了两个:kVsync和kIntendedVsync,前面说过,这两个函数是由mRenderThread.timeLord().computeFrameTimeNanos()和mRenderThread.timeLord(). latestVsync()取得的。

nsecs_t TimeLord::computeFrameTimeNanos() {
    // Logic copied from Choreographer.java
    nsecs_t now = systemTime(CLOCK_MONOTONIC);
    nsecs_t jitterNanos = now - mFrameTimeNanos;
    if (jitterNanos >= mFrameIntervalNanos) {
        nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
        mFrameTimeNanos = now - lastFrameOffset;
    }
    return mFrameTimeNanos;
}

这个函数定义在frameworks/base/libs/hwui/renderthread/TimeLord.cpp

可以看到这个函数除了特殊情况下做了修正之外,是直接返回mFrameTimeNanos的。所以要看一下这个mFrameTimeNanos是怎么被赋值的。就在同一个文件中:

bool TimeLord::vsyncReceived(nsecs_t vsync) {
    if (vsync > mFrameTimeNanos) {
        mFrameTimeNanos = vsync;
        return true;
    }
    return false;
}

mFrameTimeNanos被初始化为0,所以当这个函数被调用的时候,vsync > mFrameTimeNanos成立,所以mFrameTimeNanos被赋值为vsync,从函数名我们可以知道mFrameTimeNanos记录的就是接收到vsync的时间,即kVsync的时间。

 

© 著作权归作者所有

共有 人打赏支持
西皇小明
粉丝 5
博文 40
码字总数 20601
作品 0
海淀
程序员
Android性能优化:手把手教你如何让App更快、更稳、更省(含内存、布局优化等)

前言 在 开发中,性能优化策略十分重要 因为其决定了应用程序的开发质量:可用性、流畅性、稳定性等,是提高用户留存率的关键 本文全面讲解性能优化中的所有知识,献上一份 性能优化的详细攻...

Carson_Ho ⋅ 05/30 ⋅ 0

Android 性能优化:手把手教你优化Bitmap图片资源的使用

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的Bitmap 使用优化,希望你们会喜欢 目录 1. 优化原因 即 为什么要优化图片资源,具体如下图:

Carson_Ho ⋅ 04/24 ⋅ 0

Android性能优化:那些不可忽略的绘制优化

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的绘制优化,希望你们会喜欢。 目录 // 方式2:在 BaseActivity 的 onCreate() 方法中使用下面的代码移除 优化方案2:移除 控件...

Carson_Ho ⋅ 05/21 ⋅ 0

Android性能优化:这是一份详细的布局优化 指南(含、、)

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的布局优化,希望你们会喜欢。 目录 /** 实例说明:在上述例子,在布局B中 通过标签引用布局C 此时:布局层级为 = RelativeLa...

Carson_Ho ⋅ 05/14 ⋅ 0

Android Studio 3.2 Canary 发布,新增大量实用功能

在今天的 Google 2018 I/O 大会上,释出了 Android Studio 3.2 的最新预览版,并带来了一系列的新功能,如支持 Android P 开发预览版、新的 Android App Bundle,以及 Android Jetpack。官方...

局长 ⋅ 05/09 ⋅ 0

Android性能优化:手把手带你全面实现内存优化

前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录 1. 定义 优化处理 应用程序的内存使用、空间占用 2. 作用 避免因不正确使用内存 & 缺...

codeGoogle ⋅ 05/08 ⋅ 0

android手机卫士、3D指南针、动画精选、仿bilibli客户端、身份证银行卡识别等源码

Android精选源码 android身份证、银行卡号扫描源码(http://www.apkbus.com/thread-599859-1-1.html) android仿bilibili客户端(http://www.apkbus.com/thread-599860-1-1.html) android一款3......

逆鳞龙 ⋅ 06/04 ⋅ 0

Android性能分析工具简介

在Android项目开发工程中,功能开发只是其中的一部分,更多的时候是优化,优化除了个人的良好习惯,往往还需要借助第三方工具。本文罗列Android优化过程中的一些常用工具借助这些工具,可以很...

code_xzh ⋅ 05/02 ⋅ 0

系统角度解读Android P新特性

  先做个安利,大家都知道我们之前开源了一个AspectJ的封装库,现在,AspectJX 要了一次重大版本更新 —— 2.0.0 !!!   * 支持Instant Run编译   * 废弃 includeJarFilter和exclude...

Android群英传 ⋅ 04/25 ⋅ 0

Android P Beta 2 及终版 API 强势来袭!

在四周前的 Google I/O 开发者大会上,我们发布了Android P 的首个 Beta 版,将人工智能 (AI) 定位为操作系统的核心,并侧重于提供智能且简洁的体验。 今天,我们隆重推出 Android P Beta 2...

谷歌开发者 ⋅ 06/07 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

熊掌号收录比例对于网站原创数据排名的影响[图]

从去年下半年开始,我在写博客了,因为我觉得业余写写博客也还是很不错的,但是从2017年下半年开始,百度已经推出了原创保护功能和熊掌号平台,为此,我也提交了不少以前的老数据,而这些历史...

原创小博客 ⋅ 34分钟前 ⋅ 0

LVM讲解、磁盘故障小案例

LVM LVM就是动态卷管理,可以将多个硬盘和硬盘分区做成一个逻辑卷,并把这个逻辑卷作为一个整体来统一管理,动态对分区进行扩缩空间大小,安全快捷方便管理。 1.新建分区,更改类型为8e 即L...

蛋黄Yolks ⋅ 52分钟前 ⋅ 0

Hadoop Yarn调度器的选择和使用

一、引言 Yarn在Hadoop的生态系统中担任了资源管理和任务调度的角色。在讨论其构造器之前先简单了解一下Yarn的架构。 上图是Yarn的基本架构,其中ResourceManager是整个架构的核心组件,它负...

p柯西 ⋅ 今天 ⋅ 0

uWSGI + Django @ Ubuntu

创建 Django App Project 创建后, 可以看到路径下有一个wsgi.py的问题 uWSGI运行 直接命令行运行 利用如下命令, 可直接访问 uwsgi --http :8080 --wsgi-file dj/wsgi.py 配置文件 & 运行 [u...

袁祾 ⋅ 今天 ⋅ 0

JVM堆的理解

在JVM中,我们经常提到的就是堆了,堆确实很重要,其实,除了堆之外,还有几个重要的模块,看下图: 大 多数情况下,我们并不需要关心JVM的底层,但是如果了解它的话,对于我们系统调优是非常...

不羁之后 ⋅ 昨天 ⋅ 0

推荐:并发情况下:Java HashMap 形成死循环的原因

在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环。这个事情我4、5年前也经历...

码代码的小司机 ⋅ 昨天 ⋅ 1

聊聊spring cloud gateway的RetryGatewayFilter

序 本文主要研究一下spring cloud gateway的RetryGatewayFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/config/G......

go4it ⋅ 昨天 ⋅ 0

创建新用户和授予MySQL中的权限教程

导读 MySQL是一个开源数据库管理软件,可帮助用户存储,组织和以后检索数据。 它有多种选项来授予特定用户在表和数据库中的细微的权限 - 本教程将简要介绍一些选项。 如何创建新用户 在MySQL...

问题终结者 ⋅ 昨天 ⋅ 0

android -------- 颜色的半透明效果配置

最近有朋友问我 Android 背景颜色的半透明效果配置,我网上看资料,总结了一下, 开发中也是常常遇到的,所以来写篇博客 常用的颜色值格式有: RGB ARGB RRGGBB AARRGGBB 这4种 透明度 透明度...

切切歆语 ⋅ 昨天 ⋅ 0

CentOS开机启动subversion

建立自启动脚本: vim /etc/init.d/subversion 输入如下内容: #!/bin/bash## subversion startup script for the server## chkconfig: 2345 90 10# description: start the subve......

随风而飘 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部