Sentry(Android)源码解析

news2024/9/26 5:22:56

5178777f20dd1100b7bae79b884f7f6b.jpeg

5d3cb3038172cedd7d04674f7dd053d6.gif

本文字数:16030

预计阅读时间:40分钟

049ed09a1152d5df58a2ffe7726054fa.png

01

前言

Sentry是一个日志记录、错误上报、性能监控的开源框架,支持众多平台:

2dcab304a8b2529bc8e6fb7d446b8c64.jpeg

其使用方式在本文不进行说明了,大家可参照官方文档:https://docs.sentry.io/platforms/android/?original_referrer=https%3A%2F%2Fsentry.io%2F  

目前大部分免费的三方APM平台限制较多,好用的又收费。在降本增效的大环境下,免费开源是开发者们的目标。因此开源的Sentry平台,在基础能力上已经满足了绝大多数开发场景。而对于想深入定制,打造自己APM平台的同学们来说,Sentry也是个可以依托并以此为基础进行改造的捷径。

本文将对Sentry Android SDK(6.31.0)进行源码解析,给大家一些改造拓展的思路。 

02

基础结构说明

在讲解Sentry之前,先介绍一些基础的结构,方便之后的理解。 

2.1.SentryEvent和SentryTransaction

先介绍两个基本概念,即SentryEvent和SentryTransaction。它们俩就是Sentry支持的事件,继承自同一个父类:SentryBaseEvent。简单来说其中我们在后台看到的Issues就是SentryEvent,Performance就是SentryTransaction。一次发送一个事件,每个SentryBaseEvent都有唯一的eventId。 

2.2.Scope

Scope是保存与event一起发送的有用信息。如context,breadCrumb等。当设置了Scope里的某个属性,那么在整个Scope中都会将此属性赋值给event。 

2.3.SentryClient

SentryClient是客户端用来真正处理各种事件发送逻辑的。比方说我们调用captureEvent(),最终的实现就是在SentryClient里。 

2.4.Hub

Hub是用来管理Scope和SentryClient的。在Sentry初始化时,会创建Hub对象,Hub创建Scope和SentryClient并进行管理。了解了这些之后,我们来看源码。

03

初始化

在详细梳理初始化流程之前,我们把关键步骤梳理成图方便大家理解:

96d973cd4d6d8b0aa1f395cf69e822d3.jpeg

接下来我们分析具体的实现。

3.1.SentryAndroid.init()

我们先从初始化开始分析。SentryAndroid.java位于sentry-android-core这个包内。934faab6a00d824e996b4f70577cd7b0.jpeg

从类的结构来看我们发现,SentryAndroid实际上只做了初始化这个操作:f1b7227d7491ef5603f928148c0eb863.jpeg

//SentryAndroid.java
//Sentry initialization with a configuration handler and custom logger
//Params:
//context – Application. context 
//logger – your custom logger that implements ILogger 
//configuration – Sentry.OptionsConfiguration configuration handler
  public static synchronized void init(
      @NotNull final Context context,
      @NotNull ILogger logger,
      @NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration) {
    // if SentryPerformanceProvider was disabled or removed, we set the App Start when
    // the SDK is called.
    AppStartState.getInstance().setAppStartTime(appStart, appStartTime);

    try {
      Sentry.init(
          OptionsContainer.create(SentryAndroidOptions.class),
          options -> {
            final LoadClass classLoader = new LoadClass();
            final boolean isTimberUpstreamAvailable =
                classLoader.isClassAvailable(TIMBER_CLASS_NAME, options);
            final boolean isFragmentUpstreamAvailable =
                classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options);
            final boolean isFragmentAvailable =
                (isFragmentUpstreamAvailable
                    && classLoader.isClassAvailable(
                        SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options));
            final boolean isTimberAvailable =
                (isTimberUpstreamAvailable
                    && classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));

            final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
            final LoadClass loadClass = new LoadClass();
            final ActivityFramesTracker activityFramesTracker =
                new ActivityFramesTracker(loadClass, options);

            AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
                options, context, logger, buildInfoProvider);

            // We install the default integrations before the option configuration, so that the user
            // can remove any of them. Integrations will not evaluate the options immediately, but
            // will use them later, after being configured.
            AndroidOptionsInitializer.installDefaultIntegrations(
                context,
                options,
                buildInfoProvider,
                loadClass,
                activityFramesTracker,
                isFragmentAvailable,
                isTimberAvailable);

            configuration.configure(options);

            AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
                options, context, buildInfoProvider, loadClass, activityFramesTracker);

            deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
          },
          true);

      final @NotNull IHub hub = Sentry.getCurrentHub();
      if (hub.getOptions().isEnableAutoSessionTracking()
          && ContextUtils.isForegroundImportance(context)) {
        hub.addBreadcrumb(BreadcrumbFactory.forSession("session.start"));
        hub.startSession();
      }
    } catch (IllegalAccessException e) {
//..
    } catch (InstantiationException e) {
//...
    } catch (NoSuchMethodException e) {
//...
    } catch (InvocationTargetException e) {
//...
    }
  }

我们看到在执行Sentry.init()之前先执行了:

AppStartState.getInstance().setAppStartTime(appStart, appStartTime);
synchronized void setAppStartTime(
      final long appStartMillis, final @NotNull SentryDate appStartTime) {
    // method is synchronized because the SDK may by init. on a background thread.
    if (this.appStartTime != null && this.appStartMillis != null) {
      return;
    }
    this.appStartTime = appStartTime;
    this.appStartMillis = appStartMillis;
  }

记录了appStartTime和appStartMillis。从类型上来看,一个是日期,一个是时间戳。我们看一下这两个变量的获取规则:

appStartMillis:

//SentryAndroid.java
  // SystemClock.uptimeMillis() isn't affected by phone provider or clock changes.
  private static final long appStart = SystemClock.uptimeMillis();

记录了自开机以来的运行时间(毫秒级)。

appStartTime:

//SentryAndroid.java
  // static to rely on Class load init.
  private static final @NotNull SentryDate appStartTime =
      AndroidDateUtils.getCurrentSentryDateTime();
//AndroidDateUtils.java
public final class AndroidDateUtils {

  private static final SentryDateProvider dateProvider = new SentryAndroidDateProvider();

  public static @NotNull SentryDate getCurrentSentryDateTime() {
    return dateProvider.now();
  }
}
//SentryNanotimeDateProvider.java
public final class SentryNanotimeDateProvider implements SentryDateProvider {

  @Override
  public SentryDate now() {
    return new SentryNanotimeDate();
  }
}
//SentryNanotimeDate.java
  private final @NotNull Date date;
  private final long nanos;

  public SentryNanotimeDate() {
    this(DateUtils.getCurrentDateTime(), System.nanoTime());
  }
//DateUtils.java
  public static @NotNull Date getCurrentDateTime() {
    final Calendar calendar = Calendar.getInstance(TIMEZONE_UTC);
    return calendar.getTime();
  }

到这里,我们可以看到:appStartTime记录了当前时区的日期,和当前的高精度时间戳(精确到纳秒级)。之后SentryAndroid主要执行了Sentry.init()方法。

我们继续分析Sentry.init()的实现。

3.2.Sentry.init()

Sentry.java位于sentry-6.31.0这个包下:7ce94933755313d05bcaef1f760ac8f4.jpeg

我们先来看看Sentry.init()的实现:

//Sentry.java
  public static <T extends SentryOptions> void init(
      final @NotNull OptionsContainer<T> clazz,
      final @NotNull OptionsConfiguration<T> optionsConfiguration,
      final boolean globalHubMode)
      throws IllegalAccessException, InstantiationException, NoSuchMethodException,
          InvocationTargetException {
    final T options = clazz.createInstance();
    applyOptionsConfiguration(optionsConfiguration, options);
    init(options, globalHubMode);
  }

首先三个参数,类型分别是OptionsContainer,OptionsConfiguration和boolean。其中最后一个参数globalHubMode传的是true。然后调用applyOptionsConfiguration(),最后再执行init()方法。我们再来看看头两个参数是如何定义的。

3.2.1 final @NotNull OptionsContaine<T>clazz:**

//SentryAndroid.java
OptionsContainer.create(SentryAndroidOptions.class)
//OptionsContainer.java
public final class OptionsContainer<T> {

  public @NotNull static <T> OptionsContainer<T> create(final @NotNull Class<T> clazz) {
    return new OptionsContainer<>(clazz);
  }

  private final @NotNull Class<T> clazz;

  private OptionsContainer(final @NotNull Class<T> clazz) {
    super();
    this.clazz = clazz;
  }

  public @NotNull T createInstance()
      throws InstantiationException, IllegalAccessException, NoSuchMethodException,
          InvocationTargetException {
    return clazz.getDeclaredConstructor().newInstance();
  }
}

OptionsContainer.create()传的是SentryAndroidOptions这个class,返回的是OptionsContainer<SentryAndroidOptions>。在Sentry.java中调用clazz.createInstance()方法执行了SentryAndroidOptions的构造方法:

//SentryAndroidOptions.java
  public SentryAndroidOptions() {
    setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
    setSdkVersion(createSdkVersion());
    setAttachServerName(false);

    // enable scope sync for Android by default
    setEnableScopeSync(true);
  }

我们看做了一些Android相关的基础配置。SentryAndroidOptions的父类OptionsContainer的构造方法如下:

//SentryOptions.java
  private SentryOptions(final boolean empty) {
    if (!empty) {
      // SentryExecutorService should be initialized before any
      // SendCachedEventFireAndForgetIntegration
      executorService = new SentryExecutorService();

      // UncaughtExceptionHandlerIntegration should be inited before any other Integration.
      // if there's an error on the setup, we are able to capture it
      integrations.add(new UncaughtExceptionHandlerIntegration());

      integrations.add(new ShutdownHookIntegration());

      eventProcessors.add(new MainEventProcessor(this));
      eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));

      if (Platform.isJvm()) {
        eventProcessors.add(new SentryRuntimeEventProcessor());
      }

      setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
      setSdkVersion(createSdkVersion());
      addPackageInfo();
    }
  }

