跳至主要內容

linphone-sdk-android网络检测分析

guodongAndroid大约 11 分钟linphone-sdk-androidandroidlinphone-sdk-android

linphone-sdk-android网络检测分析

前言

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

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

分析

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

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

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

liblinphone : SIP network reachability state is now DOWN
liblinphone : SIP network reachability state is now UP
liblinphone : SIP network reachability state is now DOWN

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

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 关键字:

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,我们跟进去看看:

// 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 方法:

// 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 方法的下面就是调用刷新账户注册的实现:

// 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 的实现:

// 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 方法:

// 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 方法:

// android-platform-helpers.cpp

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

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

// 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 方法中:

// 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 方法:

// 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 方法中找到对应的网络管理器实现:

// 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 方法:

// 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 中搜索一番:

// 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 中搜索发现:

// 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 中搜索分析:

// 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); 中赋值:

// 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 层代码即可:

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 方法中有输出是否开启了「自动检测网络状态」:

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

happy~

总结

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

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

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