本文简单介绍下WiFi打开与关闭流程,参考源码Android P。

一、WiFi 开机自动打开流程

系统服务启动的时候会启动WifiService,在SystemService.PHASE_SYSTEM_SERVICES_READY的时候,会调用 WifiServiceImpl#checkAndStartWifi(),获取Wi-Fi开关,持久化存储的值。

然后判断是否需要打开WiFi。

    /*** Check if we are ready to start wifi.** First check if we will be restarting system services to decrypt the device. If the device is* not encrypted, check if Wi-Fi needs to be enabled and start if needed** This function is used only at boot time.*/public void checkAndStartWifi() {// First check if we will end up restarting WifiServiceif (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {Log.d(TAG, "Device still encrypted. Need to restart SystemServer.  Do not start wifi.");return;}// Check if wi-fi needs to be enabledboolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();........// If we are already disabled (could be due to airplane mode), avoid changing persist// state hereif (wifiEnabled) {try {setWifiEnabled(mContext.getPackageName(), wifiEnabled);} catch (RemoteException e) {/* ignore - local call */}}}

二、系统设置主动开启WiFi

1. WiFi开关切换,在WifiEnabler的SwitchToggled中会调用WifiManager.setWifiEnabled方法。

    @Overridepublic boolean onSwitchToggled(boolean isChecked) {//Do nothing if called as a result of a state machine eventif (mStateMachineEvent) {return true;}// Show toast message if Wi-Fi is not allowed in airplane modeif (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();// Reset switch to off. No infinite check/listener loop.mSwitchWidget.setChecked(false);return false;}if (isChecked) {mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_WIFI_ON);} else {// Log if user was connected at the time of switching off.mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_WIFI_OFF,mConnected.get());}if (!mWifiManager.setWifiEnabled(isChecked)) {// ErrormSwitchWidget.setEnabled(true);Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();}return true;}

2. WifiManager中通过AIDL接口调用WifiService的方法。

    /*** Enable or disable Wi-Fi.* <p>* Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}* permission to toggle wifi.** @param enabled {@code true} to enable, {@code false} to disable.* @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is*         either already in the requested state, or in progress toward the requested state.* @throws  {@link java.lang.SecurityException} if the caller is missing required permissions.*/public boolean setWifiEnabled(boolean enabled) {try {return mService.setWifiEnabled(mContext.getOpPackageName(), enabled);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

3. WiFiManager.aidl中提供了setWifiEnabled()接口。

boolean setWifiEnabled(String packageName, boolean enable);

4. WifiServiceImpl中实现WifiManager的接口,在setWifiEnabled函数中会先调用WifiSettingsStore将WiFi开关值进行持久化存储。

然后WifiController发消息CMD_WIFI_TOGGLED 去打开WiFi。

    /*** see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}* @param enable {@code true} to enable, {@code false} to disable.* @return {@code true} if the enable/disable operation was*         started or is already in the queue.*/@Overridepublic synchronized boolean setWifiEnabled(String packageName, boolean enable)throws RemoteException {........try {if (! mSettingsStore.handleWifiToggled(enable)) {// Nothing to do if wifi cannot be toggledreturn true;}} finally {Binder.restoreCallingIdentity(ident);}........mWifiController.sendMessage(CMD_WIFI_TOGGLED);return true;}

5. WifiController中对CMD_WIFI_TOGGLED消息进行处理,从初始状态StaDisabledState(如果没有开启wifi_scan_always_enabled功能)切换到DeviceActiveState状态。

    class StaDisabledState extends State {private int mDeferredEnableSerialNumber = 0;private boolean mHaveDeferredEnable = false;private long mDisabledTimestamp;@Overridepublic void enter() {mWifiStateMachinePrime.disableWifi();// Supplicant can't restart right away, so note the time we switched offmDisabledTimestamp = SystemClock.elapsedRealtime();mDeferredEnableSerialNumber++;mHaveDeferredEnable = false;mWifiStateMachine.clearANQPCache();}@Overridepublic boolean processMessage(Message msg) {switch (msg.what) {case CMD_WIFI_TOGGLED:if (mSettingsStore.isWifiToggleEnabled()) {if (doDeferEnable(msg)) {if (mHaveDeferredEnable) {//  have 2 toggles now, inc serial number and ignore bothmDeferredEnableSerialNumber++;}mHaveDeferredEnable = !mHaveDeferredEnable;break;}transitionTo(mDeviceActiveState);} else if (checkScanOnlyModeAvailable()) {// only go to scan mode if we aren't in airplane modeif (mSettingsStore.isAirplaneModeOn()) {transitionTo(mStaDisabledWithScanState);}}break;........}}/*** Parent: StaEnabledState** TODO (b/79209870): merge DeviceActiveState and StaEnabledState into a single state*/class DeviceActiveState extends State {@Overridepublic void enter() {mWifiStateMachinePrime.enterClientMode();mWifiStateMachine.setHighPerfModeEnabled(false);}@Overridepublic boolean processMessage(Message msg) {if (msg.what == CMD_USER_PRESENT) {// TLS networks can't connect until user unlocks keystore. KeyStore// unlocks when the user punches PIN after the reboot. So use this// trigger to get those networks connected.if (mFirstUserSignOnSeen == false) {mWifiStateMachine.reloadTlsNetworksAndReconnect();}mFirstUserSignOnSeen = true;return HANDLED;} else if (msg.what == CMD_RECOVERY_RESTART_WIFI) {final String bugTitle;final String bugDetail;if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];bugTitle = "Wi-Fi BugReport: " + bugDetail;} else {bugDetail = "";bugTitle = "Wi-Fi BugReport";}if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {(new Handler(mWifiStateMachineLooper)).post(() -> {mWifiStateMachine.takeBugReport(bugTitle, bugDetail);});}return NOT_HANDLED;}return NOT_HANDLED;}}

进入DeviceActiveState,执行enter()函数,执行enterClientMode,给ModeStateMachine状态机发送CMD_START_CLIENT_MODE消息。

    /*** Method to switch wifi into client mode where connections to configured networks will be* attempted.*/public void enterClientMode() {changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);}private class ModeStateMachine extends StateMachine {........private boolean checkForAndHandleModeChange(Message message) {switch(message.what) {case ModeStateMachine.CMD_START_CLIENT_MODE:Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");mModeStateMachine.transitionTo(mClientModeActiveState);break;case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");mModeStateMachine.transitionTo(mScanOnlyModeActiveState);break;case ModeStateMachine.CMD_DISABLE_WIFI:Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");mModeStateMachine.transitionTo(mWifiDisabledState);break;default:return NOT_HANDLED;}return HANDLED;}........}

6. 进入ClientModeActiveState,执行enter()函数,WifiInjector创建一个 ClientModeManager,并start。

    class ClientModeActiveState extends ModeActiveState {@Overridepublic void enter() {Log.d(TAG, "Entering ClientModeActiveState");mListener = new ClientListener();mManager = mWifiInjector.makeClientModeManager(mListener);mManager.start();mActiveModeManagers.add(mManager);updateBatteryStatsWifiState(true);}}
    /*** Create a ClientModeManager** @param listener listener for ClientModeManager state changes* @return a new instance of ClientModeManager*/public ClientModeManager makeClientModeManager(ClientModeManager.Listener listener) {return new ClientModeManager(mContext, mWifiStateMachineHandlerThread.getLooper(),mWifiNative, listener, mWifiMetrics, mScanRequestProxy, mWifiStateMachine);}

ClientModeManager 中去启动客户端模式,发送消息CMD_START,进去到IdleState继续处理,处理完毕进入StartedState。

    /*** Start client mode.*/public void start() {mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);}
        private class IdleState extends State {@Overridepublic void enter() {Log.d(TAG, "entering IdleState");mClientInterfaceName = null;mIfaceIsUp = false;}@Overridepublic boolean processMessage(Message message) {switch (message.what) {case CMD_START:updateWifiState(WifiManager.WIFI_STATE_ENABLING,WifiManager.WIFI_STATE_DISABLED);mClientInterfaceName = mWifiNative.setupInterfaceForClientMode(false /* not low priority */, mWifiNativeInterfaceCallback);if (TextUtils.isEmpty(mClientInterfaceName)) {Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,WifiManager.WIFI_STATE_ENABLING);updateWifiState(WifiManager.WIFI_STATE_DISABLED,WifiManager.WIFI_STATE_UNKNOWN);break;}sendScanAvailableBroadcast(false);mScanRequestProxy.enableScanningForHiddenNetworks(false);mScanRequestProxy.clearScanResults();transitionTo(mStartedState);break;default:Log.d(TAG, "received an invalid message: " + message);return NOT_HANDLED;}return HANDLED;}}

7. 接下来看下WifiNative 中setupInterfaceForClientMode,在其中做了HAL部分的启动(包含check驱动是否已经准备ok,HAL部分是否成功启动),之后再进行wpa_supplicant进程的启动,状态检测进程的启动startMonitoring。

    /*** Setup an interface for Client mode operations.** This method configures an interface in STA mode in all the native daemons* (wificond, wpa_supplicant & vendor HAL).** @param lowPrioritySta The requested STA has a low request priority (lower probability of*                       getting created, higher probability of getting destroyed).* @param interfaceCallback Associated callback for notifying status changes for the iface.* @return Returns the name of the allocated interface, will be null on failure.*/public String setupInterfaceForClientMode(boolean lowPrioritySta,@NonNull InterfaceCallback interfaceCallback) {synchronized (mLock) {if (!startHal()) {Log.e(TAG, "Failed to start Hal");mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();return null;}if (!startSupplicant()) {Log.e(TAG, "Failed to start supplicant");mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();return null;}Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA);if (iface == null) {Log.e(TAG, "Failed to allocate new STA iface");return null;}iface.externalListener = interfaceCallback;iface.name = createStaIface(iface, lowPrioritySta);if (TextUtils.isEmpty(iface.name)) {Log.e(TAG, "Failed to create STA iface in vendor HAL");mIfaceMgr.removeIface(iface.id);mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();return null;}if (mWificondControl.setupInterfaceForClientMode(iface.name) == null) {Log.e(TAG, "Failed to setup iface in wificond on " + iface);teardownInterface(iface.name);mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond();return null;}if (!mSupplicantStaIfaceHal.setupIface(iface.name)) {Log.e(TAG, "Failed to setup iface in supplicant on " + iface);teardownInterface(iface.name);mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();return null;}iface.networkObserver = new NetworkObserverInternal(iface.id);if (!registerNetworkObserver(iface.networkObserver)) {Log.e(TAG, "Failed to register network observer on " + iface);teardownInterface(iface.name);return null;}mWifiMonitor.startMonitoring(iface.name);// Just to avoid any race conditions with interface state change callbacks,// update the interface state before we exit.onInterfaceStateChanged(iface, isInterfaceUp(iface.name));initializeNwParamsForClientInterface(iface.name);Log.i(TAG, "Successfully setup " + iface);return iface.name;}}

8. 在ClientModeManager中进行状态的更新,广播WiFi状态改变消息。

    /*** Update Wifi state and send the broadcast.* @param newState new Wifi state* @param currentState current wifi state*/private void updateWifiState(int newState, int currentState) {if (!mExpectedStop) {mListener.onStateChanged(newState);} else {Log.d(TAG, "expected stop, not triggering callbacks: newState = " + newState);}// Once we report the mode has stopped/failed any other stop signals are redundant// note: this can happen in failure modes where we get multiple callbacks as underlying// components/interface stops or the underlying interface is destroyed in cleanupif (newState == WifiManager.WIFI_STATE_UNKNOWN|| newState == WifiManager.WIFI_STATE_DISABLED) {mExpectedStop = true;}if (newState == WifiManager.WIFI_STATE_UNKNOWN) {// do not need to broadcast failure to systemreturn;}mWifiStateMachine.setWifiStateForApiCalls(newState);final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);}

9. 系统设置在WifiEnabler中会监听WiFi的状态。

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {handleWifiStateChanged(mWifiManager.getWifiState());} else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {if (!mConnected.get()) {handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState)intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));}} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);mConnected.set(info.isConnected());handleStateChanged(info.getDetailedState());}}};

三、WiFi关闭流程

与打开流程一样,只是在WifiController中收到CMD_WIFI_TOGGLED消息,会从StaEnabledState切换到mStaDisabledState(如果没有启用wifi_scan_always_enabled功能)。

    class StaDisabledState extends State {private int mDeferredEnableSerialNumber = 0;private boolean mHaveDeferredEnable = false;private long mDisabledTimestamp;@Overridepublic void enter() {mWifiStateMachinePrime.disableWifi();// Supplicant can't restart right away, so note the time we switched offmDisabledTimestamp = SystemClock.elapsedRealtime();mDeferredEnableSerialNumber++;mHaveDeferredEnable = false;mWifiStateMachine.clearANQPCache();}

会调用WifiStateMachinePrime.disableWifi() 发送消息CMD_DISABLE_WIFI 。

    /*** Method to disable wifi in sta/client mode scenarios.** This mode will stop any client/scan modes and will not perform any network scans.*/public void disableWifi() {changeMode(ModeStateMachine.CMD_DISABLE_WIFI);}

WifiStateMachinePrime中则会由ClientModeActiveState切换到WifiDisabled,会执行ClientModeActiveState的exit()函数,其调用ModeActiveState的exit(),调用mManager.stop(),最终会将wifi最新状态设置到WifiStateMachine。

        class ModeActiveState extends State {ActiveModeManager mManager;@Overridepublic boolean processMessage(Message message) {// handle messages for changing modes herereturn NOT_HANDLED;}@Overridepublic void exit() {// Active states must have a mode manager, so this should not be null, but it isn't// obvious from the structure - add a null check here, just in case this is missed// in the futureif (mManager != null) {mManager.stop();mActiveModeManagers.remove(mManager);}updateBatteryStatsWifiState(false);}}
        private boolean checkForAndHandleModeChange(Message message) {switch(message.what) {case ModeStateMachine.CMD_START_CLIENT_MODE:Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");mModeStateMachine.transitionTo(mClientModeActiveState);break;case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");mModeStateMachine.transitionTo(mScanOnlyModeActiveState);break;case ModeStateMachine.CMD_DISABLE_WIFI:Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");mModeStateMachine.transitionTo(mWifiDisabledState);break;default:return NOT_HANDLED;}return HANDLED;}

ClientModeManager中也会从StartedState切换到IdleState,在启exit()函数中调用mWifiNative.teardownInterface。

        private class StartedState extends State {private void onUpChanged(boolean isUp) {if (isUp == mIfaceIsUp) {return;  // no change}mIfaceIsUp = isUp;if (isUp) {Log.d(TAG, "Wifi is ready to use for client mode");sendScanAvailableBroadcast(true);mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE,mClientInterfaceName);updateWifiState(WifiManager.WIFI_STATE_ENABLED,WifiManager.WIFI_STATE_ENABLING);} else {if (mWifiStateMachine.isConnectedMacRandomizationEnabled()) {// Handle the error case where our underlying interface went down if we// do not have mac randomization enabled (b/72459123).return;}// if the interface goes down we should exit and go back to idle state.Log.d(TAG, "interface down!");updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,WifiManager.WIFI_STATE_ENABLED);mStateMachine.sendMessage(CMD_INTERFACE_DOWN);}}/*** Clean up state, unregister listeners and update wifi state.*/@Overridepublic void exit() {mWifiStateMachine.setOperationalMode(WifiStateMachine.DISABLED_MODE, null);if (mClientInterfaceName != null) {mWifiNative.teardownInterface(mClientInterfaceName);mClientInterfaceName = null;mIfaceIsUp = false;}updateWifiState(WifiManager.WIFI_STATE_DISABLED,WifiManager.WIFI_STATE_DISABLING);// once we leave started, nothing else to do...  stop the state machinemStateMachine.quitNow();}}

WifiNactive中后续则会调用到mWifiMonitor.stopMonitoring(),stopSupplicantIfNecessary(),stopHalAndWificondIfNecessary(),去停止监测,停止wpa_supplicant,停止hal服务等。

    /** Helper method invoked to teardown client iface and perform necessary cleanup */private void onClientInterfaceDestroyed(@NonNull Iface iface) {synchronized (mLock) {mWifiMonitor.stopMonitoring(iface.name);if (!unregisterNetworkObserver(iface.networkObserver)) {Log.e(TAG, "Failed to unregister network observer on " + iface);}if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) {Log.e(TAG, "Failed to teardown iface in supplicant on " + iface);}if (!mWificondControl.tearDownClientInterface(iface.name)) {Log.e(TAG, "Failed to teardown iface in wificond on " + iface);}stopSupplicantIfNecessary();stopHalAndWificondIfNecessary();}}

Android 的WiFi打开关闭流程大致如此,不足之处后续会继续完善。

Android WiFi 打开关闭流程相关推荐

  1. 如何在Android中打开/关闭相机LED /手电筒

    在本教程中,我们向您展示如何在Android中打开/关闭手机摄像头或手电筒. 查看代码段: 1.开启 camera = Camera.open();Parameters p = camera.getP ...

  2. Android 蓝牙开关打开enable流程--framework层---全网最详细

    同学,别退出呀,我可是全网最牛逼的 Android 蓝牙分析博主,我写了上百篇蓝牙文章,请点击下面了解本专栏,进入本博主主页看看再走呗,一定不会让你后悔的,记得一定要去看主页置顶文章哦.   本文详细 ...

  3. Android wifi PNO扫描流程(Andriod O)

    版权声明:本文为博主原创文章,博客地址:https://blog.csdn.net/h784707460/article/details/79702275,未经博主允许不得转载. 一. Android ...

  4. Android WIFI认证的流程

    一.背景介绍   当用户打开wifi,扫描完成之后,当用户点击AP列表中一项并输入正确的密码后,就可以开始AP的连接过程了.点击连接到最终连接成功,这个过程中具体流程是如何实现的,这篇文章,将介绍一下 ...

  5. 安卓9[Android P]打开/关闭热点

    前言 网络相关工具库方法 功能 1.打开热点 代码如下(示例): /*** 打开WiFi热点* @param context*/public static void startTethering(Co ...

  6. 判断WIFI打开关闭,飞行模式打开关闭

    //飞行模式打开与关闭 int isAirplaneMode = Settings.System.getInt(context.getContentResolver(),Settings.System ...

  7. Appium操作手机网络设置(打开关闭wifi,打开关闭数据流量)

    注:Appium并不是所有的手机都能打开数据关闭数据流量成功,打开wifi有确认按钮,需要自己设置 下面放代码 # /usr/bin/env python # -*- coding: utf-8 -* ...

  8. Android Audio打开输出设备流程(十五)

    android audio 生产者与消费者 简介 全面接触生产者/消费者问题是在操作系统原理中,并发性原理讨论的问题 生产者/消费者问题.最近的工作偏向音频,接着上一篇文章,用生产者,消费者模型来理解 ...

  9. android开发打开wifi密码,【Android开发】wifi开关与wifi连接(密码连接)

    过放荡不羁的生活,容易得像顺水推舟,但是要结识良朋益友,却难如登天.-- 巴尔扎克 本文demo来自网络,找了好久找到的,后面自己做了些许修改,这里对源码解析,愧于忘记哪里出来了,感谢作者! 接下来就 ...

最新文章

  1. mvc ajax提交html标签,asp.net-mvc – 如何使用ajax get或post在带有参数的mvc中将数据从View传递到Controller...
  2. (已解决)AttributeError: ‘PrecisionRecallDisplay‘ has no attribute ‘from_predictions‘以及查看sklearn版本
  3. 汽车票销售系统mysql,jsp683客运汽车票网上售票系统mysql
  4. 日常工作问题的处理流程
  5. Python的位置参数、默认参数、关键字参数、可变参数之间的区别
  6. 前端学习(3321):瀑布流的方式演示
  7. python入门之玩转列表我的菜单_我的Python成长之路---第一天---Python基础(作业2:三级菜单)---2015年12月26日(雾霾)...
  8. Android开发笔记(一)像素的单位
  9. python关键词对联_keras基于CNN和序列标注的对联机器人
  10. Linux开机过程(转)
  11. 自动化测试如何保持登录状态_自动化测试po模式是什么?自动化测试po分层如何实现?-附详细源码...
  12. 【转载】 ppt如何导出300dpi的高分辨率图片
  13. 影视APP下载页面html源码
  14. pq磁盘分区工具的初次使用
  15. 安全赋能区域数字化转型,普陀区副区长徐树杰带队调研上海控安
  16. 【高德地图POI踩坑】AMap.PlaceSearch无法使用
  17. 超越函数e^(-x^2)在(-∞, +∞)上的定积分的两种解法
  18. KDD2020的一篇序列推荐的论文《Geography-Aware Sequential Location Recommendation》
  19. 使用HttpURLConnection下载网络文件
  20. 配置mybatis时xml出现 URI is not registed / Resource registered by this uri is not recognized 解决方法

热门文章

  1. 微信分享朋友圈链接怎么显示图片-企业微信二维码转成名片
  2. 斩获微软offer后,我总结出这10个面试必备技巧(五星干货)
  3. 在数据库使用期间创建OMF(Oracle Managed Files,Oracle管理的文件)
  4. 变形菌门扩张——肠道微生态失调和炎症肠病的潜在特征
  5. 家居装修行业APP开发解决方案
  6. 洛龙区:加快布局大数据产业
  7. C语言PAT刷题 - 1020 月饼
  8. 联想服务器AR系列,联想正式发布AR一体机:晨星AR
  9. 利用Hbuilder + Android Studio 制作安卓APP
  10. 一个通过SOAP web service驱动ssh/telnet执行命令的小平台