3.2.1.1.SentryExecutorService

首先初始化了一个SentryExecutorService:

//SentryExecutorService.java
  SentryExecutorService() {
    this(Executors.newSingleThreadScheduledExecutor(new SentryExecutorServiceThreadFactory()));
  }
//SentryExecutorService.java
  @Override
  public @NotNull Future<?> submit(final @NotNull Runnable runnable) {
    return executorService.submit(runnable);
  }

这个service开启了一个新线程执行了submit()方法,我们追踪一下代码发现这个方法有多处调用,最主要的是SendCachedEnvelopeFireAndForgetIntegration调用了,而SendCachedEnvelopeFireAndForgetIntegration的作用是在App启动的时候发送在cache中的event用的。

我们继续看SentryOptions.java的构造方法,发现会为integrations列表添加各种Integration,我们看一个最常见的UncaughtExceptionHandlerIntegration来分析,从命名上来看这个Integration就是用来抓抛出来的异常用的。

3.2.1.2. Integration

//UncaughtExceptionHandlerIntegration.java
  public UncaughtExceptionHandlerIntegration() {
    this(UncaughtExceptionHandler.Adapter.getInstance());
  }

  UncaughtExceptionHandlerIntegration(final @NotNull UncaughtExceptionHandler threadAdapter) {
    this.threadAdapter = Objects.requireNonNull(threadAdapter, "threadAdapter is required.");
  }
//UncaughtExceptionHandler.java
interface UncaughtExceptionHandler {
  Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler();

  void setDefaultUncaughtExceptionHandler(@Nullable Thread.UncaughtExceptionHandler handler);

  final class Adapter implements UncaughtExceptionHandler {

    static UncaughtExceptionHandler getInstance() {
      return Adapter.INSTANCE;
    }

    private static final Adapter INSTANCE = new Adapter();

    private Adapter() {}

    @Override
    public Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {
      return Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void setDefaultUncaughtExceptionHandler(
        final @Nullable Thread.UncaughtExceptionHandler handler) {
      Thread.setDefaultUncaughtExceptionHandler(handler);
    }
  }
}

其中Adapter实现了UncaughtExceptionHandler接口。我们回到UncaughtExceptionHandlerIntegration.java,它实现了Integration和Thread.UncaughtExceptionHandler接口,其中Integration的定义如下:


//Integration.java
public interface Integration extends IntegrationName {
  /**
   * Registers an integration
   *
   * @param hub the Hub
   * @param options the options
   */
  void register(@NotNull IHub hub, @NotNull SentryOptions options);
}

只有一个register()方法,我们回到UncaughtExceptionHandlerIntegration,看一下它的结构:bed8bc754031932b72606b933b03f015.jpeg

先看一下register()都做了什么:

//UncaughtExceptionHandlerIntegration.java
  @Override
  public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
    if (registered) {
//...
      return;
    }
    registered = true;

    this.hub = Objects.requireNonNull(hub, "Hub is required");
    this.options = Objects.requireNonNull(options, "SentryOptions is required");
//...

    if (this.options.isEnableUncaughtExceptionHandler()) {
      final Thread.UncaughtExceptionHandler currentHandler =
          threadAdapter.getDefaultUncaughtExceptionHandler();
      if (currentHandler != null) {
//...
        defaultExceptionHandler = currentHandler;
      }

      threadAdapter.setDefaultUncaughtExceptionHandler(this);
//...
    }
  }

初始化了hub和options。最主要的逻辑就是注册了Thread.UncaughtExceptionHandler等待异常抛出时作处理。那么我们再来看uncaughtException()的实现:

//UncaughtExceptionHandlerIntegration.java
  @Override
  public void uncaughtException(Thread thread, Throwable thrown) {
    if (options != null && hub != null) {
      options.getLogger().log(SentryLevel.INFO, "Uncaught exception received.");

      try {
        final UncaughtExceptionHint exceptionHint =
            new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger());
        final Throwable throwable = getUnhandledThrowable(thread, thrown);
        final SentryEvent event = new SentryEvent(throwable);
        event.setLevel(SentryLevel.FATAL);

        final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint);

        final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
        final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
        final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint);
//...
      } catch (Throwable e) {
//...
      }

      if (defaultExceptionHandler != null) {
        options.getLogger().log(SentryLevel.INFO, "Invoking inner uncaught exception handler.");
        defaultExceptionHandler.uncaughtException(thread, thrown);
      } else {
        if (options.isPrintUncaughtStackTrace()) {
          thrown.printStackTrace();
        }
      }
    }
  }

主要逻辑是创建了一个SentryEvent并将Throwable包进去,然后调用hub.captureEvent(event, hint)(之后再讲),将event上报到Sentry。到此,我们知道了UncaughtExceptionHandlerIntegration的作用就是为了将异常上报给Sentry后台的,而它实现了Integration接口,会在合适的时候将自己注册给Sentry。其他实现了Integration接口的类,目的也是将自己注册给Sentry进行绑定,并提供相应的方法去hook一些自己想要的逻辑。Integration的注册时机我们之后再讲,接下来看回到SentryOptions.java:

//SentryOptions.java
  private SentryOptions(final boolean empty) {
    if (!empty) {
      // SentryExecutorService should be initialized before any
      // SendCachedEventFireAndForgetIntegration
      executorService = new SentryExecutorService();

      // UncaughtExceptionHandlerIntegration should be inited before any other Integration.
      // if there's an error on the setup, we are able to capture it
      integrations.add(new UncaughtExceptionHandlerIntegration());

      integrations.add(new ShutdownHookIntegration());

      eventProcessors.add(new MainEventProcessor(this));
      eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));

      if (Platform.isJvm()) {
        eventProcessors.add(new SentryRuntimeEventProcessor());
      }

      setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
      setSdkVersion(createSdkVersion());
      addPackageInfo();
    }
  }

看eventProcessors.add()都干了什么,以MainEventProcessor为例。 

3.2.1.3.EventProcessor

MainEventProcessor实现了EventProcessor接口,EventProcessor是为SentryEvent或SentryTransaction服务的,目的是在发送事件时插入一些附属信息:
//EventProcessor.java
public interface EventProcessor {

  /**
   * May mutate or drop a SentryEvent
   *
   * @param event the SentryEvent
   * @param hint the Hint
   * @return the event itself, a mutated SentryEvent or null
   */
  @Nullable
  default SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
    return event;
  }

  /**
   * May mutate or drop a SentryTransaction
   *
   * @param transaction the SentryTransaction
   * @param hint the Hint
   * @return the event itself, a mutated SentryTransaction or null
   */
  @Nullable
  default SentryTransaction process(@NotNull SentryTransaction transaction, @NotNull Hint hint) {
    return transaction;
  }
}

两个process()方法分别作用于SentryEvent或SentryTransaction。回到MainEventProcessor,看看process()的实现:

//MainEventProcessor.java
  @Override
  public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
    setCommons(event);
    setExceptions(event);
    setDebugMeta(event);
    setModules(event);

    if (shouldApplyScopeData(event, hint)) {
      processNonCachedEvent(event);
      setThreads(event, hint);
    }

    return event;
  }

  @Override
  public @NotNull SentryTransaction process(
      final @NotNull SentryTransaction transaction, final @NotNull Hint hint) {
    setCommons(transaction);
    setDebugMeta(transaction);

    if (shouldApplyScopeData(transaction, hint)) {
      processNonCachedEvent(transaction);
    }

    return transaction;
  }

逻辑很简单,就是做默认配置用的,通常如果自定义了一些信息就走自定义的,没有的话就配置默认信息。process()的调用时机是发送一个事件到Sentry后台时将基础信息进行配置,代码我们之后再来看。回到Sentry.init()方法:

//Sentry.java
  public static <T extends SentryOptions> void init(
      final @NotNull OptionsContainer<T> clazz,
      final @NotNull OptionsConfiguration<T> optionsConfiguration,
      final boolean globalHubMode)
      throws IllegalAccessException, InstantiationException, NoSuchMethodException,
          InvocationTargetException {
    final T options = clazz.createInstance();
    applyOptionsConfiguration(optionsConfiguration, options);
    init(options, globalHubMode);
  }

clazz怎么创建的,并且clazz.createInstance()都干了什么我们就清楚了。

总结一下:

1.初始化了SentryAndroidOptions做各种基础配置并返回;

2.定义了各种Integration和EventProcessor,hook需要的时机,获取基础参数,为之后发送事件作准备。接下来我们看一下optionsConfiguration。

3.2.2  @NotNullOptionsConfiguration<T>optionsConfiguration

OptionsConfiguration是个接口,applyOptionsConfiguration()调用其configure()方法做一些基础的配置,所以回到SentryAndroid.java:

//SentryAndroid.java
          options -> {
            final LoadClass classLoader = new LoadClass();
            final boolean isTimberUpstreamAvailable =
                classLoader.isClassAvailable(TIMBER_CLASS_NAME, options);
            final boolean isFragmentUpstreamAvailable =
                classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options);
            final boolean isFragmentAvailable =
                (isFragmentUpstreamAvailable
                    && classLoader.isClassAvailable(
                        SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options));
            final boolean isTimberAvailable =
                (isTimberUpstreamAvailable
                    && classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));

            final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
            final LoadClass loadClass = new LoadClass();
            final ActivityFramesTracker activityFramesTracker =
                new ActivityFramesTracker(loadClass, options);

            AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
                options, context, logger, buildInfoProvider);

            // We install the default integrations before the option configuration, so that the user
            // can remove any of them. Integrations will not evaluate the options immediately, but
            // will use them later, after being configured.
            AndroidOptionsInitializer.installDefaultIntegrations(
                context,
                options,
                buildInfoProvider,
                loadClass,
                activityFramesTracker,
                isFragmentAvailable,
                isTimberAvailable);

            configuration.configure(options);

            AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
                options, context, buildInfoProvider, loadClass, activityFramesTracker);

            deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
          },

前面的几个isClassAvailable()方法就是检查是否能找到那几个类,正常情况下返回trueBuildInfoProvider是为了读取android.os.Build下的信息,包括判断是否为模拟器:

