Pury Project Analysis

Hujiawei Bujidao


Pury Project Analysis



Pury is a profiling library for measuring time between multiple independent events. Events can be triggered with one of the annotations or with a method call. All events for a single scenario are united into one report.


Performance measurements are done by Profilers. Each Profiler contains a list of Runs. Multiple Profilers can work in parallel, but only a single Run per each Profiler can be active. Once all Runs in a single Profiler are finished, result is reported. Amount of runs defines by runsCounter parameter.

Run has a root Stage inside. Each Stage has a name, an order number and an arbitrary amount of nested Stages. Stage can have only one active nested Stage. If you stop a parent Stage, then all nested Stages are also stopped.



1. 源码结构

1.1 annotations:纯Java应用,已发布到maven上,名称是pury-annotations,其中主要是定义了MethodProfilingStartProfilingStopProfiling三个注解

1.2 pury:核心工程,依赖了annotations和aspectj,已发布到maven上,名称是pury

compile 'com.nikitakozlov.pury:annotations:1.0.1'
compile 'org.aspectj:aspectjrt:1.8.6'

1.3 example:应用示例,依赖了pury,演示了几个场景下的几个方法的监控示例

2. 使用方法


profilerName — name of the profiler is displayed in the result. Along with runsCounter identifies the Profiler. runsCounter — amount of runs for Profiler to wait for. Result is available only after all runs are stopped. stageName — identifies a stage to start. Name is displayed in the result. stageOrder — stage order reflects the hierarchy of stages. In order to start a new stage, it must be bigger then order of current most nested active stage. Stage order is a subject to one more limitation: first start event must have order number equal zero. enabled — if set to false, an annotation is skipped.

Profiler is identified by combination of profilerName and runsCounter. So if you are using same profilerName, but different runsCounter, then you will get two separate results, instead of a combined one.

profiler对应一个需要监控的场景,runsCounter是指监控场景需要执行的次数 stage对应这个场景下需要监控的方法,stageOrder是指监控方法的对应层级 需要注意的是Profiler是由profilerName和runsCounter两个共同决定的,也就是说如果profilerName相同但是runsCounter不同的话是两个不同的监控场景,最终会得到两个独立的结果。


@StartProfiling(profilerName = StartApp.PROFILER_NAME, stageName = StartApp.SPLASH_LOAD_DATA,
        stageOrder = StartApp.SPLASH_LOAD_DATA_ORDER)
