最近有一部分工作涉及到GraphicsStatsService服务,有几个问题:
1:为什么会有两个相同的package
2:进程挂掉后再重启,为什么Since不变
3:有没有3个Package相同的情况
4:Since值什么时候改变
我们先从宏观角度看下具体过程,再具体解释几个问题
信息解释:
Android系统中GraphicsStatsService服务是用来汇总卡顿数据的,通过adb shell dumpsys graphicsstats调用,输出如下信息(有些机型中没有HISTOGRAM信息,Android 7.0以上版本才有):
Package: com.android.systemui
Stats since: 121034968754ns
Total frames rendered: 7067
Janky frames: 319 (4.51%)
50th percentile: 5ms
90th percentile: 11ms
95th percentile: 15ms
99th percentile: 48ms
Number Missed Vsync: 134
Number High input latency: 0
Number Slow UI thread: 206
Number Slow bitmap uploads: 11
Number Slow issue draw commands: 52
HISTOGRAM: 5ms=4738 6ms=527 7ms=388 8ms=286 9ms=235 10ms=150 11ms=129 12ms=97 13ms=77 14ms=54 15ms=42 16ms=32 17ms=23 18ms=27 19ms=25 20ms=21 21ms=22 22ms=8 23ms=18 24ms=11 25ms=11 26ms=3 27ms=6 28ms=7 29ms=3 30ms=6 31ms=6 32ms=2 34ms=5 36ms=4 38ms=9 40ms=6 42ms=7 44ms=2 46ms=3 48ms=7 53ms=4 57ms=3 61ms=3 65ms=4 69ms=5 73ms=4 77ms=4 81ms=1 85ms=2 89ms=3 93ms=3 97ms=0 101ms=3 105ms=0 109ms=0 113ms=1 117ms=0 121ms=1 125ms=0 129ms=1 133ms=1 150ms=9 200ms=2 250ms=5 300ms=2 350ms=2 400ms=0 450ms=1 500ms=2 550ms=1 600ms=2 650ms=0 700ms=0 750ms=0 800ms=0 850ms=0 900ms=0 950ms=0 1000ms=0 1050ms=0 1100ms=0 1150ms=0 1200ms=1 1250ms=0 1300ms=0 1350ms=0 1400ms=0 1450ms=0 1500ms=0 1550ms=0 1600ms=0 1650ms=0 1700ms=0 1750ms=0 1800ms=0 1850ms=0 1900ms=0 1950ms=0 2000ms=0 2050ms=0 2100ms=0 2150ms=0 2200ms=0 2250ms=0 2300ms=0 2350ms=0 2400ms=0 2450ms=0 2500ms=0 2550ms=0 2600ms=0 2650ms=0 2700ms=0 2750ms=0 2800ms=0 2850ms=0 2900ms=0 2950ms=0 3000ms=0 3050ms=0 3100ms=0 3150ms=0 3200ms=0 3250ms=0 3300ms=0 3350ms=0 3400ms=0 3450ms=0 3500ms=0 3550ms=0 3600ms=0 3650ms=0 3700ms=0 3750ms=0 3800ms=0 3850ms=0 3900ms=0 3950ms=0 4000ms=0 4050ms=0 4100ms=0 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0
名词解释:
Stats since:应用统计信息开始的时间戳
Total frames:绘制总帧数
Janky frames:卡顿总帧数
50th percentile:50%的帧在多少ms内绘制的
90th percentile:90%的帧在多少ms内绘制的
Number Missed Vsync:未正常收到的Vsync信号数量
Numer High input latency:高输入导致掉帧
Number Slow Ui Thread:主线程慢导致卡顿
Number Slow bitmap uploads:位图上传慢导致卡顿
Number Slow issue draw commands:绘制命令慢导致卡顿
HISTOGRAM:详细数据展示,柱状图
我在这些数据中一般会比较关系的数据:Stats since,Total frames,Janky frames,HISTOGRAM。
先看下调用adb shell dumpsys graphicsstats发生了什么吧:
dumpsys graphicsstats执行过程
从之前博客(http://blog.youkuaiyun.com/longlong2015/article/details/71598757)的代码分析,可以得出dumpsys主要工作分为以下4个步骤:
defaultServiceManager(),获取ServiceManager对象;
sm->listServices(),获取系统所有向ServiceManager注册过的服务;
sm->checkService(),获取系统中指定的Service;
service->dump(),dumpsys命令的核心还是调用远程服务中的dump()方法来获取相应的dump信息。
即调用graphicsstats对应服务的dump方法,从Android源码中可知,graphicsstats对应服务GraphicsStatsService,即调用了GraphicsStatsService的dump方法
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
public class GraphicsStatsService extends IGraphicsStats.Stub {
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
frameworks/base/services/java/com/android/server/SystemServer.java
if (!disableNonCoreServices) {
ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
new GraphicsStatsService(context));
}
主角慢慢登场,那么来看下dump方法的实现:
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
Log.d("liuwenlong", "GraphicsStatsService dump");
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
synchronized (mLock) {
for (int i = 0; i < mActive.size(); i++) {
final ActiveBuffer buffer = mActive.get(i);
fout.print("Package: ");
fout.print(buffer.mPackageName);
fout.flush();
try {
buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE);
ThreadedRenderer.dumpProfileData(mTempBuffer, fd);
} catch (IOException e) {
fout.println("Failed to dump");
}
fout.println();
}
for (HistoricalData buffer : mHistoricalLog) {
if (buffer == null) continue;
fout.print("Package: ");
fout.print(buffer.mPackageName);
fout.flush();
ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd);
fout.println();
}
}
}
从源码中可知,整个dump过程主要有两部分信息,mActive和mHistoricalLog两部分信息,先来说下这两个变量:
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); #存放目前目前正在运行进程的绘制信息
private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE]; #存放已经死掉进程的绘制信息
从这里有两个问题发散出来,信息是如何打印的,信息是如何被填充的,后面都会慢慢揭开面纱。
信息如何dump
先来看第一个问题,信息是如何被打印的,从源码中看,不管是mActive和mHistoricalLog,最终调用的都是ThreadedRenderer.dumpPrtofileData()方法
frameworks/base/services/core/java/android/view/ThreadedRenderer.java
public static void dumpProfileData(byte[] data, FileDescriptor fd) {
nDumpProfileData(data, fd);
}
private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
调用了native方法,接着跟一下
frameworks/base/core/jni/android_view_threadedRenderer.cpp
{ "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
从JNINativeMethod结构体中可以看出,nDumpProfileData对应的native方法为android_view_ThreadedRenderer方法,继续追踪:
static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz,
jbyteArray jdata, jobject javaFileDescriptor) {
ALOGV("liuwenlong android_view_ThreadedRenderer.cpp android_view_ThreadedRenderer_dumpProfileData");
int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
ScopedByteArrayRO buffer(env, jdata);
if (buffer.get()) {
JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd);
}
}
再来看JankTracker的dumpBufer方法:
void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
ALOGV("liuwenlong JankTracker.cpp dumpBuffer");
if (bufsize < sizeof(ProfileData)) {
return;
}
const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer);
dumpData(data, fd);
}
void JankTracker::dumpData(const ProfileData* data, int fd) {
ALOGV("liuwenlong JankTracker.cpp dumpData");
if (sFrameStart != FrameInfoIndex::IntendedVsync) {
dprintf(fd, "\nNote: Data has been filtered!");
}
dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
(float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
for (int i = 0; i < NUM_BUCKETS; i++) {
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
}
dprintf(fd, "\nHISTOGRAM:");
for (size_t i = 0; i < data->frameCounts.size(); i++) {
dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i),
data->frameCounts[i]);
}
for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs,
data->slowFrameCounts[i]);
}
dprintf(fd, "\n");
}
最终调用的dumpData方法,即是dumpsys graphicsstats输出的信息。从一层层的调用关系来看,打印的值来源于 ProfileData* data,而data最终来源于java层传下来的buffer,那这个buffer是什么呢?
dump数据来源于哪?
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
private final class ActiveBuffer implements DeathRecipient {
final int mUid;
final int mPid;
final String mPackageName;
final IBinder mToken;
MemoryFile mProcessBuffer;
HistoricalData mPreviousData;
ActiveBuffer(IBinder token, int uid, int pid, String packageName)
throws RemoteException, IOException {
mUid = uid;
mPid = pid;
mPackageName = packageName;
mToken = token;
mToken.linkToDeath(this, 0);
mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE);
mPreviousData = removeHistoricalDataLocked(mUid, mPackageName);
if (mPreviousData != null) {
mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);
}
}
@Override
public void binderDied() {
mToken.unlinkToDeath(this, 0);
processDied(this);
}
void closeAllBuffers() {
if (mProcessBuffer != null) {
mProcessBuffer.close();
mProcessBuffer = null;
}
}
}
从代码中可知,buffer对应MemoryFile,MemoryFile是一个Java封装好的匿名共享内存实现,也就是说,dumpsys graphicsstats输出信息来源于共享内存,那现在有一个假设,是不是每个App都会更新这部分内存,而dump时只是将这部分内存的信息输出出来?这个疑问我们先保留着,先来看binderDied方法,这个方法是在DeathRecipent中定义的,意思是什么呢?就是当持有binder(mToken)的进程死亡时,就会回调binderDied方法。
frameworks/base/core/java/android/os/Ibinder.java
/**
* Interface for receiving a callback when the process hosting an IBinder
* has gone away.
*
* @see #linkToDeath
*/
public interface DeathRecipient {
public void binderDied();
}
进程死亡时,会发生什么呢?
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
private void processDied(ActiveBuffer buffer) {
Log.d("liuwenlong", "GraphicsStatsService processDied");
synchronized (mLock) {
mActive.remove(buffer);
Log.d("GraphicsStats", "Buffer count: " + mActive.size());
}
HistoricalData data = buffer.mPreviousData;
buffer.mPreviousData = null;
if (data == null) {
data = mHistoricalLog[mNextHistoricalSlot];
if (data == null) {
data = new HistoricalData();
}
}
data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer);
buffer.closeAllBuffers();
mHistoricalLog[mNextHistoricalSlot] = data;
mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length;
}
从执行过程中可以看出来,会把mActive中相关信息删除,然后在mHistoricalLog创建信息,那回到ActiveBuffer的构造方法中,有一句:mPreviousData = removeHistoricalDataLocked(mUid, mPackageName)
private HistoricalData removeHistoricalDataLocked(int uid, String packageName) {
Log.d("liuwenlong", "GraphicsStatsService removeHistoricalDataLocked");
for (int i = 0; i < mHistoricalLog.length; i++) {
final HistoricalData data = mHistoricalLog[i];
if (data != null && data.mUid == uid
&& data.mPackageName.equals(packageName)) {
if (i == mNextHistoricalSlot) {
mHistoricalLog[i] = null;
} else {
mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot];
mHistoricalLog[mNextHistoricalSlot] = null;
}
return data;
}
}
return null;
}
也就是说,ActiveBuffer创建时,会先从mHistoricalLog中查询是否有相关信息,如果有,则返回 mHistoricalLog中对应的信息,即mPreviousData中会有值,然后再执行mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);这样mProcessBuffer又有值了,而且是用的历史存储的值,这一点很关键。
ActiveBuffer何时创建
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid,
String packageName) throws RemoteException {
Log.d("liuwenlong", "GraphicsStatsService fetchActiveBuffersLocked, packageName is " + packageName);
int size = mActive.size();
for (int i = 0; i < size; i++) {
ActiveBuffer buffers = mActive.get(i);
if (buffers.mPid == pid
&& buffers.mUid == uid) {
return buffers;
}
}
// Didn't find one, need to create it
try {
ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName);
mActive.add(buffers);
return buffers;
} catch (IOException ex) {
throw new RemoteException("Failed to allocate space");
}
}
从源码中可知,当发现mActive中通过pid和uid检测,如果没有匹配项,则创建新的ActiveBuffer,而fetchActiveBufferLocked方法是在哪调用的呢?
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
@Override
public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token)
throws RemoteException {
Log.d("liuwenlong", "GraphicsStatsService requestBufferForProcess");
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
ParcelFileDescriptor pfd = null;
long callingIdentity = Binder.clearCallingIdentity();
try {
mAppOps.checkPackage(uid, packageName);
synchronized (mLock) {
pfd = requestBufferForProcessLocked(token, uid, pid, packageName);
}
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
return pfd;
}
private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token,
int uid, int pid, String packageName) throws RemoteException {
ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName);
return getPfd(buffer.mProcessBuffer);
}
即requestBufferForProcess调用时很可能会创建ActiveBuffer,而requestBufferForProcess是public方法,全局搜一下发现只有一处调用此方法:
frameworks/base/core/java/android/view/ThreadedRenderer.java
private static void initGraphicsStats(Context context, long renderProxy) {
Log.d("liuwenlong", "ThreadedRenderer initGraphicsStats");
try {
IBinder binder = ServiceManager.getService("graphicsstats");
if (binder == null) return;
IGraphicsStats graphicsStatsService = IGraphicsStats.Stub
.asInterface(binder);
sProcToken = new Binder();
final String pkg = context.getApplicationInfo().packageName;
ParcelFileDescriptor pfd = graphicsStatsService.
requestBufferForProcess(pkg, sProcToken);
nSetProcessStatsBuffer(renderProxy, pfd.getFd());
pfd.close();
} catch (Throwable t) {
Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
}
}
这里面有个很关键的方法nSetProcessStatsBuffer,我们等会再看这个方法的逻辑,先来看真正创建ActiveBuffer的逻辑,initGraphicsStats是在ProcessInitializer的init方法中调用
frameworks/base/core/java/android/view/ThreadedRenderer.java
synchronized void init(Context context, long renderProxy) {
if (mInitialized) return;
mInitialized = true;
initSched(context, renderProxy);
initGraphicsStats(context, renderProxy);
initAssetAtlas(context, renderProxy);
}
ProcessInitializer的init方法会在ThreadedRenderer中调用,
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
mAmbientShadowAlpha =
(int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
a.recycle();
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
Log.d("liuwenlong", "ThreadedRenderer construct method ");
ProcessInitializer.sInstance.init(context, mNativeProxy);
loadSystemProperties();
}
ThreadedRenderer构造方法的调用是在ThreadedRenderer.create()方法中
/**
* Creates a hardware renderer using OpenGL.
*
* @param translucent True if the surface is translucent, false otherwise
*
* @return A hardware renderer backed by OpenGL.
*/
public static ThreadedRenderer create(Context context, boolean translucent) {
Log.d("liuwenlong", "ThreadedRenderer create ");
ThreadedRenderer renderer = null;
if (DisplayListCanvas.isAvailable()) {
renderer = new ThreadedRenderer(context, translucent);
}
return renderer;
}
那什么时候会create ThreadedRenderer呢?全局搜了一下,发现是在ViewRootImpl.java中
/frameworks/base/core/java/android/view/ViewRootImpl.java
Slog.d("liuwenlong", " ViewRootImpl enableHardwareAcceleration ,create ThreadedRender");
mAttachInfo.mHardwareRenderer = ThreadedRenderer.create(mContext, translucent);
熟悉Android编程的都会知道,一个window会对应一个ViewRootImpl,而一个ViewRootImpl对应一个ThreadedRenderer,这部分基本上就理顺了,当一个进程启动时,会有UI展示,那么会创建window,进而最终创建ActiveBuffer,只不过ActiveBuffer中的mProcessBuffer数据是新创建还是用历史数据,需要看GraphicsStatsService中的mActive和mHistoricalLog的数据。
数据如何填充
现在容器造好了,那么怎么填充的数据呢?再来看ThreadedRenderer.java中的 nSetProcessStatsBuffer方法:
frameworks/base/core/jni/android_view_ThreadedRenderer.cpp
{ "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, jlong proxyPtr, jint fd) {
ALOGV("liuwenlong android_view_ThreadedRenderer.cpp android_view_ThreadedRenderer_setProcessStatsBuffer");
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->setProcessStatsBuffer(fd);
}
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)
#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
typedef struct { \
a1; a2; a3; a4; a5; a6; a7; a8; \
} ARGS(name); \
static_assert(std::is_trivially_destructible<ARGS(name)>::value, \
"Error, ARGS must be trivially destructible!"); \
static void* Bridge_ ## name(ARGS(name)* args)
#define SETUP_TASK(method) \
LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
"METHOD_INVOKE_PAYLOAD_SIZE %zu is smaller than sizeof(" #method "Args) %zu", \
METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
ARGS(method) *args = (ARGS(method) *) task->payload()
CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
ALOGV("liuwenlong RenderProxy.cpp CREATE_BRIDGE2");
args->thread->jankTracker().switchStorageToAshmem(args->fd);
close(args->fd);
return nullptr;
}
void RenderProxy::setProcessStatsBuffer(int fd) {
ALOGV("liuwenlong RenderProxy.cpp setProcessStatsBuffer");
SETUP_TASK(setProcessStatsBuffer);
args->thread = &mRenderThread;
args->fd = dup(fd);
post(task);
}
从
从代码可知,一个RenderProxy会对应mRenderThread,这里面有两个很重要的函数SETUP_TASK和CREATE_BRIDGE2,代码中没有涉及CREATE_BRIDGE2的调用啊,那是因为这里面定义了一个回调函数,实现的逻辑是什么呢?setProcessStatsBuffer调用SETUP_TASK方法,新创建MethodInvokeRenderTask对应,并把Bridge_setProcessBuffer作为回调函数,而Bridge_setProcessBuffer正是由CREATE_BRIDGE2宏定义生成的方法,将其彻底转化一下,具体代码如下:
typedef struct {
RenderThread* thread,
int fd
} setProcessStatsBufferArgs;
static void* Bridge_setProcessStatsBuffer(setProcessStatsBufferArgs* args){
args->thread->jankTracker().switchStorageToAshmem(args->fd);
close(args->fd);
return nullptr;
}
void RenderProxy::setProcessStatsBuffer(int fd) {
MethodInvokeRenderTask* task = new MethodInvokeRenderTask(
(RunnableMethod) Bridge_setProcessStatsBuffer);
setProcessStatsBufferArgs *args = (setProcessStatsBufferArgs *) task->payload();
args->thread = &mRenderThread;
args->fd = dup(fd);
post(task);
}
看一下MethodInvokeRenderTask源码会更加清晰
typedef void* (*RunnableMethod)(void* data);
class MethodInvokeRenderTask : public RenderTask {
public:
MethodInvokeRenderTask(RunnableMethod method)
: mMethod(method), mReturnPtr(nullptr) {}
void* payload() { return mData; }
void setReturnPtr(void** retptr) { mReturnPtr = retptr; }
virtual void run() override {
void* retval = mMethod(mData);
if (mReturnPtr) {
*mReturnPtr = retval;
}
// Commit suicide
delete this;
}
private:
RunnableMethod mMethod;
char mData[METHOD_INVOKE_PAYLOAD_SIZE];
void** mReturnPtr;
};
从上面代码就可以清楚看到,MethodInvokeRenderTask运行时,会调用Bridge_setProcessBuffer函数,然后会调用switchStorageToAshmem函数,看下具体实现:
void JankTracker::switchStorageToAshmem(int ashmemfd) {
ALOGV("liuwenlong JankTracker.cpp switchStorageToAshmem");
int regionSize = ashmem_get_size_region(ashmemfd);
if (regionSize < static_cast<int>(sizeof(ProfileData))) {
ALOGW("Ashmem region is too small! Received %d, required %u",
regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
return;
}
ProfileData* newData = reinterpret_cast<ProfileData*>(
mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
MAP_SHARED, ashmemfd, 0));
if (newData == MAP_FAILED) {
int err = errno;
ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
ashmemfd, err);
return;
}
// The new buffer may have historical data that we want to build on top of
// But let's make sure we don't overflow Just In Case
uint32_t divider = 0;
if (newData->totalFrameCount > (1 << 24)) {
divider = 4;
}
for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) {
newData->jankTypeCounts[i] >>= divider;
newData->jankTypeCounts[i] += mData->jankTypeCounts[i];
}
for (size_t i = 0; i < mData->frameCounts.size(); i++) {
newData->frameCounts[i] >>= divider;
newData->frameCounts[i] += mData->frameCounts[i];
}
newData->jankFrameCount >>= divider;
newData->jankFrameCount += mData->jankFrameCount;
newData->totalFrameCount >>= divider;
newData->totalFrameCount += mData->totalFrameCount;
if (newData->statStartTime > mData->statStartTime
|| newData->statStartTime == 0) {
newData->statStartTime = mData->statStartTime;
}
freeData();
mData = newData;
mIsMapped = true;
}
从代码中可知,执行过程为:mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,MAP_SHARED, ashmemfd, 0)申请一个共享newData,然后将mData数据映射进去,最后将mData指向newData完成映射。此部分逻辑比较关键,关系到进程新建时处理逻辑,先继续往下看代码,后面会揭开这层面纱。
从RenderProxy.etProcessStatsBuffer()方法中可以得到RenderProxy对应mRenderThread,看下mRenderThread创建过程:
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance())
, mContext(nullptr) {
SETUP_TASK(createContext);
args->translucent = translucent;
args->rootRenderNode = rootRenderNode;
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
从上面代码可知:RenderProxy在创建时,会调用RenderThread.getInstance()方法,从这里也可以看出,一个进程中只有一个RenderThread,而其创建第一个RenderProxy对象创建时,即进程启动时第一个window开始渲染时。
RenderThread& RenderThread::getInstance() {
// This is a pointer because otherwise __cxa_finalize
// will try to delete it like a Good Citizen but that causes us to crash
// because we don't want to delete the RenderThread normally.
static RenderThread* sInstance = new RenderThread();
gHasRenderThreadInstance = true;
return *sInstance;
}
RenderThread::RenderThread() : Thread(true)
, mNextWakeup(LLONG_MAX)
, mDisplayEventReceiver(nullptr)
, mVsyncRequested(false)
, mFrameCallbackTaskPending(false)
, mFrameCallbackTask(nullptr)
, mRenderState(nullptr)
, mEglManager(nullptr) {
Properties::load();
mFrameCallbackTask = new DispatchFrameCallbacks(this);
mLooper = new Looper(false);
run("RenderThread");
}
RenderThread对应的Loop如下:
bool RenderThread::threadLoop() {
setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
initThreadLocals();
int timeoutMillis = -1;
for (;;) {
int result = mLooper->pollOnce(timeoutMillis);
LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
"RenderThread Looper POLL_ERROR!");
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();
mFrameCallbacks.insert(
mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end());
mPendingRegistrationFrameCallbacks.clear();
requestVsync();
}
if (!mFrameCallbackTaskPending && !mVsyncRequested && mFrameCallbacks.size()) {
// TODO: Clean this up. This is working around an issue where a combination
// of bad timing and slow drawing can result in dropping a stale vsync
// on the floor (correct!) but fails to schedule to listen for the
// next vsync (oops), so none of the callbacks are run.
requestVsync();
}
}
return false;
}
initThreadLocals做了许多初始化的工作,代码如下:
void RenderThread::initThreadLocals() {
ALOGV("liuwenlong RenderThread.cpp initThreadLocals");
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo);
LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
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(mDisplayInfo);
}
initThreadLocals的主要工作:与SurfaceFlinger的EventThread线程建立起连接,添加一个Vsync信号处理函数RenderThread::displayEventReceiverCallback()等工作,这一部分先不展开,与本文相关的点为:初始化JankTracker,卡顿追踪器,看一下构造方法:
JankTracker::JankTracker(const DisplayInfo& displayInfo) {
// By default this will use malloc memory. It may be moved later to ashmem
// if there is shared space for it and a request comes in to do that.
mData = new ProfileData;
reset();
nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps);
#if USE_HWC2
nsecs_t sfOffset = frameIntervalNanos - (displayInfo.presentationDeadline - 1_ms);
nsecs_t offsetDelta = sfOffset - displayInfo.appVsyncOffset;
// There are two different offset cases. If the offsetDelta is positive
// and small, then the intention is to give apps extra time by leveraging
// pipelining between the UI & RT threads. If the offsetDelta is large or
// negative, the intention is to subtract time from the total duration
// in which case we can't afford to wait for dequeueBuffer blockage.
if (offsetDelta <= 4_ms && offsetDelta >= 0) {
// SF will begin composition at VSYNC-app + offsetDelta. If we are triple
// buffered, this is the expected time at which dequeueBuffer will
// return due to the staggering of VSYNC-app & VSYNC-sf.
mDequeueTimeForgiveness = offsetDelta + 4_ms;
}
#endif
setFrameInterval(frameIntervalNanos);
}
void JankTracker::setFrameInterval(nsecs_t frameInterval) {
mFrameInterval = frameInterval;
mThresholds[kMissedVsync] = 1;
/*
* Due to interpolation and sample rate differences between the touch
* panel and the display (example, 85hz touch panel driving a 60hz display)
* we call high latency 1.5 * frameinterval
*
* NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel
* on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms
* Thus this must always be larger than frameInterval, or it will fail
*/
mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval);
// Note that these do not add up to 1. This is intentional. It's to deal
// with variance in values, and should be sort of an upper-bound on what
// is reasonable to expect.
mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval);
mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval);
mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval);
}
从执行过程看,JankTracker创建时,会new ProfileData,然后reset,而Since值就是在这里赋值上的,后面都不会再改动。然后调用setFrameInterval()方法,其中包含: kMissedVsync, kHighInputLatency, kSlowUI, kSlowSync, kSlowRT,这部分就时跟dump 信息相关的东西,按平时每帧绘制时间16.67ms计算, kMissedVsync为1×16.67, kHighInputLatency为1.5*16.67=25ms,kSlowUI=0.5*16.67=8.3ms,kSlowSync=0.2*16.67=3.3ms,kSlowRT=0.75*16.67=12.5ms,这几个值是在渲染过程中的评判标准,具体评判过程是在addFrame,也就是在渲染每一帧时会统计相应的数量,涉及到View的渲染过程,我没有细看,后面有机会我再总结这部分吧。从分析过程看,信息填充时在View渲染时,JankTracker根据系统中定义的不同时间值做相关统计,更新mData。
总结
一个window会对应一个ViewRootImpl,而一个ViewRootImpl对应一个ThreadRender,当一个进程启动时,会有UI展示,那么会创建window,进而最终创建ActiveBuffer,而ActiveBuffer中的值会先去到mActive和mHistoricalLog中查询,如果查找到,则使用,因此进程及时挂掉再重启,Since值依然不会变,其实使用的是原来的内存,如果进程是第一次创建且在GraphicsStatsService的mActive和mHistoricalLog没有相关信息时,新建的ActiveBuffer的mProcessBuffer没有数据,在创建JankTracker对象时 会新建ProfileData,初始化mData->statStartTime,此时mData还在进程的私有内存中,通过调用JankTracker.switchStorageToAshmem将其映射至共享内存中。
mData->statStartTime = systemTime(CLOCK_MONOTONIC);
问题解释
下面来解释下开始的几个问题,其实就比较简单了:
问题1:为什么会有两个相同的package
当有两个进程同时有UI操作时,且两个package相同时就会出现
如systemui运行的情况下,开启截屏程序,此时会有截屏的window,对应一个ViewRootImpl,即会初始化ThreadedRenderer,此时就会触发创建ActiveBuffer,而在mActive中通过pid和uid查找(截屏进程pid与systemui不同),未查找到就会新建ActiveBuffer,此时就会有两个相同的package信息
问题2:进程死掉后再重启,为什么Since值不变
此部分在上面已经解释过了,进程挂掉时,会回调binderDied方法做一些处理,即清楚mActive中相关信息,将信息保留在mHistoricalLog中,而在进程再次被创建时,会新建ActiveBuffer,取出mHistoricalLog中信息复用,因此Since值不变。
问题3:有没有3个Package相同的情况
像问题1中分析的,如果有一个主进程存在,二个进程名相同的子进程同样存在有ui,那么可能会有3个Package相同的情况,但这种情况应该很少,一般程序的设计基本都是有UI进程,其他进程均为服务进程没有UI,像有三个进程package相同且均有ui的情况很少,但不排除这种可能,还要一种情况,比如用户了应用双开或者手机分身,此时dump信息就会出现许多package相同的情况,pid和uid都不同,会新建ActiveBuffer。
问题4:Since值什么时候改变
Since值改变的地方为创建JankTracker对象时,将mData->statStartTime初始化为系统时间,那什么时候会触发这个逻辑呢?就是匿名共享内存不可用了,或者手机重启过,或者匿名共享内存被回收了,在GraphicsStatsService中有一个变量:
private static final int HISTORY_SIZE = 20;
此变量的值决定了HistoricalData的个数,即系统中最多保存20个历史数据,那么如果手机运行时间很长,或者手机启动非常多的App,而手机系统RAM较小时,很可能会出现Since值改变问题,此时应该注意,数据很可能会有丢失,导致数据不准确,此时dump出来的数据要小心对待,最好丢弃掉,再重新测试看。