6ccb64dd5d9a1cbd8d5ed42c18962845.jpeg

ActivityFramesTracker是利用FrameMetricsAggregator来收集app帧渲染的时间从而观察是否有掉帧的情况发生。我们继续看AndroidOptionsInitializer.loadDefaultAndMetadataOptions()的实现:

//AndroidOptionsInitializer.java
  static void loadDefaultAndMetadataOptions(
      final @NotNull SentryAndroidOptions options,
      @NotNull Context context,
      final @NotNull ILogger logger,
      final @NotNull BuildInfoProvider buildInfoProvider) {
    Objects.requireNonNull(context, "The context is required.");

    // it returns null if ContextImpl, so let's check for nullability
    if (context.getApplicationContext() != null) {
      context = context.getApplicationContext();
    }

    Objects.requireNonNull(options, "The options object is required.");
    Objects.requireNonNull(logger, "The ILogger object is required.");

    // Firstly set the logger, if `debug=true` configured, logging can start asap.
    options.setLogger(logger);

    options.setDateProvider(new SentryAndroidDateProvider());

    ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
    initializeCacheDirs(context, options);

    readDefaultOptionValues(options, context, buildInfoProvider);
  }

还是继续为SentryAndroidOptions做基础的配置。包括设置时间日期,读取Manifest里的配置信息,初始化cache目录和Android独有信息,如包名等。接着调用AndroidOptionsInitializer.installDefaultIntegrations()方法:

//AndroidOptionsInitializer.java
  static void installDefaultIntegrations(
      final @NotNull Context context,
      final @NotNull SentryAndroidOptions options,
      final @NotNull BuildInfoProvider buildInfoProvider,
      final @NotNull LoadClass loadClass,
      final @NotNull ActivityFramesTracker activityFramesTracker,
      final boolean isFragmentAvailable,
      final boolean isTimberAvailable) {

    // Integration MUST NOT cache option values in ctor, as they will be configured later by the
    // user

    // read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope
    // integrations below
    LazyEvaluator<Boolean> startupCrashMarkerEvaluator =
        new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options));

    options.addIntegration(
        new SendCachedEnvelopeIntegration(
            new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
            startupCrashMarkerEvaluator));

    // Integrations are registered in the same order. NDK before adding Watch outbox,
    // because sentry-native move files around and we don't want to watch that.
    final Class<?> sentryNdkClass =
        isNdkAvailable(buildInfoProvider)
            ? loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger())
            : null;
    options.addIntegration(new NdkIntegration(sentryNdkClass));

    // this integration uses android.os.FileObserver, we can't move to sentry
    // before creating a pure java impl.
    options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver());

    // Send cached envelopes from outbox path
    // this should be executed after NdkIntegration because sentry-native move files on init.
    // and we'd like to send them right away
    options.addIntegration(
        new SendCachedEnvelopeIntegration(
            new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
            startupCrashMarkerEvaluator));

    // AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
    // relies on AppState set by it
    options.addIntegration(new AppLifecycleIntegration());
    options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider));

    // registerActivityLifecycleCallbacks is only available if Context is an AppContext
    if (context instanceof Application) {
      options.addIntegration(
          new ActivityLifecycleIntegration(
              (Application) context, buildInfoProvider, activityFramesTracker));
      options.addIntegration(new CurrentActivityIntegration((Application) context));
      options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
      if (isFragmentAvailable) {
        options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
      }
    } else {
      options
          .getLogger()
          .log(
              SentryLevel.WARNING,
              "ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed.");
    }

    if (isTimberAvailable) {
      options.addIntegration(new SentryTimberIntegration());
    }
    options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
    options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
    options.addIntegration(
        new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger()));
    options.addIntegration(new TempSensorBreadcrumbsIntegration(context));
    options.addIntegration(new PhoneStateBreadcrumbsIntegration(context));
  }

我们发现为options添加了一堆Integration。之前我们知道已经添加了一个UncaughtExceptionHandlerIntegration用来捕获Java异常,我们再一个个看看,弄清楚Sentry都给Android带来了哪些额外的能力。

(1)SendCachedEnvelopeIntegration:SendCachedEnvelopeIntegration有两处。一个是SendFireAndForgetEnvelopeSender,这个我们之前提到过,在App启动的时候将cache中的event发送出去。另一个是SendFireAndForgetOutboxSender,还在发件箱里未被发送的event

(2)NdkIntegration:顾名思义,就是抓取NDK的异常。其中sentryNdkClass去的是上面定义的SENTRY_NDK_CLASS_NAME这个类,即io.sentry.android.ndk.SentryNdk。SentryNdk有个init()方法:

//SentryNdk.java
  public static void init(@NotNull final SentryAndroidOptions options) {
    SentryNdkUtil.addPackage(options.getSdkVersion());
    initSentryNative(options);

    // only add scope sync observer if the scope sync is enabled.
    if (options.isEnableScopeSync()) {
      options.addScopeObserver(new NdkScopeObserver(options));
    }

    options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader()));
  }

将options传入。initSentryNative()是个native方法,用来做初始化。接着为options添加IScopeObserver用来为当前Scope设置参数。这个init()方法是在NdkIntegration的register()中执行的:

//NdkIntegration.java
  @Override
  public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
    Objects.requireNonNull(hub, "Hub is required");
    this.options =
        Objects.requireNonNull(
            (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
            "SentryAndroidOptions is required");

    final boolean enabled = this.options.isEnableNdk();
    this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration enabled: %s", enabled);

    // Note: `hub` isn't used here because the NDK integration writes files to disk which are picked
    // up by another integration (EnvelopeFileObserverIntegration).
    if (enabled && sentryNdkClass != null) {
//...

      try {
        final Method method = sentryNdkClass.getMethod("init", SentryAndroidOptions.class);
        final Object[] args = new Object[1];
        args[0] = this.options;
        method.invoke(null, args);
//...
        addIntegrationToSdkVersion();
      } catch (NoSuchMethodException e) {
//...
      } catch (Throwable e) {
//...
      }
    } else {
      disableNdkIntegration(this.options);
    }
  }

我们可以看到,通过反射的方式,将options传给Sentryndk的init()方法。

(3)EnvelopeFileObserverIntegration:在发送Event到Sentry后台之前,会先把它保存到本地。这个Integration时用来监听文件读写完毕后,进行网络请求,具体流程就不进行分析了。

(4)AppLifecycleIntegration监听App前后台切换,并添加BreadCrumb给Sentry,register()的主要实现如下:

//AppLifecycleIntegration.java
  @Override
  public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
    Objects.requireNonNull(hub, "Hub is required");
//...

    if (this.options.isEnableAutoSessionTracking()
        || this.options.isEnableAppLifecycleBreadcrumbs()) {
      try {
        Class.forName("androidx.lifecycle.DefaultLifecycleObserver");
        Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
        if (AndroidMainThreadChecker.getInstance().isMainThread()) {
          addObserver(hub);
        } else {
//...
        }
      } catch (ClassNotFoundException e) {
//...
      } catch (IllegalStateException e) {
//...
      }
    }
  }
//AppLifecycleIntegration.java
  private void addObserver(final @NotNull IHub hub) {
//...
    watcher =
        new LifecycleWatcher(
            hub,
            this.options.getSessionTrackingIntervalMillis(),
            this.options.isEnableAutoSessionTracking(),
            this.options.isEnableAppLifecycleBreadcrumbs());

    try {
      ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher);
//...
      addIntegrationToSdkVersion();
    } catch (Throwable e) {
//...
    }
  }
//LifecycleWatcher.java
  @Override
  public void onStart(final @NotNull LifecycleOwner owner) {
    startSession();
    addAppBreadcrumb("foreground");

    // Consider using owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED);
    // in the future.
    AppState.getInstance().setInBackground(false);
  }

  @Override
  public void onStop(final @NotNull LifecycleOwner owner) {
    if (enableSessionTracking) {
      final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis();
      this.lastUpdatedSession.set(currentTimeMillis);

      scheduleEndSession();
    }

    AppState.getInstance().setInBackground(true);
    addAppBreadcrumb("background");
  }

(5)AnrIntegrationFactory.create():ANR在Android 11之前和之后的监测方式不同:

//AnrIntegrationFactory.java
public final class AnrIntegrationFactory {

  @NotNull
  public static Integration create(
      final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
    if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) {
      return new AnrV2Integration(context);
    } else {
      return new AnrIntegration(context);
    }
  }
}

先看Android 11及之后的处理方式:

//AnrV2Integration.java
  @SuppressLint("NewApi") // we do the check in the AnrIntegrationFactory
  @Override
  public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
//...

    if (this.options.isAnrEnabled()) {
      try {
        options
            .getExecutorService()
            .submit(new AnrProcessor(context, hub, this.options, dateProvider));
      } catch (Throwable e) {
       //...
      }
      options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed.");
      addIntegrationToSdkVersion();
    }
  }

创建了一个AnrProcessor,实现了Runnable接口:

