自Android 4.1引入了“Profile GPU rendering”這個開發工具以幫助分析應用程序性能並並精確定位渲染問題,Android 4.3增加了可視效果:On screen as bars/lines。模擬器中此功能生效要勾選AVD的:Emulation Options:User Host GPU。
這個選項的開關實現位於settings中DevelopmentSettings.java中的函數writeTrackFrameTimeOptions:
Codeprivate void writeTrackFrameTimeOptions(Object newValue) {
SystemProperties.set(HardwareRenderer.PROFILE_PROPERTY,
newValue == null ? "" : newValue.toString());
pokeSystemProperties();
updateTrackFrameTimeOptions();
}
void pokeSystemProperties() {
if (!mDontPokeProperties) {
//noinspection unchecked
(new SystemPropPoker()).execute();
}
}
static class SystemPropPoker extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
String[] services;
try {
services = ServiceManager.listServices();
} catch (RemoteException e) {
return null;
}
for (String service : services) {
IBinder obj = ServiceManager.checkService(service);
if (obj != null) {
Parcel data = Parcel.obtain();
try {
obj.transact(IBinder.SYSPROPS_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
} catch (Exception e) {
Log.i(TAG, "Somone wrote a bad service '" + service
+ "' that doesn't like to be poked: " + e);
}
data.recycle();
}
}
return null;
}
}
其中HardwareRenderer.PROFILE_PROPERTY定義於HardwareRenderer.java:
Code /** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. * * When profiling is enabled, the adb shell dumpsys gfxinfo command will * output extra information about the time taken to execute by the last * frames. * * Possible values: * "true", to enable profiling * "visual_bars", to enable profiling and visualize the results on screen * "visual_lines", to enable profiling and visualize the results on screen * "false", to disable profiling * * @see #PROFILE_PROPERTY_VISUALIZE_BARS * @see #PROFILE_PROPERTY_VISUALIZE_LINES * * @hide */
public static final String PROFILE_PROPERTY = "debug.hwui.profile";
抽象類GlRenderer中重寫函數dumpGfxInfo:
Code@Override
void dumpGfxInfo(PrintWriter pw) {
if (mProfileEnabled) {
pw.printf("\n\tDraw\tProcess\tExecute\n");
mProfileLock.lock();
try {
for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
if (mProfileData[i] < 0) {
break;
}
pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], mProfileData[i + 2]);④
mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
}
mProfileCurrentFrame = mProfileData.length;
} finally {
mProfileLock.unlock();
}
}
}
在“Profile GPU rendering”中勾選“In adb shell dumpsys gfxinfo”或者“adb shell setprop debug.hwui.profile true”之后,運行adb shell dumpsys gfxinfo package_name輸出(ms):
com.android.deskclock/com.android.deskclock.DeskClock/android.view.ViewRootImpl@41770410
Draw Process Execute
31.73 10.66 11.73
29.34 6.74 8.19
74.70 31.81 26.01
5.96 4.51 5.67
59.04 3.42 6.61
19.34 5.00 7.14
33.70 18.27 138.73
2.97 14.80 14.26
4.28 3.27 15.31
2.25 3.83 4.03
3.18 1.87 3.89
3.09 1.92 5.38
14.55 3.01 16.52
2.66 3.74 3.83
5.69 1.97 72.48
4.27 1.93 12.90
0.73 4.84 4.49
36.66 6.62 7.53
12.80 3.22 17.49
6.31 4.86 4.73
4.55 3.42 4.46
1.95 1.91 13.81
15.05 23.02 5.10
21.86 1.86 4.61
4.87 8.40 5.24
4.49 6.78 4.21
7.15 30.36 3.70
12.38 24.17 4.89
2.77 5.27 22.00
3.26 5.66 4.02
1.74 3.98 22.07
2.23 3.30 4.34
0.86 2.89 13.56
1.00 13.85 13.94
0.84 1.34 13.86
3.45 2.42 3.81
0.69 1.48 14.75
0.64 1.32 13.88
0.46 1.41 14.48
其中:
Draw--Build display lists
Process—Process(Draw) display lists
Excute--Swap GL buffers
關於此數據分析詳見Android Performance Case Study和Android application (performance and more) analysis tools - Tutorial。
重寫了函數draw:
Code@Override
void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty) {
if (canDraw()) {
if (!hasDirtyRegions()) {
dirty = null;
}
attachInfo.mIgnoreDirtyState = true;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
view.mPrivateFlags |= View.PFLAG_DRAWN;
final int surfaceState = checkCurrent();
if (surfaceState != SURFACE_STATE_ERROR) {
HardwareCanvas canvas = mCanvas;
attachInfo.mHardwareCanvas = canvas;
if (mProfileEnabled) {
mProfileLock.lock();
}
dirty = beginFrame(canvas, dirty, surfaceState);
DisplayList displayList = buildDisplayList(view, canvas);
int saveCount = 0;
int status = DisplayList.STATUS_DONE;
try {
status = prepareFrame(dirty);
saveCount = canvas.save();
callbacks.onHardwarePreDraw(canvas);
if (displayList != null) {
status |= drawDisplayList(attachInfo, canvas, displayList, status);
} else {
// Shouldn't reach here
view.draw(canvas);
}
} catch (Exception e) {
Log.e(LOG_TAG, "An error has occurred while drawing:", e);
} finally {
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
view.mRecreateDisplayList = false;
mFrameCount++;
debugDirtyRegions(dirty, canvas);
drawProfileData(attachInfo);
}
onPostDraw();
swapBuffers(status);
if (mProfileEnabled) {
mProfileLock.unlock();
}
attachInfo.mIgnoreDirtyState = false;
}
}
}
1.buildDisplayList
Codeprivate long startBuildDisplayListProfiling() {
if (mProfileEnabled) {
mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
if (mProfileCurrentFrame >= mProfileData.length) {
mProfileCurrentFrame = 0;
}
return System.nanoTime();
}
return 0;
}
private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
if (mProfileEnabled) {
long now = System.nanoTime();
float total = (now - getDisplayListStartTime) * 0.000001f;
//noinspection PointlessArithmeticExpression
mProfileData[mProfileCurrentFrame] = total;① }
}
private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
long buildDisplayListStartTime = startBuildDisplayListProfiling();
canvas.clearLayerUpdates();
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
DisplayList displayList = view.getDisplayList();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
endBuildDisplayListProfiling(buildDisplayListStartTime);
return displayList;
}
view.getDisplayList()來自View.java:
/**
* <p>Returns a display list that can be used to draw this view again
* without executing its draw method.</p>
*
* @return A DisplayList ready to replay, or null if caching is not enabled.
*
* @hide
*/
public DisplayList getDisplayList() {
mDisplayList = getDisplayList(mDisplayList, false);
return mDisplayList;
}
/**
* Returns a DisplayList. If the incoming displayList is null, one will be created.
* Otherwise, the same display list will be returned (after having been rendered into
* along the way, depending on the invalidation state of the view).
*
* @param displayList The previous version of this displayList, could be null.
* @param isLayer Whether the requester of the display list is a layer. If so,
* the view will avoid creating a layer inside the resulting display list.
* @return A new or reused DisplayList object.
*/
private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
if (!canHaveDisplayList()) {
return null;
}
if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
displayList == null || !displayList.isValid() ||
(!isLayer && mRecreateDisplayList))) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (displayList != null && displayList.isValid() &&
!isLayer && !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return displayList;
}
if (!isLayer) {
// If we got here, we're recreating it. Mark it as such to ensure that
// we copy in child display lists into ours in drawChild()
mRecreateDisplayList = true;
}
if (displayList == null) {
final String name = getClass().getSimpleName();
displayList = mAttachInfo.mHardwareRenderer.createDisplayList(name);
// If we're creating a new display list, make sure our parent gets invalidated
// since they will need to recreate their display list to account for this
// new child display list.
invalidateParentCaches();
}
boolean caching = false;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
final HardwareCanvas canvas = displayList.start(width, height);
try {
if (!isLayer && layerType != LAYER_TYPE_NONE) {
if (layerType == LAYER_TYPE_HARDWARE) {
final HardwareLayer layer = getHardwareLayer();
if (layer != null && layer.isValid()) {
canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
} else {
canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
}
caching = true;
} else {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
caching = true;
}
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
if (!isLayer) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
}
} finally {
displayList.end();
displayList.setCaching(caching);
if (isLayer) {
displayList.setLeftTopRightBottom(0, 0, width, height);
} else {
setDisplayListProperties(displayList);
}
}
} else if (!isLayer) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return displayList;
}
View Code
2.drawDisplayList
Codeprivate int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas,
DisplayList displayList, int status) {
long drawDisplayListStartTime = 0;
if (mProfileEnabled) {
drawDisplayListStartTime = System.nanoTime();
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
try {
status |= canvas.drawDisplayList(displayList, mRedrawClip, DisplayList.FLAG_CLIP_CHILDREN);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfileEnabled) {
long now = System.nanoTime();
float total = (now - drawDisplayListStartTime) * 0.000001f;
mProfileData[mProfileCurrentFrame + 1] = total;②
}
handleFunctorStatus(attachInfo, status);
return status;
}
canvas.drawDisplayList來自HardwareCanvas.java,這是一個抽象接口:
Code/** * Draws the specified display list onto this canvas. The display list can only * be drawn if {@link android.view.DisplayList#isValid()} returns true. * * @param displayList The display list to replay. */
public void drawDisplayList(DisplayList displayList) {
drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
}
/** * Draws the specified display list onto this canvas. * * @param displayList The display list to replay. * @param dirty The dirty region to redraw in the next pass, matters only * if this method returns {@link DisplayList#STATUS_DRAW}, can be null. * @param flags Optional flags about drawing, see {@link DisplayList} for * the possible flags. * * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW}, or * {@link DisplayList#STATUS_INVOKE}, or'd with {@link DisplayList#STATUS_DREW} * if anything was drawn. * * @hide */
public abstract int drawDisplayList(DisplayList displayList, Rect dirty, int flags);
3.swapBuffers
Codeprivate void swapBuffers(int status) {
if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
long eglSwapBuffersStartTime = 0;
if (mProfileEnabled) {
eglSwapBuffersStartTime = System.nanoTime();
}
sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
if (mProfileEnabled) {
long now = System.nanoTime();
float total = (now - eglSwapBuffersStartTime) * 0.000001f;
mProfileData[mProfileCurrentFrame + 2] = total;③
}
checkEglErrors();
}
}
EGL10.java中的eglSwapBuffers->EGLImpl.java->com_google_android_gles_jni_EGLImpl.cpp:
com_google_android_gles_jni_EGLImpl.cpp中:
Codestatic jboolean jni_eglSwapBuffers(JNIEnv *_env, jobject _this, jobject display, jobject surface) {
if (display == NULL || surface == NULL) {
jniThrowException(_env, "java/lang/IllegalArgumentException", NULL);
return JNI_FALSE;
}
EGLDisplay dpy = getDisplay(_env, display);
EGLSurface sur = getSurface(_env, surface);
return eglSwapBuffers(dpy, sur);
}