背景
如果作為一名android系統研發工程師,很有可能需要監控系統中所以App的卡頓,以便協助App解決卡頓問題,提升用戶體驗,當然最主要的是APP研發很難發現每個頁面的卡頓,這個時候有系統支持就會發現卡頓的Activity,
基礎知識補充
Android螢屏重繪機制
理解Android硬體加速原理的小白文
大概描述下UI繪制一幀的流程
1、無論是resume或者invalidate等重繪UI的介面,最終都呼叫到了ViewRootImpl.scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
2、scheduleTraversals方法內部會Choreographer.postCallback,這個是最主要的,看下這個介面的備注,下一幀繪制信號來了會呼叫這個callback
/**
* Posts a callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*/
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
3、mTraversalRunnable就是幀信號回呼后呼叫doTraversal()內部依次performMeasure()、performLayout()、performDraw()
4、Choreographer.postCallback加入的回呼什么條件下被執行,代碼Choreographer.java
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
層層傳遞,最終呼叫到了mDisplayEventReceiver.scheduleVsync();看一下DisplayEventReceiver.java
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
根據介面注釋,這個介面會關聯這個DisplayEventReceiver物體類安排一個定向的脈沖信號(可以理解成回呼),會在下一幀繪制開始時發送,
5、因為nativeScheduleVsync是關聯DisplayEventReceiver注冊的,所以當收到下一幀繪制信號時會回呼onVsync介面
/**
* Called when a vertical sync pulse is received.
* The recipient should render a frame and then call {@link #scheduleVsync}
* to schedule the next vertical sync pulse.
*
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
* @param builtInDisplayId The surface flinger built-in display id such as
* {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
6、Choreographer內部實體化了FrameDisplayEventReceiver,重寫了onVsync介面,最侄訓呼叫到了Choreographer.doFrame()介面,
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);
}
}
void doFrame(long frameTimeNanos, int frame) {
......
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
重要的集合mCallbackQueues,這個集合會在postCallback時加入傳入的runnable,在doFrame中呼叫doCallbacks,doCallbacks內部會從mCallbackQueues取出runnable然后執行,最終去執行ViewRootImpl的doTraversal()
硬體加速下,Draw在GPU繪制的流程
1、cpu負責計算,measure,layout都是在主執行緒進行的,View視圖被抽象成RenderNode節點傳遞到GPU進行繪制,
ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded) {
....
<!--關鍵點1 是否開啟硬體加速-->
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
....
<!--關鍵點2 硬體加速繪制-->
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
....
}
GPU繪制是借助ThreadedRenderer去繪制的
2、ThreadedRenderer.java的draw方法
/**
* Draws the specified view
*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks,
FrameDrawingCallback frameDrawingCallback) {
attachInfo.mIgnoreDirtyState = true;
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
....
final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
if (frameDrawingCallback != null) {
nSetFrameCallback(mNativeProxy, frameDrawingCallback);
}
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
// Invalidate since we failed to draw. This should fetch a Surface
// if it is still needed or do nothing if we are no longer drawing
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
通過native方法nSyncAndDrawFrame交給底層去繪制了,繪制完成會在呼叫ViewRootImpl.invalidate()重繪,
底層又如何接管去繪制的
1、nSyncAndDrawFrame介面在frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %d",
frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
2、RenderProxy.syncAndDrawFrame
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
3、frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(CLOCK_MONOTONIC);
postAndWait();
return mSyncResult;
}
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}
void DrawFrameTask::run() {
......
context->draw();
......
}
4、context是CanvasContext,frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::draw() {
SkRect dirty;
mDamageAccumulator.finish(&dirty);
if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
return;
}
mCurrentFrameInfo->markIssueDrawCommandsStart();
Frame frame = mRenderPipeline->getFrame();
setPresentTime();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
int64_t frameCompleteNr = mFrameCompleteCallbacks.size() ? getFrameNumber() : -1;
waitOnFences();
bool requireSwap = false;
bool didSwap =
mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
mIsDirty = false;
if (requireSwap) {
if (!didSwap) { // some error happened
setSurface(nullptr);
}
SwapHistory& swap = mSwapHistory.next();
swap.damage = windowDirty;
swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC);
swap.vsyncTime = mRenderThread.timeLord().latestVsync();
if (mNativeSurface.get()) {
int durationUs;
nsecs_t dequeueStart = mNativeSurface->getLastDequeueStartTime();
if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
// Ignoring dequeue duration as it happened prior to frame render start
// and thus is not part of the frame.
swap.dequeueDuration = 0;
} else {
mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs);
swap.dequeueDuration = us2ns(durationUs);
}
mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs);
swap.queueDuration = us2ns(durationUs);
} else {
swap.dequeueDuration = 0;
swap.queueDuration = 0;
}
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
mHaveNewSurface = false;
mFrameNumber = -1;
} else {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
}
#if LOG_FRAMETIME_MMA
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
FrameInfoIndex::FrameCompleted) /
NANOS_PER_MILLIS_F;
if (sFrameCount) {
sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
} else {
sBenchMma = thisFrame;
}
if (++sFrameCount == 10) {
sFrameCount = 1;
ALOGD("Average frame time: %.4f", sBenchMma);
}
#endif
if (didSwap) {
for (auto& func : mFrameCompleteCallbacks) {
std::invoke(func, frameCompleteNr);
}
mFrameCompleteCallbacks.clear();
}
mJankTracker.finishFrame(*mCurrentFrameInfo);
if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
}
GpuMemoryTracker::onFrameCompleted();
}
最終native繪制完會存放到共享記憶體中,等待Surface通過SwapBuffers獲取繪制結果,
繪制流程梳理完了,如何去監控卡頓
不知道大家留意到沒,上述的三個大步驟都有個物件一直在記錄時間,比如:
mFrameInfo.markPerformTraversalsStart();
mFrameInfo.markInputHandlingStart();
mFrameInfo.markAnimationsStart();等等等等
FrameInfo這個物件會一直被傳遞,從java層一直到native的CanvasContext,每一個步驟都會mark一個時間,記錄下來,
所以,卡頓監控就可以從這個FrameInfo物件獲取每個步驟的時間,如果時間超過一幀,其實就是卡頓了,大體思路如何操作呢?
1、從繪制的終點CanvasContext.draw()方法的末尾,傳遞FrameInfo到自定義service,
2、自定義service通過處理一些時間細節,判斷是否掉幀,
3、自定義service通過aidl橋接到app或者直接在自定義service中存盤掉幀資料,
4、把處理過得資料匯總,通過網路請求上傳到服務器,分析觀察,
5、CanvasContext中還能拿到當年繪制的Activity資訊,可以把掉幀和視窗關聯起來,
FrameInfo欄位決議
具體幾個繪制名稱的含義:
- IntendedVsync app_vsync的時間
- Vsync 開始處理vync事件的時間
- OldestInputEvent 如上處理批量事件中最老的一個inputEvent的時間
- NewestInputEvent 最新事件
- HandleInputStart mainthread開始處理input事件的時間
- AnimationStart mainthread開始處理影片的時間
- PerformTraversalsStart mainthread開始遍歷視圖的時間
- DrawStart mainthread開始執行draw函式的時間
- SyncQueued 添加事件到ThreadRender的時間
- SyncStart renderthread開始同步main thread資料的的時間
- IssueDrawCommandsStart renderThread開始繪制的時間
- SwapBuffers renderThread開始交換buffer的時間
- FrameCompleted renderThread 完成交換buffer的時間
- DequeueBufferDuration renderThread交換buffer中dequeueBuffer花費的時間
- QueueBufferDuration renderThread queueBuffer的時間
幾個重要繪制節點時間間隔的含義(MQS會通過這幾個間隔判斷是否app自身導致的卡頓):
- IntendedVsync-Vsync main thread delay time
- HandleInputStart-AnimationStart handle_input_time interval
- AnimationStart-PerformTraversalsStart handle_animation_time
- PerformTraversalsStart-DrawStart handle_traversal_time
- SyncStart-IssueDrawCommandsStart bitmap_uploads_time
- IssueDrawCommandsStart-SwapBuffers issue_draw_commands_time
卡頓使用FrameCompleted - IntendedVsync 的時間差作為一次繪制的時長,
如何優化
Android性能優化典范
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/249094.html
標籤:其他
上一篇:關于華為eNSP模擬器設備注冊,及AR/WLAN設備啟動失敗問題
下一篇:自己寫一個flutter插件