//AnrProcessor.java
    @SuppressLint("NewApi") // we check this in AnrIntegrationFactory
    @Override
    public void run() {
      final ActivityManager activityManager =
          (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

      final List<ApplicationExitInfo> applicationExitInfoList =
          activityManager.getHistoricalProcessExitReasons(null, 0, 0);
//...
      final IEnvelopeCache cache = options.getEnvelopeDiskCache();
      if (cache instanceof EnvelopeCache) {
        if (options.isEnableAutoSessionTracking()
            && !((EnvelopeCache) cache).waitPreviousSessionFlush()) {
//...
          ((EnvelopeCache) cache).flushPreviousSession();
        }
      }

      // making a deep copy as we're modifying the list
      final List<ApplicationExitInfo> exitInfos = new ArrayList<>(applicationExitInfoList);
      final @Nullable Long lastReportedAnrTimestamp = AndroidEnvelopeCache.lastReportedAnr(options);

      ApplicationExitInfo latestAnr = null;
      for (ApplicationExitInfo applicationExitInfo : exitInfos) {
        if (applicationExitInfo.getReason() == ApplicationExitInfo.REASON_ANR) {
          latestAnr = applicationExitInfo;
          // remove it, so it's not reported twice
          exitInfos.remove(applicationExitInfo);
          break;
        }
      }
//...
      if (options.isReportHistoricalAnrs()) {
        reportNonEnrichedHistoricalAnrs(exitInfos, lastReportedAnrTimestamp);
      }

      reportAsSentryEvent(latestAnr, true);
    }

不去深究代码的细节,只看方案,我们可以看到Android 11及以上是通过ActivityManager.getHistoricalProcessExitReasons()来得到Anr的相关信息,最终通过reportAsSentryEvent()来进行上报:

private void reportAsSentryEvent(
        final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) {
      final long anrTimestamp = exitInfo.getTimestamp();
      final boolean isBackground =
          exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;

      final ParseResult result = parseThreadDump(exitInfo, isBackground);
//...
      final AnrV2Hint anrHint =
          new AnrV2Hint(
              options.getFlushTimeoutMillis(),
              options.getLogger(),
              anrTimestamp,
              shouldEnrich,
              isBackground);

      final Hint hint = HintUtils.createWithTypeCheckHint(anrHint);

      final SentryEvent event = new SentryEvent();
      if (result.type == ParseResult.Type.ERROR) {
        final Message sentryMessage = new Message();
        sentryMessage.setFormatted(
            "Sentry Android SDK failed to parse system thread dump for "
                + "this ANR. We recommend enabling [SentryOptions.isAttachAnrThreadDump] option "
                + "to attach the thread dump as plain text and report this issue on GitHub.");
        event.setMessage(sentryMessage);
      } else if (result.type == ParseResult.Type.DUMP) {
        event.setThreads(result.threads);
      }
      event.setLevel(SentryLevel.FATAL);
      event.setTimestamp(DateUtils.getDateTime(anrTimestamp));

      if (options.isAttachAnrThreadDump()) {
        if (result.dump != null) {
          hint.setThreadDump(Attachment.fromThreadDump(result.dump));
        }
      }

      final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
      final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
//...
    }

这段代码我们先不去看具体的实现,它的作用就是将Anr信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。我们再来看看Android 11以下是如何处理的:

//AnrIntegrationFactory.java
  public static Integration create(
      final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
    if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) {
      return new AnrV2Integration(context);
    } else {
      return new AnrIntegration(context);
    }
  }
}
//AnrIntegration.java
  private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
//...
    if (options.isAnrEnabled()) {
      synchronized (watchDogLock) {
        if (anrWatchDog == null) {
//...

          anrWatchDog =
              new ANRWatchDog(
                  options.getAnrTimeoutIntervalMillis(),
                  options.isAnrReportInDebug(),
                  error -> reportANR(hub, options, error),
                  options.getLogger(),
                  context);
          anrWatchDog.start();

          options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration installed.");
          addIntegrationToSdkVersion();
        }
      }
    }
  }

封装了一个ANRWatchDog继承了Thread,这个方案就是传统的Anr监测方案:启动一个异步线程,在while循环中,使用主线程的Handler发送一个消息,线程休眠指定的时间5s,当线程唤醒之后,如果发送的消息还没被主线程执行,即认为主线程发生了卡顿。具体流程不再描述了,最终也是将Anr信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。

(6)ActivityLifecycleIntegration:实现了Application.ActivityLifecycleCallbacks用来监测Activity生命周期。ActivityLifecycleIntegration主要干了三件事:

1.计算冷启动时间;

2.用来将这Activity的生命周期变化及信息添加到BreadCrumb中去;

3.计算Activity的启动时间。

先看一下register()方法的实现:

//ActivityLifecycleIntegration.java
  @Override
  public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
    this.options =
        Objects.requireNonNull(
            (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
            "SentryAndroidOptions is required");

    this.hub = Objects.requireNonNull(hub, "Hub is required");
//...

    performanceEnabled = isPerformanceEnabled(this.options);
    fullyDisplayedReporter = this.options.getFullyDisplayedReporter();
    timeToFullDisplaySpanEnabled = this.options.isEnableTimeToFullDisplayTracing();

    application.registerActivityLifecycleCallbacks(this);
    this.options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration installed.");
    addIntegrationToSdkVersion();
  }

通过application.registerActivityLifecycleCallbacks(this)注册生命周期的监听,当执行了onActivityCreated():

//ActivityLifecycleIntegration.java
  @Override
  public synchronized void onActivityCreated(
      final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
    setColdStart(savedInstanceState);
    addBreadcrumb(activity, "created");
    startTracing(activity);
    final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);

    firstActivityCreated = true;

    if (fullyDisplayedReporter != null) {
      fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan));
    }
  }

我们看首先setColdStart()设置了是否为冷启动的标志位,addBreadcrumb()设置面包屑,startTracing()开始追踪这个Activity。具体代码实现不在此展开。除了加面包屑这件事,主要就是为了区分是否为冷启动,为了之后统计冷启动速度和页面加载速度作区分。而这两个都是以onActivityCreated()作为起始点(冷启动其实是以SentryPerformanceProvider作为起点的,但如果SentryPerformanceProvider被disable了,那就以第一个Activity走到onCreate()作为起点,在onActivityResumed()作为统计的终点:

//ActivityLifecycleIntegration.java
  public synchronized void onActivityResumed(final @NotNull Activity activity) {
    if (performanceEnabled) {
      // app start span
      @Nullable final SentryDate appStartStartTime = AppStartState.getInstance().getAppStartTime();
      @Nullable final SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
      if (appStartStartTime != null && appStartEndTime == null) {
        AppStartState.getInstance().setAppStartEnd();
      }
      finishAppStartSpan();

      final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity);
      final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
      final View rootView = activity.findViewById(android.R.id.content);
      if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN
          && rootView != null) {
        FirstDrawDoneListener.registerForNextDraw(
            rootView, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider);
      } else {
        mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan));
      }
    }
    addBreadcrumb(activity, "resumed");
  }

其中finishAppStartSpan()方法最终会构造一个SentryTransaction,通过captureTransaction()将启动信息上报到Sentry后台。

(7)CurrentActivityIntegration:这个Integration也实现了Application.ActivityLifecycleCallbacks接口,目的是为CurrentActivityHolder添加目前活跃Activity的引用的。

(8)UserInteractionIntegration:为了记录用户的交互信息,依然是实现了Application.ActivityLifecycleCallbacks接口,仅对onActivityResumed()和onActivityPaused()做了实现:

//UserInteractionIntegration.java
  @Override
  public void onActivityResumed(@NotNull Activity activity) {
    startTracking(activity);
  }

  @Override
  public void onActivityPaused(@NotNull Activity activity) {
    stopTracking(activity);
  }
//UserInteractionIntegration.java
  private void startTracking(final @NotNull Activity activity) {
    final Window window = activity.getWindow();
    if (window == null) {
//...
      return;
    }

    if (hub != null && options != null) {
      Window.Callback delegate = window.getCallback();
      if (delegate == null) {
        delegate = new NoOpWindowCallback();
      }

      final SentryGestureListener gestureListener =
          new SentryGestureListener(activity, hub, options);
      window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options));
    }
  }

在startTracking()方法中构建了SentryGestureListener和SentryWindowCallback,用来监听手势事件,从而记录用户行为。

(9)FragmentLifecycleIntegration:不知为什么看不到源码,但从命名来看就是监听Fragment生命周期的。

(10)SentryTimberIntegration:也看不到源码。

(11)AppComponentsBreadcrumbsIntegration:实现了ComponentCallbacks2接口监听内存不足的情况。

(12)SystemEventsBreadcrumbsIntegration:构造了一个SystemEventsBroadcastReceiver,监听了一系列系统相关的事件:

private static @NotNull List<String> getDefaultActions() {
    final List<String> actions = new ArrayList<>();
    actions.add(ACTION_APPWIDGET_DELETED);
    actions.add(ACTION_APPWIDGET_DISABLED);
    actions.add(ACTION_APPWIDGET_ENABLED);
    actions.add("android.appwidget.action.APPWIDGET_HOST_RESTORED");
    actions.add("android.appwidget.action.APPWIDGET_RESTORED");
    actions.add(ACTION_APPWIDGET_UPDATE);
    actions.add("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS");
    actions.add(ACTION_POWER_CONNECTED);
    actions.add(ACTION_POWER_DISCONNECTED);
    actions.add(ACTION_SHUTDOWN);
    actions.add(ACTION_AIRPLANE_MODE_CHANGED);
    actions.add(ACTION_BATTERY_LOW);
    actions.add(ACTION_BATTERY_OKAY);
    actions.add(ACTION_BOOT_COMPLETED);
    actions.add(ACTION_CAMERA_BUTTON);
    actions.add(ACTION_CONFIGURATION_CHANGED);
    actions.add("android.intent.action.CONTENT_CHANGED");
    actions.add(ACTION_DATE_CHANGED);
    actions.add(ACTION_DEVICE_STORAGE_LOW);
    actions.add(ACTION_DEVICE_STORAGE_OK);
    actions.add(ACTION_DOCK_EVENT);
    actions.add("android.intent.action.DREAMING_STARTED");
    actions.add("android.intent.action.DREAMING_STOPPED");
    actions.add(ACTION_INPUT_METHOD_CHANGED);
    actions.add(ACTION_LOCALE_CHANGED);
    actions.add(ACTION_REBOOT);
    actions.add(ACTION_SCREEN_OFF);
    actions.add(ACTION_SCREEN_ON);
    actions.add(ACTION_TIMEZONE_CHANGED);
    actions.add(ACTION_TIME_CHANGED);
    actions.add("android.os.action.DEVICE_IDLE_MODE_CHANGED");
    actions.add("android.os.action.POWER_SAVE_MODE_CHANGED");
    // The user pressed the "Report" button in the crash/ANR dialog.
    actions.add(ACTION_APP_ERROR);
    // Show activity for reporting a bug.
    actions.add(ACTION_BUG_REPORT);

    // consider if somebody mounted or ejected a sdcard
    actions.add(ACTION_MEDIA_BAD_REMOVAL);
    actions.add(ACTION_MEDIA_MOUNTED);
    actions.add(ACTION_MEDIA_UNMOUNTABLE);
    actions.add(ACTION_MEDIA_UNMOUNTED);

    return actions;
  }

