首页 > 其他分享 >Android View绘制原理 - RenderThread

Android View绘制原理 - RenderThread

时间:2023-08-07 11:04:36浏览次数:44  
标签:std return void toProcess RenderThread threadLoop Android View

前面的文章介绍了HardwareRendere在初始化的时候,涉及到了一个组件RenderThread并简要的分析了一下,这篇文章将继续深入的分析一下这个RenderThread,介绍一下它的几个重要特性和功能

1 Thread

RenderThread首先是继承自ThreadBase,是一个真实的线程。 frameworks/base/libs/hwui/renderthread/RenderThread.h

class RenderThread : private ThreadBase {
    PREVENT_COPY_AND_ASSIGN(RenderThread);

frameworks/base/libs/hwui/thread/ThreadBase.h

ThreadBase()
            : Thread(false)
            , mLooper(new Looper(false))
            , mQueue([this]() { mLooper->wake(); }, mLock) {}

ThreadBase是一个hwui中定义的一个线程类,它由一个消息循环Looper,工作队列mQueue,和一个锁mLock组成,继承自util/Thread,通过系统调用pthread_create创建的线程,它的入口是一个无限循环函数_threadLoop.。

system/core/libutils/Threads.cpp

res = androidCreateRawThreadEtc(_threadLoop,
                this, name, priority, stack, &mThread);
int Thread::_threadLoop(void* user) {
    Thread* const self = static_cast<Thread*>(user);
    sp<Thread> strong(self->mHoldSelf);
     ...
    do {
           bool result;
            result = self->threadLoop();
        }
   ...
    } while(strong != nullptr);

    return 0;
}

每次循环都去执行threadLoop方法。在hwui的ThreadBase里面实现了这方法。 frameworks/base/libs/hwui/thread/ThreadBase.h

virtual bool threadLoop() override {
        Looper::setForThread(mLooper);
        while (!exitPending()) {
            waitForWork();
            processQueue();
        }
        Looper::setForThread(nullptr);
        return false;
    }

在第一次调用threadLoop的时候,会进入到ThreadBase自身的一个无限循环中,所以threadLoop只会执行一次。 在这一次执行中,会给当前的线程绑定一个Looper。在ThreadBase中会去调用waitForWork等到looper中的消息,收到消息线程被唤醒后,执行processQueue。处理任务队列中的任务。

void waitForWork() {
        nsecs_t nextWakeup;
        {
            std::unique_lock lock{mLock};
            nextWakeup = mQueue.nextWakeup(lock);
        }
        int timeout = -1;
        if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
            timeout = ns2ms(nextWakeup - WorkQueue::clock::now());
            if (timeout < 0) timeout = 0;
        }
        int result = mLooper->pollOnce(timeout);
        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR, "RenderThread Looper POLL_ERROR!");
    }

这里会一直阻塞到mLooper->pollOnce返回。之后就执行processQueue处理任务队列mQueue. 任务队列的类型是WorkQueue

void processQueue() { mQueue.process(); }

frameworks/base/libs/hwui/thread/WorkQueue.h

void process() {
        auto now = clock::now();
        std::vector<WorkItem> toProcess;
        {
            std::unique_lock _lock{mLock};
            if (mWorkQueue.empty()) return;
            toProcess = std::move(mWorkQueue);
            auto moveBack = find_if(std::begin(toProcess), std::end(toProcess),
                                    [&now](WorkItem& item) { return item.runAt > now; });
            if (moveBack != std::end(toProcess)) {
                mWorkQueue.reserve(std::distance(moveBack, std::end(toProcess)) + 5);
                std::move(moveBack, std::end(toProcess), std::back_inserter(mWorkQueue));
                toProcess.erase(moveBack, std::end(toProcess));
            }
        }
        for (auto& item : toProcess) {
            item.work();
        }
    }

这个队列筛选出所有可以处理的WorkItem(runAt < now),然后再循环处理它们。当有任务需要交给这个线程执行的时候,可以获取到这个mQueue,然后调用添加任务的方法,比如post方法

template <class F>
    void postAt(nsecs_t time, F&& func) {
        enqueue(WorkItem{time, std::function<void()>(std::forward<F>(func))});
    }

然后调用到入队的enqueue方法

void enqueue(WorkItem&& item) {
        bool needsWakeup;
        {
            std::unique_lock _lock{mLock};
            auto insertAt = std::find_if(
                    std::begin(mWorkQueue), std::end(mWorkQueue),
                    [time = item.runAt](WorkItem & item) { return item.runAt > time; });
            needsWakeup = std::begin(mWorkQueue) == insertAt;
            mWorkQueue.emplace(insertAt, std::move(item));
        }
        if (needsWakeup) {
            mWakeFunc();
        }
    }

如果新的这任务放到队列的头部的话,就调用mWakeFunc方法,这个方法是构造Queue的时候传入的,回看上面ThreadBase的构造方法,传入的是 this { mLooper->wake()},因此会唤醒mLooper的pollOnce,从而线程开始处理工作队列,整个RenderThread线程的执行流程就理顺了,跟java层的HandlerThread很相似。

