0%

linphone-sdk-android网络检测分析

linphone-sdk-android网络检测分析

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 8 天,点击查看活动详情


前言

好久没写 linphone-sdk-android 相关的文章了,上一篇文章还是一个月之前,经过上次修改 linphone-sdk-android 后最近没有啥问题发生,本文记录下之前遇到的 linphone 网络问题的坑。

注:笔者的 App 作为 Launcher 启动,目前运行在 Android 7.1.2 API 25 系统上,使用以太网连接。

分析

注:以下源码基于 linphone-sdk-android 4.5.26。

问题描述:在设备重启后,程序偶现发起不了呼叫,提示当前网络不可达,但是当前网络是好的,这就比较奇怪了。

接下来查看设备重启后的日志,发现一些与 linphone 网络相关的:

1
2
3
liblinphone : SIP network reachability state is now DOWN
liblinphone : SIP network reachability state is now UP
liblinphone : SIP network reachability state is now DOWN

刷新账户注册时也提示网络不可达,日志如下:

1
liblinphone : Refresh register operation not available (network unreachable)

然后执行测试程序调用 Core#isNetworkReachable 方法也返回 false ,发起呼叫时笔者调用了此接口判断网络是否可达,所以出现了呼叫不了,提示当前网络不可达的问题,现在猜测是 linphone 内部维护的网络状态出现了问题,遂去查看下源码中对网络状态的处理。

Core#setNetworkReachable

首先想到 Core#setNetworkReachable 接口,从 setNetworkReachable 下手吧,setNetworkReachable 方法最终会调用 native 的 private native void setNetworkReachable(long var1, boolean var3);

在之前的文章中,我们分析了 Core 接口调用的 native 方法一般在 linphone_jni.cc 中实现,我们打开 linphone_jni.cc,在其中搜索 setNetworkReachable 关键字:

1
2
3
4
5
6
7
8
JNIEXPORT void JNICALL Java_org_linphone_core_CoreImpl_setNetworkReachable(JNIEnv *env, jobject thiz, jlong ptr, jboolean reachable) {
LinphoneCore *cptr = (LinphoneCore*)ptr;
if (cptr == nullptr) {
bctbx_error("Java_org_linphone_core_CoreImpl_setNetworkReachable's LinphoneCore C ptr is null!");
return ;
}
linphone_core_set_network_reachable(cptr, (bool_t)reachable);
}

linphone_jni.cc 中调用了 linphone_core_set_network_reachable,我们跟进去看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// linphonecore.c

void linphone_core_set_network_reachable(LinphoneCore *lc, bool_t is_reachable) {
bool_t reachable = is_reachable;

// SIP协议层网络状态 - 用户设置的状态
lc->sip_network_state.user_state = is_reachable;

// 媒体传输层网络状态 - 用户设置的状态
lc->media_network_state.user_state = is_reachable;

// 如果开启了网络状态自动检测
if (lc->auto_net_state_mon) reachable = reachable && getPlatformHelpers(lc)->isNetworkReachable();

// 设置SIP协议层网络状态 - 全局状态
set_sip_network_reachable(lc, reachable, ms_time(NULL));

// 设置媒体传输层网络状态 - 全局状态
set_media_network_reachable(lc, reachable);

// 通知网络可达性变更
notify_network_reachable_change(lc);
}

linphone_core_set_network_reachable 方法中首先是修改了 「SIP 协议层网络状态 - 用户设置的状态」和「媒体传输层网络状态 - 用户设置的状态」,接下来判断是否开启了「网络状态自动检测」配置,若是开启了,则获取自动检测的网络状态并与传入的状态取且值,这里可能会改变传入的值:如果传入 True,而 isNetworkReachable 返回 False,最终 reachable 为 False。

接下来,我们看下 set_sip_network_reachable 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// linphonecore.c

static void set_sip_network_reachable(LinphoneCore* lc,bool_t is_sip_reachable, time_t curtime){
// second get the list of available proxies
const bctbx_list_t *elem = NULL;

// SIP协议层网络状态 - 全局状态,比较是否有变化
if (lc->sip_network_state.global_state==is_sip_reachable) return; // no change, ignore.
lc->network_reachable_to_be_notified=TRUE;

// ......

// 对应上面提到的设备重启后与网络相关的日志
ms_message("SIP network reachability state is now [%s]",is_sip_reachable?"UP":"DOWN");

// ......

lc->netup_time=curtime;

// 修改SIP协议层网络状态 - 全局状态
lc->sip_network_state.global_state=is_sip_reachable;

// ......
}