收到相应的action将被添加至BreadCrumb。(13)NetworkBreadcrumbsIntegration:通过ConnectivityManager监听网络状态的变化并添加至BreadCrumb。

(14)TempSensorBreadcrumbsIntegration:实现了SensorEventListener接口,监听Sensor的状态变化。

(15)PhoneStateBreadcrumbsIntegration:通过TelephonyManager监听TelephonyManager.CALL_STATE_RINGING状态。好了, AndroidOptionsInitializer.installDefaultIntegrations()方法分析完了。这个方法就是为Android添加的Integration。

到此为止,这些Integration便是Sentry为Android添加的能力。我们再回到SentryAndroid.java继续来看Sentry.init()方法中applyOptionsConfiguration()针对options还干了什么,主要还剩最后一个方法:AndroidOptionsInitializer.initializeIntegrationsAndProcessors():

static void initializeIntegrationsAndProcessors(
      final @NotNull SentryAndroidOptions options,
      final @NotNull Context context,
      final @NotNull BuildInfoProvider buildInfoProvider,
      final @NotNull LoadClass loadClass,
      final @NotNull ActivityFramesTracker activityFramesTracker) {

    if (options.getCacheDirPath() != null
        && options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) {
      options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
    }

    options.addEventProcessor(new DeduplicateMultithreadedEventProcessor(options));
    options.addEventProcessor(
        new DefaultAndroidEventProcessor(context, buildInfoProvider, options));
    options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));
    options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider));
    options.addEventProcessor(new ViewHierarchyEventProcessor(options));
    options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
    options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
    final SentryFrameMetricsCollector frameMetricsCollector =
        new SentryFrameMetricsCollector(context, options, buildInfoProvider);
    options.setTransactionProfiler(
        new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));
    options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
    options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));

    final boolean isAndroidXScrollViewAvailable =
        loadClass.isClassAvailable("androidx.core.view.ScrollingView", options);
    final boolean isComposeUpstreamAvailable =
        loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options);

    if (options.getGestureTargetLocators().isEmpty()) {
      final List<GestureTargetLocator> gestureTargetLocators = new ArrayList<>(2);
      gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable));

      final boolean isComposeAvailable =
          (isComposeUpstreamAvailable
              && loadClass.isClassAvailable(
                  SENTRY_COMPOSE_GESTURE_INTEGRATION_CLASS_NAME, options));

      if (isComposeAvailable) {
        gestureTargetLocators.add(new ComposeGestureTargetLocator(options.getLogger()));
      }
      options.setGestureTargetLocators(gestureTargetLocators);
    }

    if (options.getViewHierarchyExporters().isEmpty()
        && isComposeUpstreamAvailable
        && loadClass.isClassAvailable(
            SENTRY_COMPOSE_VIEW_HIERARCHY_INTEGRATION_CLASS_NAME, options)) {

      final List<ViewHierarchyExporter> viewHierarchyExporters = new ArrayList<>(1);
      viewHierarchyExporters.add(new ComposeViewHierarchyExporter(options.getLogger()));
      options.setViewHierarchyExporters(viewHierarchyExporters);
    }

    options.setMainThreadChecker(AndroidMainThreadChecker.getInstance());
    if (options.getCollectors().isEmpty()) {
      options.addCollector(new AndroidMemoryCollector());
      options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
    }
    options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));

    if (options.getCacheDirPath() != null) {
      options.addScopeObserver(new PersistingScopeObserver(options));
      options.addOptionsObserver(new PersistingOptionsObserver(options));
    }
  }

首先我们可以看到,添加了很多EventProcessor,在之前的章节我们介绍过,EventProcessor的目的是在发送事件时插入一些附属信息的,其中process()方法是具体的实现。我们选取ScreenshotEventProcessor看看都实现了什么,其他EventProcessor就不介绍了:

//ScreenshotEventProcessor.java
  @Override
  public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
//...
    final byte[] screenshot =
        takeScreenshot(
            activity, options.getMainThreadChecker(), options.getLogger(), buildInfoProvider);
    if (screenshot == null) {
      return event;
    }

    hint.setScreenshot(Attachment.fromScreenshot(screenshot));
    hint.set(ANDROID_ACTIVITY, activity);
    return event;
  }

主要实现是如果开启了ScreenShot模式,发送事件之前会截图并添加至hint中,待发送事件时将截图作为附件上传至服务端。除了EventProcessor,还有其他几项配置会添加到options里,我们来看看几个重要的配置:

(1)AndroidTransportGate用来判断设备的网络是否是connected的状态,如果是的话发送事件到Sentry后台,否则存在cache中。

(2)AndroidTransactionProfiler用来管理Transaction的,实现了ITransactionProfiler接口,重写了onTransactionStart()和onTransactionFinish()方法。设置了buffer,如果在跟踪的过程中到了buffer的阈值,那么新的记录将会被丢弃。默认每个traces会跟踪30s,总共给30MB的buffer。大概能记录3次的Transaction。其中onTransactionStart()设置了traceFilesDir。而onTransactionFinish()主要构件了一个ProfilingTraceData除了记录了常规的设备相关信息外,还有三个重要的参数:File traceFile,List<ProfilingTransactionData> transactions和measurementsMap。traceFile是在onTransactionStart()时调用系统的VMDebug.startMethodTracing()生成的trace文件。transactions记录了每一个Transaction相关的基础信息,其数据结构如下:

//ProfilingTransactionData.java
  private @NotNull String id; // transaction event id (the current transaction_id)
  private @NotNull String traceId; // trace id (the current trace_id)
  private @NotNull String name; // transaction name
  private @NotNull Long relativeStartNs; // timestamp in nanoseconds this transaction started
  private @Nullable Long relativeEndNs; // timestamp in nanoseconds this transaction ended
  private @NotNull Long relativeStartCpuMs; // cpu time in milliseconds this transaction started
  private @Nullable Long relativeEndCpuMs; // cpu time in milliseconds this transaction ended

measurementsMap包含了screenFrameRateMeasurements,slowFrameRenderMeasurements和frozenFrameRenderMeasurements三个信息。

(3)AndroidMemoryCollector,AndroidCpuCollector和DefaultTransactionPerformanceCollector:AndroidMemoryCollector和AndroidCpuCollector实现了ICollector接口,重写了collect()方法用来记录一个Transaction里的内存和cpu使用情况。并交由DefaultTransactionPerformanceCollector进行处理。DefaultTransactionPerformanceCollector每100ms进行一次collect()操作总共持续30s,收集各个Collector的信息,在发送事件时一并发送到Sentry后来。

(4)PersistingScopeObserver:持久的Scope observer参数的实现。

(5)PersistingOptionsObserver:持久的SentryOptions observer的实现。到此为止,Sentry.init()中的applyOptionsConfiguration()方法的解析终于完成了。总结一下:applyOptionsConfiguration()最重要的事情就是初始化了开发者赋予Sentry的能力Intergration,和每个事件所需要的额外硬件/软件/环境等相关基础信息EventProcessor,及不同Intergration对应的信息和其他随着事件一起发送的额外数据。另外,进行了一些cache的初始化,做了一些硬件相关的检测等。总而言之,就是在我们真正产生事件之前做好一切基础准备,在上报各种事件时,相关的信息都会最终被封装在SentryOptions中。

我们再回到Sentry.init():

//Sentry.java
  public static <T extends SentryOptions> void init(
      final @NotNull OptionsContainer<T> clazz,
      final @NotNull OptionsConfiguration<T> optionsConfiguration,
      final boolean globalHubMode)
      throws IllegalAccessException, InstantiationException, NoSuchMethodException,
          InvocationTargetException {
    final T options = clazz.createInstance();
    applyOptionsConfiguration(optionsConfiguration, options);
    init(options, globalHubMode);
  }

还剩最后一行代码:init(options, globalHubMode)

3.2.3.init(options, globalHubMode)

通过上一章节的分析,我们知道Sentry会先初始化好各种所需的能力,以及随事件需要上报的各种参数,并最终构建一个SentryOptions对象。init()这个方法就是根据我们上一步构建的SentryOptions真正的去初始化SDK

//Sentry.java
  private static synchronized void init(
      final @NotNull SentryOptions options, final boolean globalHubMode) {
//...
    if (!initConfigurations(options)) {
      return;
    }
    Sentry.globalHubMode = globalHubMode;

    final IHub hub = getCurrentHub();
    mainHub = new Hub(options);

    currentHub.set(mainHub);

    hub.close();

    final ISentryExecutorService sentryExecutorService = options.getExecutorService();
    // If the passed executor service was previously called we set a new one
    if (sentryExecutorService.isClosed()) {
      options.setExecutorService(new SentryExecutorService());
    }

    for (final Integration integration : options.getIntegrations()) {
      integration.register(HubAdapter.getInstance(), options);
    }

    notifyOptionsObservers(options);

    finalizePreviousSession(options, HubAdapter.getInstance());
  }

首先initConfigurations()读取了Sentry的配置信息,包括DNSHost信息等,然后创建了一个Hub对象,用来管理ScopeSentryClient

//Hub.java
  public Hub(final @NotNull SentryOptions options) {
    this(options, createRootStackItem(options));

    // Integrations are no longer registered on Hub ctor, but on Sentry.init
  }

  private static StackItem createRootStackItem(final @NotNull SentryOptions options) {
    validateOptions(options);
    final Scope scope = new Scope(options);
    final ISentryClient client = new SentryClient(options);
    return new StackItem(options, client, scope);
  }

返回一个StackItem对象。

//Hub.java
  private Hub(final @NotNull SentryOptions options, final @NotNull StackItem rootStackItem) {
    this(options, new Stack(options.getLogger(), rootStackItem));
  }

  private Hub(final @NotNull SentryOptions options, final @NotNull Stack stack) {
    validateOptions(options);

    this.options = options;
    this.tracesSampler = new TracesSampler(options);
    this.stack = stack;
    this.lastEventId = SentryId.EMPTY_ID;
    this.transactionPerformanceCollector = options.getTransactionPerformanceCollector();

    // Integrations will use this Hub instance once registered.
    // Make sure Hub ready to be used then.
    this.isEnabled = true;
  }