2 threadLoop

RenderThread 重写了threadLoop方法,所以执行的是它自己的threadLoop方法

bool RenderThread::threadLoop() {
    ...
    initThreadLocals();
    while (true) {
        waitForWork();
        processQueue();
        ...
        if (!mFrameCallbackTaskPending && !mVsyncRequested && mFrameCallbacks.size()) {
            requestVsync();
        }
    }

    return false;
}

主要的流程和父类的threadLoop差不多,只是这里多了几个处理,一个是初始化,另外一个是处理完工作队列后,在一定条件下调用requestVsync,也就是说再处理完一个绘制任务后,再RenderThread里也可能会请求vsync信号,来看一下RenderThread的Vsync机制。

void RenderThread::initThreadLocals() {
    setupFrameInterval();
    initializeChoreographer();
    mEglManager = new EglManager();
    mRenderState = new RenderState(*this);
    mVkManager = VulkanManager::getInstance();
    mCacheManager = new CacheManager();
}
void RenderThread::setupFrameInterval() {
    nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
    mTimeLord.setFrameInterval(frameIntervalNanos);
    mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}

setupFrameInterval比较简单,它读取的 DeviceInfo::getVsyncPeriod(),这个是在Java层初始化HardwareRenderer的时候设置到C层。接着调用initializeChoreographer初始话C层的Choreographer. 那这里就是关于Vsync的逻辑了。

void RenderThread::initializeChoreographer() {
        ...
        mChoreographer = AChoreographer_create();
        LOG_ALWAYS_FATAL_IF(mChoreographer == nullptr, "Initialization of Choreographer failed");
        AChoreographer_registerRefreshRateCallback(mChoreographer,
                                                   RenderThread::refreshRateCallback, this);

        // Register the FD
        mLooper->addFd(AChoreographer_getFd(mChoreographer), 0, Looper::EVENT_INPUT,
                       RenderThread::choreographerCallback, this);
        mVsyncSource = new ChoreographerSource(this);
}

通过AChoreographer_create方法,创建一个C层的Choreographer对象,并调用AChoreographer_registerRefreshRateCallback注册刷新率更新的回调,当屏幕刷新率变化之后调用setupFrameInterval方法。然后让当前RenderThread的looper去观察mChoreographer文件描述符,如果由输入事件,则调用choreographerCallback方法。最后生成一个ChoreographerSource对象。C层的AChoreographer处理VSync的流程我们先不看,我们来看看什么RenderThread需要监听Vsync。 在threadLoop中,当执行完一次任务后(processQueue),会判断是否需要requestVsync。它的条件是

if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
            mVsyncSource->drainPendingEvents();
            mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
                                   mPendingRegistrationFrameCallbacks.end());
            mPendingRegistrationFrameCallbacks.clear();
            requestVsync();
        }

也就是当mPendingRegistrationFrameCallbacks不为空的时候且还没有执行回调的,会去requestVsync。 这是因为processQueue仅仅只完成了CPU的工作,GPU和HWComposer是否完成显示是未知的,而mPendingRegistrationFrameCallbacks是延迟绘制任务,它通常是由动画导致的,需要等到一帧显示完毕才能回调,因此这里就需要去监听Vsync,收到Vsync之后才去回调callback。RenderThread提供注册的方法:

void RenderThread::postFrameCallback(IFrameCallback* callback) {
    mPendingRegistrationFrameCallbacks.insert(callback);
}

因此如果没有mPendingRegistrationFrameCallbacks的话,EventThread就不需要请求Vsync了

3 requireGlContext

这个方法是准备GPU绘制的上下文,需要在绘制之前准备好向GPU提交数据的相关能力

void RenderThread::requireGlContext() {
    if (mEglManager->hasEglContext()) {
        return;
    }
    mEglManager->initialize();

    sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
    LOG_ALWAYS_FATAL_IF(!glInterface.get());

    GrContextOptions options;
    initGrContextOptions(options);
    auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
    auto size = glesVersion ? strlen(glesVersion) : -1;
    cacheManager().configureContext(&options, glesVersion, size);
    sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
    LOG_ALWAYS_FATAL_IF(!grContext.get());
    setGrContext(grContext);
}
  • mEglManager->initialize();加载EGL驱动,初始化EGL
  • glInterface(GrGLCreateNativeInterface()),GrGLCreateNativeInterface()里封装所有的openGL接口external/skia/src/gpu/gl/egl/GrGLMakeEGLInterface.cpp
sk_sp<const GrGLInterface> GrGLMakeEGLInterface() {
    return GrGLMakeAssembledInterface(nullptr, egl_get_gl_proc);
}

