我们在使用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
JNI_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层的原因