Android上 PeerConnection 与 PeerConnectionFactory 的释放

我们在使用WebRTC Android native SDK进行开发的时候,PeerConnection与PeerConnectionFactory是两个再熟悉不过的类了。他们的源码分别位于:

src\sdk\android\api\org\webrtc\PeerConnectionFactory.java
src\sdk\android\api\org\webrtc\PeerConnection.java

这两个类担负着所有和服务器建立连接的初始工作,以及LocalMediaStream、A/V Track、A/V Source等重要对象的创建。这里主要是简单描述一下PeerConnection(以下简称PC)和PeerConnectionFactory(以下简称PCF)的释放。

在描述释放过程之前,我们先来看一下PC和PCF的创建过程。首先,让我们从WebRTC自带的世界著名的例子程序AppRTC的源码开始,它的Android版本位于:

src\examples\androidapp\

其中,几乎所有的有关连接、媒体的关键代码,都在PeerConnectionClient.java中可以找到。它有一个成员变量,是这样的:

  // Executor thread is started once in private ctor and is used for all
  // peer connection API calls to ensure new peer connection factory is
  // created on the same thread as previously destroyed factory.
  private static final ExecutorService executor = Executors.newSingleThreadExecutor();

所以我们看到,无论是创建PCF的方法 createPeerConnectionFactoryInternal() 还是创建PC的方法createPeerConnectionInternal(),都是通过调用 executor.execute() 来执行的,这样可以保证他们工作在同一个线程。

OK,让我们看看PCF和PC的创建内部。注:以下代码来源于WebRTC branch-heads/m73,因WebRTC代码变化较快,你看到的可能跟我的不是相同版本。

createPeerConnectionFactory() & PCF.createPeerConnection()

public PeerConnectionFactory createPeerConnectionFactory() {
      checkInitializeHasBeenCalled();
      return nativeCreatePeerConnectionFactory(ContextUtils.getApplicationContext(), options,
          audioDeviceModule == null ? 0 : audioDeviceModule.getNativeAudioDeviceModulePointer(),
          audioEncoderFactoryFactory.createNativeAudioEncoderFactory(),
          audioDecoderFactoryFactory.createNativeAudioDecoderFactory(), videoEncoderFactory,
          videoDecoderFactory,
          audioProcessingFactory == null ? 0 : audioProcessingFactory.createNative(),
          fecControllerFactoryFactory == null ? 0 : fecControllerFactoryFactory.createNative(),
          mediaTransportFactoryFactory == null
              ? 0
              : mediaTransportFactoryFactory.createNativeMediaTransportFactory());
    }
/**
   * Internal helper function to pass the parameters down into the native JNI bridge.
   */
  @Nullable
  PeerConnection createPeerConnectionInternal(PeerConnection.RTCConfiguration rtcConfig,
      MediaConstraints constraints, PeerConnection.Observer observer,
      SSLCertificateVerifier sslCertificateVerifier) {
    checkPeerConnectionFactoryExists();
    long nativeObserver = PeerConnection.createNativePeerConnectionObserver(observer);
    if (nativeObserver == 0) {
      return null;
    }
    long nativePeerConnection = nativeCreatePeerConnection(
        nativeFactory, rtcConfig, constraints, nativeObserver, sslCertificateVerifier);
    if (nativePeerConnection == 0) {
      return null;
    }
    return new PeerConnection(nativePeerConnection);
  }

显而易见,我们需要离开Java,走向JNI的世界:

src\sdk\android\src\jni\pc\peer_connection_factory.cc

static ScopedJavaLocalRef<jobject>
JNI_PeerConnectionFactory_CreatePeerConnectionFactory(
    JNIEnv* jni,
    const JavaParamRef<jobject>& jcontext,
    const JavaParamRef<jobject>& joptions,
    jlong native_audio_device_module,
    jlong native_audio_encoder_factory,
    jlong native_audio_decoder_factory,
    const JavaParamRef<jobject>& jencoder_factory,
    const JavaParamRef<jobject>& jdecoder_factory,
    jlong native_audio_processor,
    jlong native_fec_controller_factory,
    jlong native_media_transport_factory) {
  rtc::scoped_refptr<AudioProcessing> audio_processor =
      reinterpret_cast<AudioProcessing*>(native_audio_processor);
  return CreatePeerConnectionFactoryForJava(
      jni, jcontext, joptions,
      reinterpret_cast<AudioDeviceModule*>(native_audio_device_module),
      TakeOwnershipOfRefPtr<AudioEncoderFactory>(native_audio_encoder_factory),
      TakeOwnershipOfRefPtr<AudioDecoderFactory>(native_audio_decoder_factory),
      jencoder_factory, jdecoder_factory,
      audio_processor ? audio_processor : CreateAudioProcessing(),
      TakeOwnershipOfUniquePtr<FecControllerFactoryInterface>(
          native_fec_controller_factory),
      TakeOwnershipOfUniquePtr<MediaTransportFactory>(
          native_media_transport_factory));
}

