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 ScopedJavaLocalRefJNI_PeerConnectionFactory_CreatePeerConnectionFactory( JNIEnv* jni, const JavaParamRef& jcontext, const JavaParamRef& joptions, jlong native_audio_device_module, jlong native_audio_encoder_factory, jlong native_audio_decoder_factory, const JavaParamRef& jencoder_factory, const JavaParamRef& jdecoder_factory, jlong native_audio_processor, jlong native_fec_controller_factory, jlong native_media_transport_factory) { rtc::scoped_refptr audio_processor = reinterpret_cast(native_audio_processor); return CreatePeerConnectionFactoryForJava( jni, jcontext, joptions, reinterpret_cast(native_audio_device_module), TakeOwnershipOfRefPtr(native_audio_encoder_factory), TakeOwnershipOfRefPtr(native_audio_decoder_factory), jencoder_factory, jdecoder_factory, audio_processor ? audio_processor : CreateAudioProcessing(), TakeOwnershipOfUniquePtr( native_fec_controller_factory), TakeOwnershipOfUniquePtr( 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& j_rtc_config, const JavaParamRef& j_constraints, jlong observer_p, const JavaParamRef& 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. * * 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 * bug * 3721 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(j_p); field_trial::InitFieldTrialsFromString(nullptr); GetStaticObjects().field_trials_init_string = nullptr;}
实现很简单,但我曾经遇到过在执行 delete reinterpret_cast
网上还有一些关于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层的原因
更多相关文章
- 关于Android多项目依赖在Eclipse中无法关联源代码的问题解决 (an
- Android 7.1 触摸事件代码跟踪
- android 开发中将十六进制 颜色代码 转换为int类型数值 方法 :
- TextView --- 内容设置成上下滑动 和 代码设置字体颜色
- android微博客户端源代码
- Android 登录界面xml代码
- android 正三角,倒三角的实现代码
- Android上积累代码
- Android实用代码