创建了Stack对象。接着如果SentryExecutorService是关闭的状态,那么创建一个SentryExecutorService对象并交由options。然后就是执行上一章节我们分析过的各种Integration的注册方法,这样Sentry就真正拥有了各种能力。notifyOptionsObservers()方法是为了获得我们上一章节讲的为PersistingOptionsObserver进行赋值。好了到此为止SentryAndroid.init()方法就分析完了。现在Sentry已经都准备好了,等待着我们发送各种事件了。接下来帮大家梳理一下事件发送的流程。 

04

事件发送

在文章的最初介绍概念时,我们知道Sentry的事件分为SentryEventSentryTransaction。它们俩其实继承自同一个父类:SentryBaseEvent。我们最常用到的崩溃日志的上报是在UncaughtExceptionHandlerIntegration中调用了hub.captureEvent(event, hint)Sentry还为我们封装了个captureException(final @NotNull Throwable throwable)的方法方便我们直接上报Throwable。而captureException()方法最终也是调到captureEvent()里。实际上只要构建了一个SentryEvent,最终都会调用到captureEvent()里。我们以UncaughtExceptionHandlerIntegration中的处理为例,看一下captureEvent()的流程。

4.1.IHub.captureEvent()

UncaughtExceptionHandlerIntegration里,由于实现了UncaughtExceptionHandler接口,当有exception出现时,会回调至uncaughtException()方法中进行处理:

//UncaughtExceptionHandlerIntegration.java
  @Override
  public void uncaughtException(Thread thread, Throwable thrown) {
    if (options != null && hub != null) {
      options.getLogger().log(SentryLevel.INFO, "Uncaught exception received.");

      try {
        final UncaughtExceptionHint exceptionHint =
            new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger());
        final Throwable throwable = getUnhandledThrowable(thread, thrown);
        final SentryEvent event = new SentryEvent(throwable);
        event.setLevel(SentryLevel.FATAL);

        final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint);

        final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
        final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
        final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint);
//...
      } catch (Throwable e) {
//...
      }

      if (defaultExceptionHandler != null) {
//...
        defaultExceptionHandler.uncaughtException(thread, thrown);
      } else {
        if (options.isPrintUncaughtStackTrace()) {
          thrown.printStackTrace();
        }
      }
    }
  }

先看SentryEvent的创建,将Throwable作为参数构造SentryEvent

//SentryEvent.java
  public SentryEvent(final @Nullable Throwable throwable) {
    this();
    this.throwable = throwable;
  }

  public SentryEvent() {
    this(new SentryId(), DateUtils.getCurrentDateTime());
  }

  SentryEvent(final @NotNull SentryId eventId, final @NotNull Date timestamp) {
    super(eventId);
    this.timestamp = timestamp;
  }
//SentryId.java
  public SentryId() {
    this((UUID) null);
  }

  public SentryId(@Nullable UUID uuid) {
    if (uuid == null) {
      uuid = UUID.randomUUID();
    }
    this.uuid = uuid;
  }

创建了一个uuid作为SentryId,记录上报的日期和Throwable。通过event.setLevel(SentryLevel.FATAL)将事件等级设置为FATAL。然后创建hint调用hub.captureEvent(event, hint)

//IHub.java
  @NotNull
  SentryId captureEvent(@NotNull SentryEvent event, @Nullable Hint hint);
//Hub.java
  @Override
  public @NotNull SentryId captureEvent(
      final @NotNull SentryEvent event, final @Nullable Hint hint) {
    return captureEventInternal(event, hint, null);
  }
//Hub.java
  private @NotNull SentryId captureEventInternal(
      final @NotNull SentryEvent event,
      final @Nullable Hint hint,
      final @Nullable ScopeCallback scopeCallback) {
    SentryId sentryId = SentryId.EMPTY_ID;
    if (!isEnabled()) {
//...
    } else if (event == null) {
//...
    } else {
      try {
        assignTraceContext(event);
        final StackItem item = stack.peek();

        final Scope scope = buildLocalScope(item.getScope(), scopeCallback);

        sentryId = item.getClient().captureEvent(event, scope, hint);
        this.lastEventId = sentryId;
      } catch (Throwable e) {
//...
      }
    }
    return sentryId;
  }

最终调到SentryClient中的captureEvent()方法,在看captureEvent()的实现之前,我们先来看一下SentryClient的构造方法:

//SentryClient.java
  SentryClient(final @NotNull SentryOptions options) {
    this.options = Objects.requireNonNull(options, "SentryOptions is required.");
    this.enabled = true;

    ITransportFactory transportFactory = options.getTransportFactory();
    if (transportFactory instanceof NoOpTransportFactory) {
      transportFactory = new AsyncHttpTransportFactory();
      options.setTransportFactory(transportFactory);
    }

    final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options);
    transport = transportFactory.create(options, requestDetailsResolver.resolve());

    this.random = options.getSampleRate() == null ? null : new SecureRandom();
  }

最重要的就是初始化了网络的部分,构造了一个AsyncHttpTransportFactory,创建了AsyncHttpTransport对象并赋值给transport,而AsyncHttpTransport负责缓存事件到本地和发送事件到服务端。接下来我们来看captureEvent()的实现:

//SentryClient.java
  @Override
  public @NotNull SentryId captureEvent(
      @NotNull SentryEvent event, final @Nullable Scope scope, @Nullable Hint hint) {
//...

    event = processEvent(event, hint, options.getEventProcessors());

    if (event != null) {
      event = executeBeforeSend(event, hint);
//...
    }
//...

    @Nullable
    Session sessionBeforeUpdate =
        scope != null ? scope.withSession((@Nullable Session session) -> {}) : null;
    @Nullable Session session = null;

    if (event != null) {
      // https://develop.sentry.dev/sdk/sessions/#terminal-session-states
      if (sessionBeforeUpdate == null || !sessionBeforeUpdate.isTerminated()) {
        session = updateSessionData(event, hint, scope);
      }
//...
    }

    final boolean shouldSendSessionUpdate =
        shouldSendSessionUpdateForDroppedEvent(sessionBeforeUpdate, session);
//...

    SentryId sentryId = SentryId.EMPTY_ID;
    if (event != null && event.getEventId() != null) {
      sentryId = event.getEventId();
    }

    try {
      @Nullable TraceContext traceContext = null;
      if (HintUtils.hasType(hint, Backfillable.class)) {
        // for backfillable hint we synthesize Baggage from event values
        if (event != null) {
          final Baggage baggage = Baggage.fromEvent(event, options);
          traceContext = baggage.toTraceContext();
        }
      } else if (scope != null) {
        final @Nullable ITransaction transaction = scope.getTransaction();
        if (transaction != null) {
          traceContext = transaction.traceContext();
        } else {
          final @NotNull PropagationContext propagationContext =
              TracingUtils.maybeUpdateBaggage(scope, options);
          traceContext = propagationContext.traceContext();
        }
      }

      final boolean shouldSendAttachments = event != null;
      List<Attachment> attachments = shouldSendAttachments ? getAttachments(hint) : null;
      final SentryEnvelope envelope =
          buildEnvelope(event, attachments, session, traceContext, null);

      hint.clear();
      if (envelope != null) {
        transport.send(envelope, hint);
      }
    } catch (IOException | SentryEnvelopeException e) {
//...
    }
//...

    return sentryId;
  }

这段代码非常长,我们截取了核心部分。首先调用了processEvent()方法:

//SentryClient.java  
  @Nullable
  private SentryEvent processEvent(
      @NotNull SentryEvent event,
      final @NotNull Hint hint,
      final @NotNull List<EventProcessor> eventProcessors) {
    for (final EventProcessor processor : eventProcessors) {
      try {
        // only wire backfillable events through the backfilling processors, skip from others, and
        // the other way around
        final boolean isBackfillingProcessor = processor instanceof BackfillingEventProcessor;
        final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class);
        if (isBackfillable && isBackfillingProcessor) {
          event = processor.process(event, hint);
        } else if (!isBackfillable && !isBackfillingProcessor) {
          event = processor.process(event, hint);
        }
      } catch (Throwable e) {
//...
      }
//...
    }
    return event;
  }

我们可以看到传的参数:options.getEventProcessors()就是在初始化阶段创建的EventProcessor列表,用来在发送事件时添加一些信息的。在processEvent()方法中主要是就是执行了各个EventProcessorprocess()方法去添加额外信息。executeBeforeSend(event, hint)实际上是个callback,用来给用户提供一个发送事件之前的时机进行额外的处理。之后的代码实际上都是为了构建一个SentryEnvelope对象envelope交给transport去处理。envelope会把SentryEventAttachment(比如截图),sessiontraceContext进行封装,最终调用transport.send(envelope, hint)方法。

我们来看transport.send()方法的实现:

