GraphicsStatsService常见的几个问题带来的思考

最近有一部分工作涉及到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.csdn.net/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出来的数据要小心对待,最好丢弃掉,再重新测试看。

猜你喜欢

转载自blog.csdn.net/longlong2015/article/details/75579431