set_sip_network_reachable 方法中,首先判断传入的值是否与已有的值相等,相等则认为没有变化,忽略此次调用,然后打印传入 is_sip_reachable 的值,这行日志对应前面提到的设备重启后与网络相关的日志,最后修改「SIP协议层网络状态 - 全局状态」。

前面提到刷新账户注册时也提示网络不可达,赶巧了,set_sip_network_reachable 方法的下面就是调用刷新账户注册的实现:

1
2
3
4
5
6
7
8
9
10
11
12
// linphonecore.c

void linphone_core_refresh_registers(LinphoneCore* lc) {
// 判断SIP协议层网络状态 - 全局状态
if (!lc->sip_network_state.global_state) {
// 对应刷新账户注册时的网络不可达日志
ms_warning("Refresh register operation not available (network unreachable)");
return;
}

// ......
}

linphone_core_refresh_registers 方法中,首先就判断了「SIP协议层网络状态 - 全局状态」,如果为 False,则此时网络不可达,输出网络不可达日志。

最后看下 Core#isNetworkReachable 的实现:

1
2
3
4
5
6
// linphonecore.c

bool_t linphone_core_is_network_reachable(LinphoneCore* lc) {
// 返回SIP协议层网络状态 - 全局状态
return lc->sip_network_state.global_state;
}

在 Java 中调用 isNetworkReachable 最终会调用到 linphone_core_is_network_reachable 方法,在 linphone_core_is_network_reachable 方法中,直接返回了「SIP协议层网络状态 - 全局状态」。

AndroidPlatformHelper#setNetworkReachable

在上一节中我们分析是在 set_sip_network_reachable 方法修改的「SIP协议层网络状态 - 全局状态」,除了我们调用 Core#setNetworkReachable 外,应该还有调用方,在 IDE 中分析发现了 linphone_core_set_network_reachable_internal 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// linphonecore.c

void linphone_core_set_network_reachable_internal(LinphoneCore *lc, bool_t is_reachable) {
// 如果开启了网络状态自动检测
if (lc->auto_net_state_mon) {
// 设置SIP协议层网络状态 - 全局状态
set_sip_network_reachable(lc, lc->sip_network_state.user_state && is_reachable, ms_time(NULL));

// 设置媒体传输层网络状态 - 全局状态
set_media_network_reachable(lc, lc->media_network_state.user_state && is_reachable);

// 通知网络可达性变更
notify_network_reachable_change(lc);
}
}

linphone_core_set_network_reachable_internal 方法中首先判断是否开启了「开启了网络状态自动检测」配置,然后修改全局网络状态。

继续分析哪些调用方调用了 linphone_core_set_network_reachable_internal 方法:

1
2
3
4
5
6
// android-platform-helpers.cpp

void AndroidPlatformHelpers::setNetworkReachable(bool reachable) {
mNetworkReachable = reachable;
linphone_core_set_network_reachable_internal(getCore()->getCCore(), reachable ? 1 : 0);
}

继续分析哪些调用方调用了 AndroidPlatformHelpers::setNetworkReachable 方法:

1
2
3
4
5
6
7
8
9
// android-platform-helpers.cpp

extern "C" JNIEXPORT void JNICALL Java_org_linphone_core_tools_AndroidPlatformHelper_setNetworkReachable(JNIEnv* env, jobject thiz, jlong ptr, jboolean reachable) {
AndroidPlatformHelpers *androidPlatformHelper = static_cast<AndroidPlatformHelpers *>((void *)ptr);
const std::function<void ()> fun = [androidPlatformHelper, reachable]() {
androidPlatformHelper->setNetworkReachable(reachable);
};
androidPlatformHelper->getCore()->doLater(fun);
}

在这里创建了一个 fun 对象,类似 Java 中的 Runnable 接口,抛给 doLater 方法稍后执行。

跟踪到这里,追到了 JNI 层,看到了熟悉的 Java 包名和类名 AndroidPlatformHelper ,在 AS 中打开 AndroidPlatformHelper ,搜索调用 setNetworkReachable 的地方,共有 6 处,均在 updateNetworkReachability 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// AndroidPlatformHelper.java