static GrGLFuncPtr egl_get_gl_proc(void* ctx, const char name[]) {
    ...
    #define M(X) if (0 == strcmp(#X, name)) { return (GrGLFuncPtr) X; }
    M(eglGetCurrentDisplay)
    M(eglQueryString)
    ....
    #undef M
    return eglGetProcAddress(name);
 }
  • grContext(GrDirectContext::MakeGL(std::move(glInterface), options)); 创建GPU绘制的上下文GrDirectContext,并初始化它的成员fGpu,它将负责向GPU提交数据。
sk_sp<GrDirectContext> GrDirectContext::MakeGL(sk_sp<const GrGLInterface> glInterface,
                                               const GrContextOptions& options) {
    sk_sp<GrDirectContext> direct(new GrDirectContext(GrBackendApi::kOpenGL, options));
    ...
    direct->fGpu = GrGLGpu::Make(std::move(glInterface), options, direct.get());
    if (!direct->init()) {
        return nullptr;
    }
    return direct;
}

到这里EventThread里就具备了请求GPU进行OpenGL渲染的能力了。

4 总结

本文主要介绍了EventThread这个组件相关的功能,主要包含了以下内容

  • 线程和任务队列原理
  • vsync机制
  • 初始化EGLManager,GrDirectContext,并创建好了向GPU提交数据的GrGLGpu对象。

标签:std,return,void,toProcess,RenderThread,threadLoop,Android,View
From: https://blog.51cto.com/u_16175630/6991477

相关文章

  • 详解直播应用源码Android端优质技术(三):可变比特率
     直播应用源码平台作为如今一个火爆的平台深受现代人的喜爱,而直播行业也是流行的媒体形式之一,所以不论是直播应用源码的观众用户还是作为直播应用源码的主播用户人数都是巨大的,并且用户地区涵盖了世界各个国家。这时候,直播应用源码平台就需要开发技术来去提高平台的稳定,提升平台......
  • 详解直播应用源码Android端优质技术(三):可变比特率
    直播应用源码平台作为如今一个火爆的平台深受现代人的喜爱,而直播行业也是流行的媒体形式之一,所以不论是直播应用源码的观众用户还是作为直播应用源码的主播用户人数都是巨大的,并且用户地区涵盖了世界各个国家。这时候,直播应用源码平台就需要开发技术来去提高平台的稳定,提升平台的质......
  • Google Review评价被删除了,怎么恢复?【附详细操作指引】
    自从2022年初,谷歌推出Googlereviews的人工智能AI评论过滤器之后,在删除大量虚假Googlereviews的同时,连同许多正常顾客的Google评价也一起删掉了,这种情况经常发生,这引起很多Googlebusiness商家强烈不满。 对此,今年Google终于发布一个关于Googlereviews的表单求助功能,帮助Google......
  • Android平台GB28181设备接入端如何降低资源占用和性能消耗
    背景我们在做GB28181设备接入模块的时候,考虑到好多设备性能一般,我们一般的设计思路是,先注册设备到平台侧,平台侧发calalog过来,获取设备信息,然后,设备侧和国标平台侧维持心跳,如果有位置订阅信息,按照订阅时间间隔,实时上报设备位置信息。如果本地没有录像诉求,或者,国标平台侧不发起invite......
  • 微信小程序视图容器 view
    <viewclass="section"><viewclass="section__title">flex-direction:row</view><viewclass="flex-wrp"style="flex-direction:row;"><viewclass="flex-itembc_green">1&l......
  • Android判断是否联网
    /***是否联网**@return*/publicstaticbooleanisConnected(Contextcontext){ConnectivityManagerconnManager=(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);if(co......
  • Android开发 Jetpack Compose 与xml的混合开发AndroidView
    前言  JetpackCompose虽然已经逐渐完善,但是其实还是有很多地方未满足需求。比如播放视频、相机预览等等依然需要原来的View。所以目前阶段JetpackCompose与xml的混合开发非常重要。  官方文档地址:https://developer.android.google.cn/jetpack/compose/migrate/interopera......
  • java-concurrent-interview-must
    10道不得不会的Java并发基础面试题以下都是Java的并发基础面试题,相信大家都会有种及眼熟又陌生的感觉、看过可能在短暂的面试后又马上忘记了。JavaPub在这里整理这些容易忘记的重点知识及解答,建议收藏,经常温习查阅。评论区见1.start()方法和run()方法的区别如果只是调用run(......
  • Android学习笔记(三):Andriod程序框架
    修改Eclipse的字体,我希望大一些,反正22寸的显示屏:Window->Preferences->General->Apprearance->ColorsandFonts->Java->JavaEditorTextFont(...)->Edit在此次,我们先创建一个Hello,Android的程序,并既而讨论Andriod的程序架构。1、创建project:File>New>Project>And......
  • Android学习笔记(三五):再谈Intent(下)-一些实践
    Android的UI框架要求用户将他们的app分为activity,通过itent来进行调度,其中有一个mainactivity由Android的launcher在桌面中调用。例如一个日历的应用,需要查看日历的activity,查看单个事件的activity,编辑事件的activity等等。在查看日历的activity中,如果用户选择的某个事件,需要通过......