具体源码就不详细写了,代码比较清晰。其中它会创建3个重要native线程:NetworkThread、SignalingThread、WorkerThread,这3个线程自始至终都在底层默默地工作着,为我们的App提供各种服务。当然还有其他一些重要的Factory对象会创建出来,各负其责。当其就绪的时候,也会再通知到Java层。例如当WorkerThread就绪的时候,PeerConnectionFactory.java会收到onWorkerThreadReady()通知(这个是通过peer_connection_factory.cc 里的 NativeToScopedJavaPeerConnectionFactory方法实现的)。

static jlong JNI_PeerConnectionFactory_CreatePeerConnection(
    JNIEnv* jni,
    jlong factory,
    const JavaParamRef<jobject>& j_rtc_config,
    const JavaParamRef<jobject>& j_constraints,
    jlong observer_p,
    const JavaParamRef<jobject>& j_sslCertificateVerifier) {
    //代码太长就不贴了
}

OK,PCF和PC的创建过程大致如此。那么来看本文的正题,即PCF和PC的释放。

有了上面的介绍,释放的代码就比较好找了:

PC的释放:

public void close() {
    nativeClose();
  }
/**
   * Free native resources associated with this PeerConnection instance.
   *
   * This method removes a reference count from the C++ PeerConnection object,
   * which should result in it being destroyed. It also calls equivalent
   * "dispose" methods on the Java objects attached to this PeerConnection
   * (streams, senders, receivers), such that their associated C++ objects
   * will also be destroyed.
   *
   * <p>Note that this method cannot be safely called from an observer callback
   * (PeerConnection.Observer, DataChannel.Observer, etc.). If you want to, for
   * example, destroy the PeerConnection after an "ICE failed" callback, you
   * must do this asynchronously (in other words, unwind the stack first). See
   * <a href="https://bugs.chromium.org/p/webrtc/issues/detail?id=3721">bug
   * 3721</a> for more details.
   */
  public void dispose() {
    close();
    for (MediaStream stream : localStreams) {
      nativeRemoveLocalStream(stream.getNativeMediaStream());
      stream.dispose();
    }
    localStreams.clear();
    for (RtpSender sender : senders) {
      sender.dispose();
    }
    senders.clear();
    for (RtpReceiver receiver : receivers) {
      receiver.dispose();
    }
    for (RtpTransceiver transceiver : transceivers) {
      transceiver.dispose();
    }
    transceivers.clear();
    receivers.clear();
    nativeFreeOwnedPeerConnection(nativePeerConnection);
  }

注意,PC的dispose()会执行自动调用 nativeClose()、LocalMediaStream.dispose()、RtpSender.dispose()、RtpReceiver.dispose()、RtpTransceiver.dispose()其中,MediaStream.dispose()还会继续释放掉所有AudioTracks、VideoTracks及其持有的nativeStream对象,同样地,RtpSender、RtpReceiver、RtpTransceiver等都是类似。而如果你的代码中,有其他地方使用了这些对象,一定要意识到,调用了PC.dispose(),这些对象都不存在了,避免再使用它们中的任意一个而产生异常调用。我就曾经因为这个原因,踩了不少坑。

PC.dispose()的native调用,参考 src\sdk\android\src\jni\pc\peer_connection.cc 里面的实现,代码就不贴了。

PCF的释放:

public void dispose() {
    checkPeerConnectionFactoryExists();
    nativeFreeFactory(nativeFactory);
    networkThread = null;
    workerThread = null;
    signalingThread = null;
    MediaCodecVideoEncoder.disposeEglContext();
    MediaCodecVideoDecoder.disposeEglContext();
    nativeFactory = 0;
  }

nativeFreeFactory() 对应的JNI代码位于src\sdk\android\src\jni\pc\peer_connection_factory.cc:

static void JNI_PeerConnectionFactory_FreeFactory(JNIEnv*,
                                                  jlong j_p) {
  delete reinterpret_cast<OwnedFactoryAndThreads*>(j_p);
  field_trial::InitFieldTrialsFromString(nullptr);
  GetStaticObjects().field_trials_init_string = nullptr;
}

实现很简单,但我曾经遇到过在执行 delete reinterpret_cast<OwnedFactoryAndThreads*>(j_p); 产生随机性崩溃的问题,也是因为Java层对native对象持有以及释放顺序不正确导致的。

网上还有一些关于PC.dispose()和PCF.dispose()会引起crash的问题描述,例如:

https://stackoverflow.com/questions/36191282/is-a-particular-threading-model-required-for-webrtc-native-android-app

他遇到的问题原因主要是使用了一个内部类来同时实现了 SDPObserver、PCObserver以及DataObserver。

最后,回到本文一开始引用AppRtc例子中的executor的目的,就是提醒大家,在dispose的时候,也一定要保持在相同线程进行,否则也有可能产生意想不到的后果。

总结一下,释放的关键点:

  • 同一个线程中创建及销毁
  • PC的销毁会自动释放大量的对象,不要在dispose()后再使用它们,尤其在多线程环境中
  • 实际应用中,对象模型往往是很复杂的,不会像AppRtc例子那么简单。但无论多么复杂,一定要保证释放顺序不要弄错:可以参考AppRtc例子中PeerConnectionClient.java 的 close()方法。
  • 关注Java持有的native对象的生命周期,如果发生native层crash,先找Java层的原因

 

 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值