基于Perfetto 解讀一幀的生產消費流程 Android >= S Qualcomm

廣告

首先幫我朋友打個廣告 我們一起在運營一個視頻號 感興趣的可以幫忙點擊右邊這個小鈴鐺 鈴鐺

1.這個流程里面的東西如果展開其實是有很多的 內容其實還是比較淺顯的 sf處就不貼源碼了 關一個Vsync就有的解釋 當然筆者在流程上先形成一個思維閉環
2.如有小伙伴需要 筆者可提供所有原材料供二次編輯

先吐槽
其實我覺得大部分Android開發者都是聚集在上層 java層 或者說的具體點就是業務層 app層 始終沒有脫離業務場景
我對應用開發范圍的定義是 不限于hal層 c++代碼實現層 只要涉及到業務場景的 都是應用開發
隨著工作中遇到的一些00后 水平是真的不錯 在這里也提醒那些80后90后 快了奧 小心被擠下來 逆水行舟 不進則退 出來混是要還的

本文闡述的預期
1.view的繪制流程 以及 送顯到屏幕一整個過程
2.trace的分析方法
3.因為很多看似一點思路都沒有的問題 其實是基礎不夠牢靠 希望筆者接下來的闡述 前期可以讓大家節省多的熟悉成本

一.從一個view的setText開始

1.1view開始setText

Button btnTraceClick = findViewById(R.id.btn_trace_click);btnTraceClick.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Trace.beginSection("super.yu click#btn test");btnTraceClick.setText("帥是內在 但騷不是");Trace.endSection();}
});

可以看到2處 是加上去的trace setText就從這里開始 是會走下去請求vsync-app 即app主線程有更新ui的請求 但此時沒有往下走 因為1處已經有一個requestNextVsync vsync-app的請求 等待sf進程回調上來 Choreographer#onVsync 告訴app可以doFrame 此時才會繪制 4處是線程運行狀態

如果長時間的runnable或runnable preempted或running狀態 60幀 超過16.6ms 那就可以看做是一個卡頓或掉幀 優化的思路可以是此處cpu有哪個進程運行時間較長 app線程得不到調度 負載較高 找對應模塊的人分析 或修改優先級 等 如果是system_server例如binder 鎖競爭 耗時 則要通過閱讀源碼去定位 或 app自身是否存在主線程耗時 出現諸如 下述log 考慮是否mainthread有耗時操作 ui結構過于復雜等等 思路不僅限于此 在Perfetto可以很直觀的看出來

I/Choreographer: Skipped 196 frames! The application may be doing too much work on its main thread. 

在這里插入圖片描述
分別對應2和3處

此時由于已經在1處requestNextVsync vsync-app請求 在2更新ui就不會往下走 所以只會有句scheduleTraversals 所以3處的onVsync回調其實是上一次ui更新請求的 所以ui的請求一直到屏幕顯示至少得在第二個vsync信號到來

在這里插入圖片描述
在這里插入圖片描述
我們從這里的vsync請求往下贅述也是對應1處
在這里插入圖片描述