//AsyncHttpTransport.java
  @Override
  public void send(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint)
      throws IOException {
    // For now no caching on envelopes
    IEnvelopeCache currentEnvelopeCache = envelopeCache;
    boolean cached = false;
    if (HintUtils.hasType(hint, Cached.class)) {
      currentEnvelopeCache = NoOpEnvelopeCache.getInstance();
      cached = true;
      options.getLogger().log(SentryLevel.DEBUG, "Captured Envelope is already cached");
    }

    final SentryEnvelope filteredEnvelope = rateLimiter.filter(envelope, hint);

    if (filteredEnvelope == null) {
      if (cached) {
        envelopeCache.discard(envelope);
      }
    } else {
      SentryEnvelope envelopeThatMayIncludeClientReport;
      if (HintUtils.hasType(
          hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) {
        envelopeThatMayIncludeClientReport =
            options.getClientReportRecorder().attachReportToEnvelope(filteredEnvelope);
      } else {
        envelopeThatMayIncludeClientReport = filteredEnvelope;
      }

      final Future<?> future =
          executor.submit(
              new EnvelopeSender(envelopeThatMayIncludeClientReport, hint, currentEnvelopeCache));

      if (future != null && future.isCancelled()) {
        options
            .getClientReportRecorder()
            .recordLostEnvelope(DiscardReason.QUEUE_OVERFLOW, envelopeThatMayIncludeClientReport);
      }
    }
  }

这段代码我们讲一下重点:先初始化currentEnvelopeCache,再将envelope封装成envelopeThatMayIncludeClientReport,最终将envelopeThatMayIncludeClientReporthintcurrentEnvelopeCache封装成EnvelopeSender交给QueuedThreadPoolExecutor处理。其中EnvelopeSender是个Runnable,我们看看其run()方法的实现:

//AsyncHttpTransport.java
    @Override
    public void run() {
      TransportResult result = this.failedResult;
      try {
        result = flush();
        options.getLogger().log(SentryLevel.DEBUG, "Envelope flushed");
      } catch (Throwable e) {
        options.getLogger().log(SentryLevel.ERROR, e, "Envelope submission failed");
        throw e;
      } finally {
        final TransportResult finalResult = result;
        HintUtils.runIfHasType(
            hint,
            SubmissionResult.class,
            (submissionResult) -> {
//...
            });
      }
    }

执行了flush()方法:

//AsyncHttpTransport.java
    private @NotNull TransportResult flush() {
      TransportResult result = this.failedResult;

      envelope.getHeader().setSentAt(null);
      envelopeCache.store(envelope, hint);

      HintUtils.runIfHasType(
          hint,
          DiskFlushNotification.class,
          (diskFlushNotification) -> {
            diskFlushNotification.markFlushed();
            options.getLogger().log(SentryLevel.DEBUG, "Disk flush envelope fired");
          });

      if (transportGate.isConnected()) {
        final SentryEnvelope envelopeWithClientReport =
            options.getClientReportRecorder().attachReportToEnvelope(envelope);
        try {

          @NotNull SentryDate now = options.getDateProvider().now();
          envelopeWithClientReport
              .getHeader()
              .setSentAt(DateUtils.nanosToDate(now.nanoTimestamp()));

          result = connection.send(envelopeWithClientReport);
          if (result.isSuccess()) {
            envelopeCache.discard(envelope);
          } else {
            final String message =
                "The transport failed to send the envelope with response code "
                    + result.getResponseCode();
//...
            if (result.getResponseCode() >= 400 && result.getResponseCode() != 429) {
              HintUtils.runIfDoesNotHaveType(
                  hint,
                  Retryable.class,
                  (hint) -> {
//...
                  });
            }

            throw new IllegalStateException(message);
          }
        } catch (IOException e) {
//...
        }
      } else {
        // If transportGate is blocking from sending, allowed to retry
        HintUtils.runIfHasType(
            hint,
            Retryable.class,
            (retryable) -> {
              retryable.setRetry(true);
            },
            (hint, clazz) -> {
//...
            });
      }
      return result;
    }

我们可以看到,首先就是通过envelopeCache.store(envelope, hint)将这个事件保存在本地。然后通过connection.send(envelopeWithClientReport)将事件发送至服务端,如果事件发送成功的话,再调用envelopeCache.discard(envelope)将保存在本地的事件删除。到此为止一个SentryEvent的发送流程就分析完毕了。 

简单地梳理一下发送的流程:d7c67aec517890784f96a17fe959b3d4.jpeg

下面我们再来分析一下SentryTransaction的发送流程。 

4.2.IHub.captureTransaction()

之前我们在分析ActivityLifecycleIntegration的实现时提到过,在onActivityCreated()作为一个Transaction起始点,在onActivityResumed()时作为这个Transaction统计的终点,通过调用finishAppStartSpan()来进行Transaction的上报。我们先来看看在onActivityCreated()是如何创建一个SentryTransaction的:

//ActivityLifecycleIntegration.java
  @Override
  public synchronized void onActivityCreated(
      final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
    setColdStart(savedInstanceState);
    addBreadcrumb(activity, "created");
    startTracing(activity);
    final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);

    firstActivityCreated = true;

    if (fullyDisplayedReporter != null) {
      fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan));
    }
  }

继续追踪startTracing()方法:

//ActivityLifecycleIntegration.java
  private void startTracing(final @NotNull Activity activity) {
    WeakReference<Activity> weakActivity = new WeakReference<>(activity);
    if (hub != null && !isRunningTransactionOrTrace(activity)) {
      if (!performanceEnabled) {
        activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance());
        TracingUtils.startNewTrace(hub);
      } else if (performanceEnabled) {
        // as we allow a single transaction running on the bound Scope, we finish the previous ones
        stopPreviousTransactions();

        final String activityName = getActivityName(activity);

        final SentryDate appStartTime =
            foregroundImportance ? AppStartState.getInstance().getAppStartTime() : null;
        final Boolean coldStart = AppStartState.getInstance().isColdStart();

        final TransactionOptions transactionOptions = new TransactionOptions();
        if (options.isEnableActivityLifecycleTracingAutoFinish()) {
          transactionOptions.setIdleTimeout(options.getIdleTimeout());
          transactionOptions.setTrimEnd(true);
        }
        transactionOptions.setWaitForChildren(true);
        transactionOptions.setTransactionFinishedCallback(
            (finishingTransaction) -> {
              @Nullable Activity unwrappedActivity = weakActivity.get();
              if (unwrappedActivity != null) {
                activityFramesTracker.setMetrics(
                    unwrappedActivity, finishingTransaction.getEventId());
              } else {
                if (options != null) {
                  options
                      .getLogger()
                      .log(
                          SentryLevel.WARNING,
                          "Unable to track activity frames as the Activity %s has been destroyed.",
                          activityName);
                }
              }
            });

        // This will be the start timestamp of the transaction, as well as the ttid/ttfd spans
        final @NotNull SentryDate ttidStartTime;

        if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
          // The first activity ttid/ttfd spans should start at the app start time
          ttidStartTime = appStartTime;
        } else {
          // The ttid/ttfd spans should start when the previous activity called its onPause method
          ttidStartTime = lastPausedTime;
        }
        transactionOptions.setStartTimestamp(ttidStartTime);

        // we can only bind to the scope if there's no running transaction
        ITransaction transaction =
            hub.startTransaction(
                new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),
                transactionOptions);
        setSpanOrigin(transaction);
//...
      }
    }
  }

截取重点部分,创建一个TransactionOptions对象设置各种参数,再封装一个TransactionContext对象记录activityName,之后调用hub.startTransaction()进行上报:

//Hub.java
  @ApiStatus.Internal
  @Override
  public @NotNull ITransaction startTransaction(
      final @NotNull TransactionContext transactionContext,
      final @NotNull TransactionOptions transactionOptions) {
    return createTransaction(transactionContext, transactionOptions);
  }
//Hub.java
  private @NotNull ITransaction createTransaction(
      final @NotNull TransactionContext transactionContext,
      final @NotNull TransactionOptions transactionOptions) {
    Objects.requireNonNull(transactionContext, "transactionContext is required");

    ITransaction transaction;
    if (!isEnabled()) {
//...
    } else if (!options.getInstrumenter().equals(transactionContext.getInstrumenter())) {
//...
    } else if (!options.isTracingEnabled()) {
//...
    } else {
      final SamplingContext samplingContext =
          new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext());
      @NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext);
      transactionContext.setSamplingDecision(samplingDecision);

      transaction =
          new SentryTracer(
              transactionContext, this, transactionOptions, transactionPerformanceCollector);

      // The listener is called only if the transaction exists, as the transaction is needed to
      // stop it
      if (samplingDecision.getSampled() && samplingDecision.getProfileSampled()) {
        final ITransactionProfiler transactionProfiler = options.getTransactionProfiler();
        transactionProfiler.onTransactionStart(transaction);
      }
    }
    if (transactionOptions.isBindToScope()) {
      configureScope(scope -> scope.setTransaction(transaction));
    }
    return transaction;
  }

根据传入的transactionContexttransactionOptions创建一个SamplingContext对象,调用tracesSampler.sample(samplingContext)获取当前activity的采样率samplingDecision,再创建一个SentryTracer对象transaction。接着获取AndroidTransactionProfiler对象transactionProfiler,调用其onTransactionStart()方法开始跟踪(前面的章节已经讲过了AndroidTransactionProfiler)。onActivityCreated()创建Transaction的过程讲完了,我们再来看看在onActivityResumed()时调用finishAppStartSpan()进行上报的实现:

//ActivityLifecycleIntegration.java
  private void finishAppStartSpan() {
    final @Nullable SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
    if (performanceEnabled && appStartEndTime != null) {
      finishSpan(appStartSpan, appStartEndTime);
    }
  }
//ActivityLifecycleIntegration.java
  private void finishSpan(
      final @Nullable ISpan span,
      final @NotNull SentryDate endTimestamp,
      final @Nullable SpanStatus spanStatus) {
    if (span != null && !span.isFinished()) {
      final @NotNull SpanStatus status =
          spanStatus != null
              ? spanStatus
              : span.getStatus() != null ? span.getStatus() : SpanStatus.OK;
      span.finish(status, endTimestamp);
    }
  }

跟踪到SentryTracer. finish()方法:

//SentryTracer.java
  @Override
  @ApiStatus.Internal
  public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) {
    finish(status, finishDate, true);
  }
//SentryTracer.java
  @Override
  public void finish(
      @Nullable SpanStatus status, @Nullable SentryDate finishDate, boolean dropIfNoChildren) {
//...

      ProfilingTraceData profilingTraceData = null;
      if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) {
        profilingTraceData =
            hub.getOptions()
                .getTransactionProfiler()
                .onTransactionFinish(this, performanceCollectionData);
      }
//...
      final SentryTransaction transaction = new SentryTransaction(this);
      final TransactionFinishedCallback finishedCallback =
          transactionOptions.getTransactionFinishedCallback();
      if (finishedCallback != null) {
        finishedCallback.execute(this);
      }