private void loadData() {
    new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {

        public void run() {
    }, 1000);

@StopProfiling(profilerName = StartApp.PROFILER_NAME, stageName = StartApp.SPLASH_SCREEN)
private void onDataLoaded() {


监控App Start场景下的方法调用时长的输出示例,它表示监控的场景名(ProfilerName)是App Start,这个场景总共耗时1182ms,这个场景下有6个stage,分别是App StartSplash ScreenSplash Load DataMain Activity LaunchonCreate()onStart(),下面的输出显示了每个stage的运行时间。

Profiling results for App Start:
App Start --> 0ms
  Splash Screen --> 5ms
    Splash Load Data --> 37ms
    Splash Load Data <-- 1042ms, execution = 1005ms
  Splash Screen <-- 1042ms, execution = 1037ms
  Main Activity Launch --> 1043ms 
    onCreate() --> 1077ms 
    onCreate() <-- 1100ms, execution = 23ms
    onStart() --> 1101ms 
    onStart() <-- 1131ms, execution = 30ms
  Main Activity Launch <-- 1182ms, execution = 139ms
App Start <-- 1182ms

监控Pagination场景下的方法调用时长的输出示例,它统计了Pagination这个场景下的3个stage,分别是Get Next PageLoadProcess,每个stage都会运行5次并统计avg、min和max用时。

Profiling results for Pagination:
Get Next Page --> 0ms
  Load --> avg = 1.80ms, min = 1ms, max = 3ms, for 5 runs
  Load <-- avg = 258.40ms, min = 244ms, max = 278ms, for 5 runs
  Process --> avg = 261.00ms, min = 245ms, max = 280ms, for 5 runs
  Process <-- avg = 114.20ms, min = 99ms, max = 129ms, for 5 runs
Get Next Page <-- avg = 378.80ms, min = 353ms, max = 411ms, for 5 runs

3. 核心代码分析

3.1 annotations工程中的注解


 * Combination of {@link StartProfiling} and {@link StopProfiling}. If stage name is empty, then stage name from method's name and class will be generated.
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface MethodProfiling {
     * Profiler Name, used in results.
    String profilerName() default "";

     * Name of stage to start. Used in results. If stage name is empty, then stage name from method's name and class will be generated.
    String stageName() default "";

     * Stage order must be bigger then order of current most nested active stage.
     * First profiling must starts with value 0.
    int stageOrder() default 0;

     * Amount of runs to average. Result will be available only after all runs are stopped.
    int runsCounter() default 1;

     * Set to false if you want to skip this annotation.
    boolean enabled() default true;

3.2 pury工程中的注解处理类


下面是ProfileMethodAspect的源码,其中定义了4个PointCut以及1个Around Advice。方法weaveJoinPoint是核心方法,它的主要执行流程是:假设我们对方法M提供了MethodProfiling注解,weaveJointPoint先会根据注解提供的参数去获取并启动所有相关的stage,也就是该方法所在的所有场景(profiler)下的对应stage,然后调用方法M使其执行,最后再停止所有的stage。

public class ProfileMethodAspect {
    private static final String POINTCUT_METHOD =
            "execution(@com.nikitakozlov.pury.annotations.MethodProfiling * *(..))";

    private static final String POINTCUT_CONSTRUCTOR =
            "execution(@com.nikitakozlov.pury.annotations.MethodProfiling *.new(..))";

    private static final String GROUP_ANNOTATION_POINTCUT_METHOD =
            "execution(@com.nikitakozlov.pury.annotations.MethodProfilings * *(..))";

    private static final String GROUP_ANNOTATION_POINTCUT_CONSTRUCTOR =
            "execution(@com.nikitakozlov.pury.annotations.MethodProfilings *.new(..))";

    public void method() {

    public void constructor() {

    public void methodWithMultipleAnnotations() {

    public void constructorWithMultipleAnnotations() {

    @Around("constructor() || method() || methodWithMultipleAnnotations() || constructorWithMultipleAnnotations()")
    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        ProfilingManager profilingManager = ProfilingManager.getInstance();
        List<StageId> stageIds = getStageIds(joinPoint);
        for (StageId stageId : stageIds) {
                    .startStage(stageId.getStageName(), stageId.getStageOrder());

        Object result = joinPoint.proceed();

        for (StageId stageId : stageIds) {

        return result;

    private List<StageId> getStageIds(ProceedingJoinPoint joinPoint) {
        if (!Pury.isEnabled()) {
            return Collections.emptyList();

        Annotation[] annotations =
                ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotations();
        List<StageId> stageIds = new ArrayList<>();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType() == MethodProfiling.class) {
                StageId stageId = getStageId((MethodProfiling) annotation, joinPoint);
                if (stageId != null) {
            if (annotation.annotationType() == MethodProfilings.class) {
                for (MethodProfiling methodProfiling : ((MethodProfilings) annotation).value()) {
                    StageId stageId = getStageId(methodProfiling, joinPoint);
                    if (stageId != null) {
        return stageIds;

    private StageId getStageId(MethodProfiling annotation, ProceedingJoinPoint joinPoint) {
        if (!annotation.enabled()) {
            return null;
        ProfilerId profilerId = new ProfilerId(annotation.profilerName(), annotation.runsCounter());
        String stageName = annotation.stageName();
        if (stageName.isEmpty()) {
            CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
            String className = codeSignature.getDeclaringType().getSimpleName();
            String methodName = codeSignature.getName();
            stageName = className + "." + methodName;

        return new StageId(profilerId, stageName, annotation.stageOrder());

StartProfilingAspectStopProfilingAspect与之类似,只不过前者定义的是@Before advice,而后者定义的是@After advice。

3.3 核心类Pury


public final class Pury {
    static volatile Logger sLogger;
    static volatile boolean sEnabled = true;

    public static void setLogger(Logger logger) {
        sLogger = logger;

    public synchronized static Logger getLogger() {
        if (sLogger == null) {
            sLogger = new DefaultLogger();
        return sLogger;

    public static boolean isEnabled() {
        return sEnabled;

    public synchronized static void setEnabled(boolean enabled) {
        if (!enabled) {
        sEnabled = enabled;

     * @param profilerName used to identify profiler. Used in results.
     * @param stageName Name of stage to start. Used in results.
     * @param stageOrder Stage order must be bigger then order of current most nested active stage.
     *                   First profiling must starts with value 0.
     * @param runsCounter used to identify profiler. Amount of runs to average.
     *                    Result will be available only after all runs are stopped.
    public static void startProfiling(String profilerName, String stageName, int stageOrder, int runsCounter) {
        ProfilerId profilerId = new ProfilerId(profilerName, runsCounter);
        ProfilingManager.getInstance().getProfiler(profilerId).startStage(stageName, stageOrder);

     * @param profilerName used to identify profiler. Used in results.
     * @param stageName  Name of stage to stop. Used in results.
     * @param runsCounter used to identify profiler. Amount of runs to average.
     *                    Result will be available only after all runs are stopped.
    public static void stopProfiling(String profilerName, String stageName, int runsCounter) {
        ProfilerId profilerId = new ProfilerId(profilerName, runsCounter);


ProfilerId profilerId = new ProfilerId(profilerName, runsCounter);


ProfilingManager.getInstance().getProfiler(profilerId).startStage(stageName, stageOrder);

ProfilingManager.getInstance().getProfiler(profilerId).stopStage(stageName, stageOrder);

3.4 其他包和类


4. 其他内容

4.1 Pury的优缺点



4.2 Pury使用的gradle插件

发布到maven使用的gradle插件是https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle 实现注解解析的gradle插件是com.nikitakozlov.weaverlite,这个是作者自己封装的插件WeaverLite


Hujiawei is a mobile developer Guangdong, China https://hujiaweibujidao.github.io/ 本博客所有文章均为原创,请勿随意转载,如需转载请联系我 (hujiawei090807 AT gmail.com) 我在小专栏有个移动开发技术专栏,不定期分享移动开发的核心技术,总结移动开发的实战经验
所有文章皆为原创,内容制作精良,保证干货满满,欢迎订阅 (https://xiaozhuanlan.com/u/javayhu)
>>> 我最近在Android面试指南小专栏里面写了一篇稿子 [Android面试——算法面试心得] ,欢迎阅读!<<<