public synchronized void updateNetworkReachability() {
if (this.mNativePtr == 0L) {
Log.w(new Object[]{"[Platform Helper] Native pointer has been reset, stopping there"});
} else if (this.mNetworkManager == null) {
Log.w(new Object[]{"[Platform Helper] Network Manager is null, stopping there"});
} else {
boolean connected = this.mNetworkManager.isCurrentlyConnected(this.mContext);
if (!connected) {
Log.i(new Object[]{"[Platform Helper] No connectivity: setting network unreachable"});

// ①
this.setNetworkReachable(this.mNativePtr, false);
} else {
if (this.mNetworkManager.hasHttpProxy(this.mContext)) {
if (this.useSystemHttpProxy(this.mNativePtr)) {
String host = this.mNetworkManager.getProxyHost(this.mContext);
int port = this.mNetworkManager.getProxyPort(this.mContext);
this.setHttpProxy(this.mNativePtr, host, port);
if (!this.mUsingHttpProxy) {
Log.i(new Object[]{"[Platform Helper] Proxy wasn't set before, disabling network reachability first"});
// ②
this.setNetworkReachable(this.mNativePtr, false);
}

this.mUsingHttpProxy = true;
} else {
Log.w(new Object[]{"[Platform Helper] Proxy available but forbidden by linphone core [sip] use_system_http_proxy setting"});
}
} else {
this.setHttpProxy(this.mNativePtr, "", 0);
if (this.mUsingHttpProxy) {
Log.i(new Object[]{"[Platform Helper] Proxy was set before, disabling network reachability first"});

// ③
this.setNetworkReachable(this.mNativePtr, false);
}

this.mUsingHttpProxy = false;
}

NetworkInfo networkInfo = this.mNetworkManager.getActiveNetworkInfo();
if (networkInfo == null) {
Log.e(new Object[]{"[Platform Helper] getActiveNetworkInfo() returned null !"});

// ④
this.setNetworkReachable(this.mNativePtr, false);
} else {
Log.i(new Object[]{"[Platform Helper] Active network type is " + networkInfo.getTypeName() + ", state " + networkInfo.getState() + " / " + networkInfo.getDetailedState()});
if (networkInfo.getState() == State.DISCONNECTED && networkInfo.getDetailedState() == DetailedState.BLOCKED) {
Log.w(new Object[]{"[Platform Helper] Active network is in bad state..."});
}

Network network = this.mNetworkManager.getActiveNetwork();
this.mNetworkManager.updateDnsServers();
int currentNetworkType = networkInfo.getType();
if (this.mLastNetworkType != -1 && this.mLastNetworkType != currentNetworkType) {
Log.i(new Object[]{"[Platform Helper] Network type has changed (last one was " + this.networkTypeToString(this.mLastNetworkType) + "), disabling network reachability first"});

// ⑤
this.setNetworkReachable(this.mNativePtr, false);
}

this.mLastNetworkType = currentNetworkType;
Log.i(new Object[]{"[Platform Helper] Network reachability enabled"});

// ⑥
this.setNetworkReachable(this.mNativePtr, true);
}
}
}
}

以上 6 处调用,只有最后的第 6 处传入了 True,认为网络可达;其余 5 处均传入了 False,认为网络不可达。上面的代码主要是获取当前活跃的网络信息,如果没有活跃的网络信息就认为网络不可达。

继续分析哪些调用方调用了 AndroidPlatformHelpers#updateNetworkReachability 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AndroidPlatformHelper.java

private synchronized void startNetworkMonitoring() {
// 判断是否开启了自动检测网络状态
if (this.mMonitoringEnabled) {
// 根据 Android 系统版本创建网络管理器
this.mNetworkManager = this.createNetworkManager();
Log.i(new Object[]{"[Platform Helper] Registering network callbacks"});

// 注册网络变化回调
this.mNetworkManager.registerNetworkCallbacks(this.mContext);

// ......

// 调用了updateNetworkReachability
this.updateNetworkReachability();
}
}

startNetworkMonitoring 方法主要是开启网络状态自动检测功能,首先判断是否开启了自动检测网络状态配置,接下来根据 Android 系统版本创建网络管理器,然后注册网络变化回调,最后调用了 updateNetworkReachability 方法,进行一次网络状态通知。

前面提到笔者目前使用的设备是 API 25,在 createNetworkManager 方法中找到对应的网络管理器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// NetworkManagerAbove24.java

// ......