//...

      transaction.getMeasurements().putAll(measurements);
      hub.captureTransaction(transaction, traceContext(), null, profilingTraceData);
    }
  }

先从options拿到AndroidTransactionProfiler对象再调用其onTransactionFinish()方法封装成ProfilingTraceData(在之前的章节已经介绍过ProfilingTraceData了)。然后创建一个SentryTransaction对象,最后调用 hub.captureTransaction(transaction, traceContext(), null, profilingTraceData)上报事件:

//Hub.java
  @ApiStatus.Internal
  @Override
  public @NotNull SentryId captureTransaction(
      final @NotNull SentryTransaction transaction,
      final @Nullable TraceContext traceContext,
      final @Nullable Hint hint,
      final @Nullable ProfilingTraceData profilingTraceData) {
    Objects.requireNonNull(transaction, "transaction is required");

    SentryId sentryId = SentryId.EMPTY_ID;
    if (!isEnabled()) {
//...
    } else {
      if (!transaction.isFinished()) {
//...
      } else {
        if (!Boolean.TRUE.equals(transaction.isSampled())) {
//...
        } else {
          StackItem item = null;
          try {
            item = stack.peek();
            sentryId =
                item.getClient()
                    .captureTransaction(
                        transaction, traceContext, item.getScope(), hint, profilingTraceData);
          } catch (Throwable e) {
//...
          }
        }
      }
    }
    return sentryId;
  }

调用了SentryClientcaptureTransaction()方法。这个具体流程不讲了,跟captureEvent()类似,只是封装的数据结构不太一样。最终都是调用到AsyncHttpTransport.send()方法,流程一致。 

到此为止,Sentry提供的针对SentryEventSentryTransaction两种事件的上报已经分析完毕了。

05

定制化APM系统的初步想法

分析完整个Sentry的实现后,我们意识到如果希望定制自己的APM,完全可以以Sentrybase进行一些拓展和改造。比如说根据需求创建自己的IntegrationSentry增加新的能力;创建EventProcessor随事件上报新的参数;重写AndroidTransactionProfiler添加新的性能数据;结合其他三方APM相关库的输出作为附件封装成事件上报等。

总而言之就是将我们希望监测的性能数据与Sentry的基本能力和参数进行绑定,复用Sentry的数据结构,上报到Sentry后台或者其他后台。 

06

总结

这篇文章相信分析了Sentry(Android端)实现的具体流程,希望能给大家定制化APM系统一些参考和想法。

da03efa0e1204a963d91c7ba66f3e805.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络基础】VRRP虚拟路由冗余协议介绍与配置

目录 一、VRRP的概述 1.1 VRRP的由来 1.2 作用 1.3 基本结构 1.4 状态机流程 1.5 设备类型 二、 实例演示 一、VRRP的概述 1.1 VRRP的由来 局域网中的用户终端通常采用配置一个默认网关的形式访问外部网络&#xff0c;如果此时默认网关设备发生故障&#xff0c;将中断…

算法设计与分析-分支限界——沐雨先生

&#xff08;1&#xff09;抓奶牛问题描述&#xff1a; 农夫约翰被告知逃跑的奶牛的位置&#xff0c;并且要求立即去抓住它。约翰开始的位置在数轴上位置 N &#xff08; 0 ≤ N ≤ 100) &#xff0c;而奶牛的位置在同样一个数轴上的 K (0 ≤ K ≤ 100) 。约翰有两种移动方式&…

普洛斯怀来数据中心获Uptime MO认证,以高品质服务持续提升客户体验

近日&#xff0c;普洛斯怀来数据中心顺利通过Uptime M&O&#xff08;运维与管理&#xff09;认证&#xff0c;获得Uptime Institute颁发的认证证书。普洛斯数据中心致力于为客户提供高品质、高可靠的运维服务&#xff0c;此项认证&#xff0c;标志着普洛斯数据中心运营及管…

Mac上玩《赛博朋克2077》mac电脑怎么玩这个游戏

X用户crushovitz_b最近发现&#xff0c;在《赛博朋克2077》游戏主菜单页面&#xff0c;将鼠标停在版本号选项卡上面足够长时间&#xff0c;就会发现游戏当前的版本号由2.12变为了2.0.77&#xff0c;这是对游戏标题2077的致敬彩蛋。 《赛博朋克2077》的叙事总监兼续集副总监Pawe…

Flutter 事件传递简单概述、事件冒泡、事件穿透

前言 当前案例 Flutter SDK版本&#xff1a;3.13.2 本文对 事件传递只做 简单概述&#xff0c;主要讲解&#xff0c;事件传递过程中可能遇到的问题解决&#xff0c;比如 事件冒泡、事件穿透&#xff1b; 不是我偷懒&#xff0c;是自认为没有这几位写的详细、仔细&#xff0c…

FPGA学习_时序分析

文章目录 前言一、组合逻辑与时序逻辑二、建立时间和保持时间三、建立时间和保持时间 前言 心中有电路&#xff0c;下笔自然神&#xff01;&#xff01;&#xff01; 一、组合逻辑与时序逻辑 组合逻辑&#xff1a;没有时钟控制的数字电路&#xff0c;代码里的判断逻辑都是组…

颠覆传统:Web3如何塑造未来的数字经济

引言 近年来&#xff0c;随着数字化时代的到来&#xff0c;互联网已经成为人们生活中不可或缺的一部分。然而&#xff0c;随着技术的不断发展和社会的不断变迁&#xff0c;传统的Web2模式逐渐显露出一些弊端&#xff0c;如数据垄断、隐私泄露等问题&#xff0c;这促使人们寻求…

简历指导与模板获取

简历是应聘过程当中最重要的材料&#xff0c;是我们在求职市场的一张名片&#xff0c;一份好的简历能够吸引招聘者的注意&#xff0c;使你在竞争激烈的求职市场中脱颖而出。 1.简历指导 以下是一份典型简历的主要部分和常见内容&#xff1a; 联系信息&#xff1a; 包括你的全…

设计模式 适配器模式

1.背景 适配器模式&#xff0c;这个模式也很简单&#xff0c;你笔记本上的那个拖在外面的黑盒子就是个适配器&#xff0c;一般你在中国能用&#xff0c;在日本也能用&#xff0c;虽然两个国家的的电源电压不同&#xff0c;中国是 220V&#xff0c;日本是 110V&#xff0c;但是这…

操作系统面经-什么是操作系统?

通过以下四点可以概括操作系统到底是什么&#xff1a; 操作系统&#xff08;Operating System&#xff0c;简称 OS&#xff09;是管理计算机硬件与软件资源的程序&#xff0c;是计算机的基石。操作系统本质上是一个运行在计算机上的软件程序 &#xff0c;主要用于管理计算机硬…

DP:路径规划模型

创作不易&#xff0c;感谢三连支持&#xff01; 路径规划主要是让目标对象在规定范围内的区域内找到一条从起点到终点的无碰撞安全路径。大多需要用二维dp数组去实现 一、不同路径 . - 力扣&#xff08;LeetCode&#xff09;不同路径 class Solution { public:int uniquePath…

自动驾驶---Motion Planning之轨迹Path优化

1 背景 在之前的几篇文章中,不管是通过构建SL图《自动驾驶---Motion Planning之Path Boundary》,ST图《自动驾驶---Motion Planning之Speed Boundary》,又或者是构建SLT图《自动驾驶---Motion Planning之构建SLT Driving Corridor》,最终我们都是为了得到boundary的信息。 …

基于springboot的4S店车辆管理系统

基于springboot的4S店车辆管理系统 的设计和实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开…

算法-最短路径

图的最短路径问题是一个经典的计算机科学和运筹学问题&#xff0c;旨在找到图中两个顶点之间的最短路径。这种问题在多种场景中都有应用&#xff0c;如网络路由、地图导航等。 解决图的最短路径问题有多种算法&#xff0c;其中最著名的包括&#xff1a; 1.迪杰斯特拉算法 (1).…

抖音小店怎么定类目?分享几个爆单几率大,适合新手的细分类目!

大家好&#xff0c;我是电商糖果 做电商的应该经常听过这么一句话&#xff0c;类目大于一切&#xff01; 好的类目可以让商家减少很多竞争和难题。 糖果做电商有很多年了&#xff0c;我一直认为做店前期最难的定类目&#xff0c;中期是选品&#xff0c;后期是维护店铺。 如…

物联网数据报表分析

随着物联网技术的迅猛发展&#xff0c;越来越多的企业开始将物联网解决方案应用于各个领域&#xff0c;从提高生产效率到优化用户体验&#xff0c;物联网都发挥着至关重要的作用。然而&#xff0c;如何有效地分析和管理物联网产生的海量数据&#xff0c;成为企业面临的挑战之一…

【Java开发过程中的流程图】

流程图由一系列的图形符号和箭头组成&#xff0c;每个符号代表一个特定的操作或决策。下面是一些常见的流程图符号及其含义&#xff1a; 开始/结束符号&#xff08;圆形&#xff09;&#xff1a;表示程序的开始和结束点。 过程/操作符号&#xff08;矩形&#xff09;&#xff…

<Linux> 生产者消费者模型

目录 前言&#xff1a; 一、什么是生产者消费者模型 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;生产者消费者之间的关系 &#xff08;三&#xff09;生产者消费者模型特点 &#xff08;四&#xff09;生产者消费者模型的优点 二、基于阻塞队列实现生产…

《定时执行专家》:Nircmd 的超级搭档,解锁自动化新境界

目录 Nircmd 简介 《定时执行专家》与 Nircmd 的结合 示例&#xff1a; 自动清理电脑垃圾: 定时发送邮件: 定时关闭电脑: 《定时执行专家》的优势: 总结: 以下是一些其他使用示例&#xff1a; 立即下载《定时执行专家》&#xff1a; Nircmd 官方网站&#xff1a; 更…

代码随想录阅读笔记-栈与队列【用队列实现栈】

题目 使用队列实现栈的下列操作&#xff1a; push(x) -- 元素 x 入栈pop() -- 移除栈顶元素top() -- 获取栈顶元素empty() -- 返回栈是否为空 注意: 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。你所使用的语言…