析构函数的妙用

代码之美

前言

在 C++ 中,当一个对象被销毁的时候,会自动调用这个对象的析构函数,无需显示调用。利用这一特性,可以有很多的妙用,本文将介绍在 Android framework 中两个常用的例子。

systrace 中的函数运行时间

我们在看 systrace 的时候,经常能够看到这样的画面:

systrace

图中每个矩形的宽度代表的是这个函数运行的时间,那么这是这么做到的呢?

在源码中可以看到,图中 onMessageReceived()handleMessageRefresh() 等,这些函数的第一行,都调用了 ATRACE_CALL()

void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS {
    ATRACE_CALL();
    
void SurfaceFlinger::handleMessageRefresh() {
    ATRACE_CALL();

这个函数应该就是答案了,看一下实现:

// ATRACE_NAME traces from its location until the end of its enclosing scope.
#define _PASTE(x, y) x ## y
#define PASTE(x, y) _PASTE(x,y)
#define ATRACE_NAME(name) android::ScopedTrace PASTE(___tracer, __LINE__) (ATRACE_TAG, name)

// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)

namespace android {

class ScopedTrace {
public:
    inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) {
        atrace_begin(mTag, name);
    }

    inline ~ScopedTrace() {
        atrace_end(mTag);
    }

private:
    uint64_t mTag;
};

}  // namespace android

可以看到,当调用 ATRACE_CALL() 时,相当于定义了一个类型为 ScopedTrace 的变量,这个变量本身没有什么特别的,重要的是这个 ScopedTrace 这个类,其构造函数会去调用 atrace_begin(),析构函数会去调用 atrace_end()。也就是说,在函数开头,定义一个 ScopedTrace 类型的变量,经由构造函数去调用 atrace_begin();然后等函数结束,前面定义的 ScopedTrace 会被销毁,会经由析构函数调用 atrace_end(),从而达到了统计一个函数的运行时间的效果。

这里稍微补充一下,在 Android 里,总能听到各种各样的 trace:如 atrace, ftrace, systrace 等,那么这些 trace 究竟有什么区别呢?

  • ftrace
    ftrace 是 Linux Kernel 自带的调试框架,它在内核态工作,用户可以通过 debugfs 接口来控制和使用 ftrace。目前 debugfs 一般挂载在 /sys/kernel/debug/tracing/
  • atrace
    这个是 Android 针对 ftrace 的一个封装,它将 ftrace 的各种 Event 抽象为 tag,一个 atrace tag 对应多个 ftrace Event,例如 sched 这个 tag 分别包括了 sched/sched_switchsched/sched_wakeupsched/sched_wakingsched/sched_blocked_reasonsched/sched_cpu_hotplug 等这些 Event。
  • systrace
    systrace 其实是 Android SDK 里的一个工具,是对 atrace 的主机端封装,利用 atrace 来使能 ftrace,然后读取 ftrace 的缓冲区并将其全部封装到一个独立的 HTML 文件。

具体的可以看一下内核工匠的这篇文章,写得很详细。

而我们在 framework 中,Java 代码常用的 Trace.traceBegin()Trace.traceEnd();C++ 有 atrace_begin()atrace_end() 以及一系列 ATRACE_ 打头的宏,其实都是利用 atrace 来实现 trace 的抓取。

Mutex 锁自动释放

在 framework 还有另外一处也有类似的用法,那就是锁。framework 经常能够看到类似下面的代码:

sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName,
        bool secure)
{
    sp<BBinder> token = new DisplayToken(this);

    // Mutex 是在哪里释放的呢?
    Mutex::Autolock _l(mStateLock);
    // Display ID is assigned when virtual display is allocated by HWC.
    DisplayDeviceState state;
    state.isSecure = secure;
    state.displayName = displayName;
    mCurrentState.displays.add(token, state);
    mInterceptor->saveDisplayCreation(state);
    return token;
}

这个 Mutex 是在哪里释放的呢?原理其实跟前面的 ATRACE_CALL() 是一样的,在析构函数里面去释放锁,就不赘述了。

Author: simowce

Permalink: https://blog.simowce.com/destrcutor/

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。