public NetworkManagerAbove24(AndroidPlatformHelper helper, ConnectivityManager cm, boolean wifiOnly) {
this.mHelper = helper;
this.mConnectivityManager = cm;
this.mWifiOnly = wifiOnly;
this.mNetworkAvailable = null;
this.mLastNetworkAvailable = null;

// 网络变化回调
this.mNetworkCallback = new NetworkCallback() {
// 网络可用
public void onAvailable(final Network network) {
// MainHandler
NetworkManagerAbove24.this.mHelper.getHandler().post(new Runnable() {
public void run() {
NetworkInfo info = NetworkManagerAbove24.this.mConnectivityManager.getNetworkInfo(network);
if (info == null) {
Log.e(new Object[]{"[Platform Helper] [Network Manager 24] A network should be available but getNetworkInfo failed."});
} else {
Log.i(new Object[]{"[Platform Helper] [Network Manager 24] A network is available: " + info.getTypeName() + ", wifi only is " + (NetworkManagerAbove24.this.mWifiOnly ? "enabled" : "disabled")});
if (NetworkManagerAbove24.this.mWifiOnly && info.getType() != 1 && info.getType() != 9) {
Log.i(new Object[]{"[Platform Helper] [Network Manager 24] Network isn't wifi and wifi only mode is enabled"});
if (NetworkManagerAbove24.this.mWifiOnly) {
NetworkManagerAbove24.this.mLastNetworkAvailable = network;
}
} else {
NetworkManagerAbove24.this.mNetworkAvailable = network;

// 调用了updateNetworkReachability
NetworkManagerAbove24.this.mHelper.updateNetworkReachability();
}

}
}
});
}

// 网络丢失
public void onLost(final Network network) {
// MainHandler
NetworkManagerAbove24.this.mHelper.getHandler().post(new Runnable() {
public void run() {
Log.i(new Object[]{"[Platform Helper] [Network Manager 24] A network has been lost"});
if (NetworkManagerAbove24.this.mNetworkAvailable != null && NetworkManagerAbove24.this.mNetworkAvailable.equals(network)) {
NetworkManagerAbove24.this.mNetworkAvailable = null;
}

if (NetworkManagerAbove24.this.mLastNetworkAvailable != null && NetworkManagerAbove24.this.mLastNetworkAvailable.equals(network)) {
NetworkManagerAbove24.this.mLastNetworkAvailable = null;
}

// 调用了updateNetworkReachability
NetworkManagerAbove24.this.mHelper.updateNetworkReachability();
}
});
}
};
}

// ......

// 注册网络变化回调
public void registerNetworkCallbacks(Context context) {
int permissionGranted = context.getPackageManager().checkPermission("android.permission.ACCESS_NETWORK_STATE", context.getPackageName());
Log.i(new Object[]{"[Platform Helper] [Network Manager 24] ACCESS_NETWORK_STATE permission is " + (permissionGranted == 0 ? "granted" : "denied")});
if (permissionGranted == 0) {
this.mConnectivityManager.registerDefaultNetworkCallback(this.mNetworkCallback);
}
}

// ......

NetworkManagerAbove24.java 的实现中,网络可用时也会调用 AndroidPlatformHelper#updateNetworkReachability方法。

到这里其实差不多了,因为分析下来,发现网络可用时通知 linphone 网络可达,网络丢失时通知 linphone 网络不可达,以上流程符合正常的思维逻辑,笔者追到这里,只能是怀疑在 startNetworkMonitoringNetworkManagerAbove24#onAvailable 的两处调用有并发顺序问题,onAvailable 一定是在主线程执行的,而 startNetworkMonitoring 就不确定是在哪个线程执行了。

最后笔者想着关闭「自动检测网络状态」配置,由业务程序自身监听网络变化,然后调用 Core#setNetworkReachable 方法通知 linphone 网络是否可达。

auto_net_state_mon

在上一节,我们分析了 startNetworkMonitoring 方法,现在我们继续分析哪些调用方调用了 startNetworkMonitoring 方法:

1
2
3
4
5
6
7
// AndroidPlatformHelper.java

public synchronized void onLinphoneCoreStart(boolean monitoringEnabled) {
Log.i(new Object[]{"[Platform Helper] onLinphoneCoreStart, network monitoring is " + monitoringEnabled});
this.mMonitoringEnabled = monitoringEnabled;
this.startNetworkMonitoring();
}

onLinphoneCoreStart 方法在 AndroidPlatformHelper.java 中没有找到调用方,怀疑此方法是在 native 层调用了,我们去 android-platform-helpers.cpp 中搜索一番:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// android-platform-helpers.cpp

void AndroidPlatformHelpers::onLinphoneCoreStart(bool monitoringEnabled) {
JNIEnv *env = ms_get_jni_env();
if (env) {

// ......

// 调用AndroidPlatformHelper#onLinphoneCoreStart
if (mJavaHelper) {
env->CallVoidMethod(mJavaHelper, mOnLinphoneCoreStartId, (jboolean)monitoringEnabled);
}
}
}