/frameworks/base/core/java/android/view/ViewRootImpl.java#scheduleTraversals
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;// 發送一個屏障信號 下次loop來 doFramemTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 編舞者 post發送請求mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...
/frameworks/base/core/java/android/view/Choreographer.java#postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {...synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);// dueTime 肯定是大于或等于now 所以除了首次一個loop會直接走這里 其他情況會走下述的msgif (dueTime <= now) {scheduleFrameLocked(now);} else {Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, dueTime);}...
|
void doScheduleCallback(int callbackType) {synchronized (mLock) {if (!mFrameScheduled) {final long now = SystemClock.uptimeMillis();// 此處 肯定是在Choreographer注冊了一個回調 我們先不關注他 這個回調就是后面3Choreographer#onVsync 也就是 vsync-appif (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {scheduleFrameLocked(now);...
|
private void scheduleVsyncLocked() {try {// 這里的trace會和圖上一一對上Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");mDisplayEventReceiver.scheduleVsync();} ...
中間省略一步hal也就是DisplayEventDispatcher.java
/frameworks/native/libs/gui/DisplayEventDispatcher.cpp#scheduleVsync
status_t DisplayEventDispatcher::scheduleVsync() {if (!mWaitingForVsync) {ALOGV("dispatcher %p ~ Scheduling vsync.", this);...if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", this,ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));}// app層開始請求vsync 此處開啟binder請求status_t status = mReceiver.requestNextVsync();...

一步一步從java層 到c++ vsync-app 整個流程還是比較長的 大家可以看出 其實Java就是個殼子 他可以是kotlin可以是js也可以是Flutter 因為硬件屏幕刷新是固定 不需要每次都去校對硬件vsync當前是會否可以繪制 所以模擬一個軟件的信號源 在這里把代碼貼出來 可以看一下 其實就是代碼流程 當然怎么去注冊的 怎么回調的我們在此就不去深究了

此時3處回調vsync-app 也就是Choreographer#onVsync app就開始繪制了 其實繪制本質就是在組織結構體 組織繪制的命令 在這里我們留一個后續探究的點 也就是一個canvas gui的本質是什么 我們都知道java層的bitmap的draw api的調用方式 但java就是個殼子 真正繪制不是在這里 相當于aidl綁定兩個connection就可以通信 但實質是native指向server/client端一側的地址 從而實現進程通信的場景 所以 到底是gpu合成還是hwc合成 是有一個判斷依據的 對于高刷的場景 例如游戲 都是hwc合成 簡單的場景 走的是gpu合成 否則 gpu負載太高 功耗就會有增加 也是終端項目中需要check的地方

frameworks/native/libs/gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {if (mEventConnection != nullptr) {mEventConnection->requestNextVsync();return NO_ERROR;}return mInitError.has_value() ? mInitError.value() : NO_INIT;
}// EventThread這是軟件模擬硬件vsync 后續會講到
frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp
binder::Status EventThreadConnection::requestNextVsync() {ATRACE_CALL();mEventThread->requestNextVsync(this);return binder::Status::ok();
}

接下來再來個圖
在這里插入圖片描述
大家要注意的是 我們目前為止setText的渲染其實是1處右邊那代碼塊 此處還是之前的ui更新操作

1處ui線程ui就開始繪制了 值得注意的是 1處如果draw時長超過16.6ms那么大概率就是應用本身阻塞主線程 我們把trace堆棧放大
在這里插入圖片描述
這一步我理解是遍歷 比如animation input啊 有哪些 measure layout丈量等 是把ui結構進行數據化 比如這個view的坐標 color等

這里是引用一篇博客里面的解釋 但我的理解就是 為了后續的遍歷而去組織數據結構 分門別類
Choreographer.doFrame 計算掉幀邏輯
Choreographer.doFrame 處理 Choreographer 的第一個 callback : input
Choreographer.doFrame 處理 Choreographer 的第二個 callback : animation
Choreographer.doFrame 處理 Choreographer 的第三個 callback : insets animation
Choreographer.doFrame 處理 Choreographer 的第四個 callback : traversal

setRefreshRateIfNeed這個應該是手機廠家提供出的接口 不管 traversal 他就是遍歷 draw 就是 draw 著重介紹一下postAndWait 這里就會到RenderThread 應用的渲染線程 postAndWait喚醒線程的run 此時我們進入到應用的RenderThread 拓展一下 如果是游戲進程的話 一般是unitymain gfx線程 flutter為什么會比rn要快 因為他直接和sf打交道 不需要再轉換一層 想了解的可以看看官方的架構圖 流程繼續 這里要注意的是 這里的執行順序是從左往右 單獨模塊從上到下執行 然后再回到起始點 往右執行

// 代碼太多 不一一解釋 這里就是把一個frame組織成一個結構體 cpu/gpu可讀懂的結構體
frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
void DrawFrameTask::postAndWait() {ATRACE_CALL();AutoMutex _lock(mLock);mRenderThread->queue().post([this]() { run(); });mSignal.wait(mLock);
}
// 從這里我們就可以看到我們熟悉的canvas 當然真正的渲染不是在java進行的
// dequeueBufferDuration 這里有個queue buffer的輪轉 我們后續分析
void DrawFrameTask::run() {const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);...// Grab a copy of everything we needCanvasContext* context = mContext;nsecs_t dequeueBufferDuration = 0;if (CC_LIKELY(canDrawThisFrame)) {dequeueBufferDuration = context->draw();} else {...
...
}

這里postAndWait后會到2處 也就是自身的渲染線程了 但是2處就是渲染個寂寞 真正渲染的地方是在3處 我們把2處放大一下
在這里插入圖片描述
我們現在到應用出幀的地方 也就是renderthread 可以看出 DrawFrames 66363537 和上述1處 Choreographer#doFrame 66363537 id是一樣的 但是沒有進行渲染 是cpu在執行其他線程 沒有得到調度 是因為該進程中的一個線程在初始化 有一定的負載
在這里插入圖片描述
dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 此處MainActivity應該是Producter才對 不應該是Consumer 在這里我們需要引入兩個知識點BufferQueue和GPU Fence

// ********** @引用_start 努比亞技術團隊**********

BufferQueue要解決的是生產者和消費者的同步問題 應用程序生產畫面 SurfaceFlinger消費畫面 SurfaceFlinger生產畫面而HWC Service消費畫面 用來存儲這些畫面的存儲區我們稱其為幀緩沖區buffer

在BufferQueue的設計中 一個buffer的狀態有以下幾種:

FREE:表示該buffer可以給到應用程序 由應用程序來繪畫

DEQUEUED:表示該buffer的控制權已經給到應用程序側,這個狀態下應用程序可以在上面繪畫

QUEUED: 表示該buffer已經由應用程序繪畫完成 buffer的控制權已經回到SurfaceFlinger手上

ACQUIRED:表示該buffer已經交由HWC Service去合成了 這時控制權已給到HWC Service

FREE->DEQUEUED->QUEUED->ACQUIRED->FREE

CPU和GPU的工作完全是異步的 Fence提供了一種方式來處理不同硬件對共享資源的訪問控制

// ********** @引用_end 努比亞技術團隊**********
在這里插入圖片描述
其實真正渲染的地方是在3處 我們放大一下 渲染線程中我們只需要重點了解dequeuebuffer和queuebuffer
在這里插入圖片描述
此時 應用的renderThread從自身的bufferqueue申請一塊buffer用來繪制 需要注意的是從R之后為了分擔sf的壓力 bufferqueue都在各自應用進程里進行 所以dequeuebuffer此處沒有binder調用dequeueBuffer 拿一塊buffer的地址下標 也就是往結構體填充指令的數組 下圖放大

在這里插入圖片描述

/frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,uint32_t width, uint32_t height, PixelFormat format,uint64_t usage, uint64_t* outBufferAge,FrameEventHistoryDelta* outTimestamps) {ATRACE_CALL();{ // Autolock scopestd::lock_guard<std::mutex> lock(mCore->mMutex);// trace中 dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 也是此處的拼接mConsumerName = mCore->mConsumerName;
...

VRI[MainActivity]#0(BLAST Consumer)0: 0 這里的solt是0

在dequeuebuffer 右邊還有一句 HWC release fence 19 has signaled 這里dequeuebuffer后這個solt地址并不是立即就往上填充數據 是要等待 GPU釋放對應的Fence 只是告訴你我要釋放了 相當于bt模組和modem 你請求查詢數據 然后模組告訴你有數據了 然后你還得調用個get請求去獲取這些數據

接下來就是queuebuffer部分 圖片部分放大

在這里插入圖片描述

// 表示hwc release fence 19 buffer 還給了bufferqueue 但gpu還沒有繪制完
Trace GPU completion fence 19// 將繪制好的buffer返回Surfacefinger
eglSwapBuffersWithDamageKHRstatus_t BufferQueueProducer::queueBuffer(int slot,const QueueBufferInput &input, QueueBufferOutput *output) {ATRACE_CALL();ATRACE_BUFFER_INDEX(slot);int64_t requestedPresentTimestamp;bool isAutoTimestamp;android_dataspace dataSpace;Rect crop(Rect::EMPTY_RECT);int scalingMode;...if (frameAvailableListener != nullptr) {// 按照需要回調至app層frameAvailableListener->onFrameAvailable(item);......

此時就會到SurfaceFlinger進程 值得注意的是 BufferQueue 可以看BLASTBufferQueue

在這里插入圖片描述
在這里插入圖片描述
waiting for presentFence 699 可以看出kernel消費情況
在這里插入圖片描述
所以 setText 開始到顯示 最終是這樣的
在這里插入圖片描述
其實整體內容還是比較淺顯的 sf這塊還是比較復雜的 設計的場景有點多 后續也只能找一個閉環去用貼源碼解釋

感謝觀看

參考文獻


  1. https://blog.csdn.net/rzleilei/article/details/94639329
  2. 作者:努比亞技術團隊
    鏈接:https://www.jianshu.com/p/3c61375cc15b
    來源:簡書
    著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/13888.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13888.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13888.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java方法的遞歸

Java方法的遞歸 前言一、遞歸的概念示例代碼示例 二、遞歸執行過程分析代碼示例執行過程圖 三、遞歸練習代碼示例按順序打印一個數字的每一位(例如 1234 打印出 1 2 3 4)遞歸求 1 2 3 ... 10寫一個遞歸方法&#xff0c;輸入一個非負整數&#xff0c;返回組成它的數字之和. …

零基礎學Java第二十一天之IIO流之對象流

IO流之對象流 1、對象流 1、理解 將對象寫入到文件&#xff0c;將文件里的對象讀取到程序中 class ObjectInputStream – 對象輸入流 class ObjectOutputStream – 對象輸出流 序列化/鈍化&#xff1a;程序里的對象 寫入到 文件中 反序列化/活化&#xff1a;文件中的對象 讀取…

【OpenCV實戰】OpenCV實現自動調整亮度和對比度

一,基于局部直方圖信息增強算法 對比度受限的自適應直方圖均衡化(Contrast Limited Adaptive Histogram Equalization,簡稱CLAHE)是一種用于圖像增強的技術,其原理主要基于自適應直方圖均衡化(Adaptive Histogram Equalization,簡稱AHE)但增加了對比度限制來避免過度放…

uniapp藍牙打印圖片

前言 這是個藍牙打印圖片的功能&#xff0c;業務是打印界面固定的demo范圍&#xff0c;這里通過html2canvas插件生成的圖片base64&#xff0c;然后圖片base64繪制到canvas中去后&#xff0c;獲取canvas中的像素信息&#xff0c;然后對像素信息進行一個灰度值處理&#xff0c;灰…

在Linux系統中解決Java生成海報文字亂碼和缺少字體文件的問題

在Linux系統中,如果缺少特定的字體文件,可以通過以下幾種方法來解決: 1. 安裝系統字體包 大多數Linux發行版提供了各種字體包,可以通過包管理器安裝這些字體包。例如,在Debian/Ubuntu系統上,可以使用以下命令安裝常見的字體包: # 安裝基本的字體包 sudo apt-get updat…

Java集合的組內平均值怎么計算

要計算Java集合&#xff08;例如List或Set中的Integer、Double或其他數值類型的對象&#xff09;的組內平均值&#xff0c;我們需要遍歷這個集合&#xff0c;累加所有的元素值&#xff0c;然后除以集合的大小&#xff08;即元素的數量&#xff09;。以下是一個詳細的步驟說明和…

opencl色域變換,處理傳遞顯存數據

在使用ffmpeg解碼后的多路解碼數據非常慢&#xff0c;還要給AI做行的加速方式是在顯存處理數據&#xff0c;在視頻拼接融合產品的產品與架構設計中&#xff0c;提出了比較可靠的方式是使用cuda&#xff0c;那么沒有cuda的顯卡如何處理呢 &#xff0c;比較好的方式是使用opencl來…

go語言的一些常見踩坑問題

開始之前&#xff0c;介紹一下?最近很火的開源技術&#xff0c;低代碼。 作為一種軟件開發技術逐漸進入了人們的視角里&#xff0c;它利用自身獨特的優勢占領市場一角——讓使用者可以通過可視化的方式&#xff0c;以更少的編碼&#xff0c;更快速地構建和交付應用軟件&#…

安卓手機APP開發__網絡連接性支持VPN

安卓手機APP開發__網絡連接性支持VPN 安卓提供了API給開發者,來創建一個虛擬的私有網絡(VPN)的解決方案. 根據這里的介紹,你能知道如何開發和測試你的針對安卓設備的VPN的客戶端. 概述 VPN允許設備為了安全地連接網絡,而沒有物理性的連接在一個網絡上. 安卓包括了一個內嵌的…

【無重復字符的最長子串】python,滑動窗口+哈希表

滑動窗口哈希表 哈希表 seen 統計&#xff1a; 指針 j遍歷字符 s&#xff0c;哈希表統計字符 s[j]最后一次出現的索引 。 更新左指針 i &#xff1a; 根據上輪左指針 i 和 seen[s[j]]&#xff0c;每輪更新左邊界 i &#xff0c;保證區間 [i1,j] 內無重復字符且最大。 更新結…

使用JSDOM安全截斷文章HTML內容

在Web開發中&#xff0c;經常需要處理大量的HTML內容&#xff0c;尤其是在展示文章預覽、動態加載內容或限制顯示長度等場景中。直接截斷HTML字符串可能會導致頁面布局混亂、樣式錯誤或標簽不完整等問題。為了安全地截斷HTML內容&#xff0c;我們可以利用jsdom庫來解析HTML&…

JVM學習-垃圾回收器(一)

垃圾回收器 按線程數分類 串行垃圾回收器 串行回收是在同一時間段內只允許有一個CPU用于執行垃圾回收操作&#xff0c;此時工作線程被暫停&#xff0c;直至垃圾收集工作結束 在諸如單CPU處理器或者較小的應用內存等硬件平臺不是特別優越的場合&#xff0c;串行回收器的性能表…

http和https的區別,怎么免費實現https(內涵教學)

超文本傳輸協議HTTP協議被用于在Web瀏覽器和網站服務器之間傳遞信息&#xff0c;HTTP協議以明文方式發送內容&#xff0c;不提供任何方式的數據加密&#xff0c;如果攻擊者截取了Web瀏覽器和網站服務器之間的傳輸報文&#xff0c;就可以直接讀懂其中的信息&#xff0c;因此&…

etcd 和 MongoDB 的混沌(故障注入)測試方法

最近在對一些自建的數據庫 driver/client 基礎庫的健壯性做混沌&#xff08;故障&#xff09;測試, 去驗證了解業務的故障處理機制和恢復時長. 主要涉及到了 MongoDB 和 etcd 這兩個基礎組件. 本文會介紹下相關的測試方法. MongoDB 中的故障測試 MongoDB 是比較世界上熱門的文…

AI網絡爬蟲:批量爬取電視貓上面的《慶余年》分集劇情

電視貓上面有《慶余年》分集劇情&#xff0c;如何批量爬取下來呢&#xff1f; 先找到每集的鏈接地址&#xff0c;都在這個class"epipage clear"的div標簽里面的li標簽下面的a標簽里面&#xff1a; <a href"/drama/Yy0wHDA/episode">1</a> 這個…

速盾:負載均衡能防ddos攻擊嗎?

負載均衡是一種分布式系統的設計思想&#xff0c;通過將流量分散到多個服務器上&#xff0c;以提高系統的穩定性和可擴展性。然而&#xff0c;負載均衡本身并不能完全防止DDoS攻擊&#xff0c;但可以在一定程度上減輕其影響。 DDoS&#xff08;分布式拒絕服務&#xff09;攻擊…

【C語言】8.C語言操作符詳解(1)

文章目錄 1.操作符的分類2.?進制和進制轉換3.原碼、反碼、補碼4.移位操作符4.1 左移操作符4.2 右移操作符 5.位操作符&#xff1a;&、|、^、~5.1 &&#xff1a;按位與5.2 |&#xff1a;按位或5.3 ^&#xff1a;按位異或5.4 ~&#xff1a;按位取反5.5 例題例題1例題2例…

短視頻矩陣系統4年獨立開發正規代發布接口源碼搭建部署開發

1. 短視頻矩陣源碼技術開發要求及實現流程&#xff1a; 短視頻矩陣源碼開發要求具備視頻錄制、編輯、剪輯、分享等基本功能&#xff0c;支持實時濾鏡、特效、音樂等個性化編輯&#xff0c;能夠實現高效的視頻渲染和處理。開發流程主要包括需求分析、技術選型、設計架構、編碼實…

Web前端開發技術、詳細文章、(例子)html 列表、有序列表、無序列表、列表嵌套

目錄 列表概述 列表類型與標記符號 無序列表 語法&#xff1a; 語法說明&#xff1a; 無序列表標記的 type 屬性及其說明 代碼解釋 有序列表 基本語法 屬性說明 1、列表 o1標記的屬性 2、列表項li標記的屬性 有序列表 o1標記的屬性、值 代碼解釋 列表嵌套 基本…

如何將Qt pro工程文件 改成CMakeLists.txt

Qt pro工程管理文件&#xff0c;本人認為是很好用的&#xff0c;語法簡潔易懂&#xff0c;但是只能在QtCreator中使用&#xff0c;想用使用其它IDE比如Clion或者vs&#xff0c;CMakeLists是種通用的選擇&#xff0c;另外QtCreator的調試功能跟粑粑一樣。 一&#xff0c;思路 …