AndroidPlatformHelpers::onLinphoneCoreStart 方法中调用了 AndroidPlatformHelper#onLinphoneCoreStart 方法,好的,我们继续分析 AndroidPlatformHelpers::onLinphoneCoreStart 的调用方,通过在 IDE 中搜索发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// linphonecore.c

LinphoneStatus linphone_core_start (LinphoneCore *lc) {
// ......

// 获取是否开启了网络状态自动检测
bool autoNetworkStateMonitoringEnabled = !!lc->auto_net_state_mon;

// 如果没有开启,输出日志
if (!autoNetworkStateMonitoringEnabled) {
bctbx_warning("Automatic network state monitoring is disabled by configuration (auto_net_state_mon=0). This is not recommended.");
bctbx_warning("In this mode, apps must use linphone_core_set_network_reachable() and linphone_core_set_dns_servers() to notify the LinphoneCore of network availability and provide the DNS server list.");
}

// 调用AndroidPlatformHelpers::onLinphoneCoreStart
getPlatformHelpers(lc)->onLinphoneCoreStart(autoNetworkStateMonitoringEnabled);

// ......
}

bingo~,发现了 lc->auto_net_state_mon ,现在我们只要找到 lc->auto_net_state_mon 是在哪里赋值的就可以了,还是在 IDE 中搜索分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// linphonecore.c

static void _linphone_core_read_config(LinphoneCore * lc) {

// 读取各种配置项
sip_setup_register_all(lc->factory);
sound_config_read(lc);
net_config_read(lc);
rtp_config_read(lc);
codecs_config_read(lc);

// 读取sip_config
sip_config_read(lc);
video_config_read(lc);
//autoreplier_config_init(&lc->autoreplier_conf);
misc_config_read(lc);
ui_config_read(lc);
#ifdef TUNNEL_ENABLED
if (lc->tunnel) {
linphone_tunnel_configure(lc->tunnel);
}
#endif

// 使用sip_conf->auto_net_state_mon赋值
lc->auto_net_state_mon=lc->sip_conf.auto_net_state_mon;
}

_linphone_core_read_config 方法主要是读取各种配置项,与 auto_net_state_mon 相关的主要是 sip_config_read(lc); 和 最后一行代码,最后一行代码使用 sip_conf 中的 auto_net_state_monlc->auto_net_state_mon 赋值,sip_confsip_config_read(lc); 中赋值:

1
2
3
4
5
6
7
8
9
// linphonecore.c

static void sip_config_read(LinphoneCore *lc) {
// ......

lc->sip_conf.auto_net_state_mon = !!linphone_config_get_int(lc->config,"sip","auto_net_state_mon",1);

// ......
}

sip_config_read 方法中,通过读取 Config 中的 SectionKey 赋值,如果没有相应的 SectionKey ,则取默认值 1。

终于找到配置「自动检测网络状态」开关的地方了,Config 是在 Java 层创建 Core 时传入的,所以现在我们只需修改 Java 层代码即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int start() {
// 创建Config
Config configWithFactory = mFactory.createConfigWithFactory(null, null);

// 根据外部传入的配置判断是否开启linphone的「自动检测网络状态」开关
if (mConfig.autoNetStateMonitorEnabled()) {
configWithFactory.setInt("sip", "auto_net_state_mon", 1);
} else {
configWithFactory.setInt("sip", "auto_net_state_mon", 0);
}

// 根据Config创建Core
mCore = mFactory.createCoreWithConfig(configWithFactory, mContext.getApplicationContext());
}

首先通过 Factory 创建空的 Config 对象,然后根据外部传入的配置判断是否开启linphone的「自动检测网络状态」开关,随后根据 Config 对象创建 Core

最后运行程序查看 Logcat 日志,在 AndroidPlatformHelper#onLinphoneCoreStart 方法中有输出是否开启了「自动检测网络状态」:

1
linphone-android : [Platform Helper] onLinphoneCoreStart, network monitoring is false

happy~

总结

本文笔者记录了从根据问题现象,分析问题,反向跟踪源码到网络检测逻辑,至网络检测逻辑时,问题分析陷入困境,再到大胆假设是并发引起的问题,随后沿着并发引起问题的假设,想到关闭 linphone「自动检测网络状态」配置的方法,最后分析「自动检测网络状态」配置的开启流程。

笔者在分析排查期间有一步放弃的话,也看不到最后的曙光。

不经历风雨怎能见彩虹,希望可以帮到你~

欢迎关注我的其它发布渠道