WifiSettings源码分析一
来源:互联网 发布:张翰身材知乎 编辑:程序博客网 时间:2024/06/05 03:28
wifi的入口Activity:WifiSettings.java
进入wifi界面首先显示的是wifi关闭的页面(wifi默认关闭)
接着打开开关,开始扫面附近可用热点
搜索到热点就以列表的形式显示出来,我们可对可用的wifi进行连接、保存、添加、删除等操作。
那么这一系列的动作在代码中是如何体现出来的呢?看代码分析,。
1、onCreate()
@Overridepublic void onCreate(Bundle icicle) { super.onCreate(icicle); //设置布局文件 addPreferencesFromResource(R.xml.wifi_settings); mUserBadgeCache = new UserBadgeCache(getPackageManager()); //启动线程 mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); mBgThread.start();}
2、onActivityCreated()
@Overridepublic void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //创建wifitracker对象,设置扫描到wifi后系统发送的广播的action mWifiTracker = new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false); //获取wifi管理器,管理wifi的开启、关闭、扫描、建立连接、配置等信息 mWifiManager = mWifiTracker.getManager(); //wifi连接监听 mConnectListener = new WifiManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { Activity activity = getActivity(); if (activity != null) { Toast.makeText(activity, R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT).show(); } } }; //wifi保存监听(右上角menu:Saved Networks) mSaveListener = new WifiManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { Activity activity = getActivity(); if (activity != null) { Toast.makeText(activity, R.string.wifi_failed_save_message, Toast.LENGTH_SHORT).show(); } } }; //忘记网络监听 mForgetListener = new WifiManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { Activity activity = getActivity(); if (activity != null) { Toast.makeText(activity, R.string.wifi_failed_forget_message, Toast.LENGTH_SHORT).show(); } } }; if (savedInstanceState != null) { mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE); mDlgModify = savedInstanceState.getBoolean(SAVE_DIALOG_MODIFY_MODE); if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); } if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) { mWifiNfcDialogSavedState = savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE); } } // if we're supposed to enable/disable the Next button based on our current connection // state, start it off in the right state Intent intent = getActivity().getIntent(); mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); if (mEnableNextOnConnection) { if (hasNextButton()) { final ConnectivityManager connectivity = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity != null) { NetworkInfo info = connectivity.getNetworkInfo( ConnectivityManager.TYPE_WIFI); changeNextButtonState(info.isConnected()); } } } //初始化空的视图 mEmptyView = initEmptyView(); //注册上下文菜单(相当于ListView的长按事件) registerForContextMenu(getListView()); //在Fragment中给action bar添加action在onCreate()中调用 setHasOptionsMenu(true)。Android Framework会调用Fragement中的onCreateOptionsMenu()来向Activity的Menu中添加item。 setHasOptionsMenu(true); if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); onAccessPointsChanged(); }}
在OnActivityCreate()函数中首先是创建了WifiTracker对象,经过分析发现这个类是负责启动wifi扫描附近可用热点的类,在启动扫描前进行了广播动作的设置。但还木有真正的注册广播接收者。
@VisibleForTestingWifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans, boolean includePasspoints, WifiManager wifiManager, Looper currentLooper) { if (!includeSaved && !includeScans) { throw new IllegalArgumentException("Must include either saved or scans"); } mContext = context; if (currentLooper == null) { // When we aren't on a looper thread, default to the main. currentLooper = Looper.getMainLooper(); } mMainHandler = new MainHandler(currentLooper); mWorkHandler = new WorkHandler( workerLooper != null ? workerLooper : currentLooper); mWifiManager = wifiManager; mIncludeSaved = includeSaved; mIncludeScans = includeScans; mIncludePasspoints = includePasspoints; mListener = wifiListener; // check if verbose logging has //检查已打开或关闭的详细日志记录 been turned on or off sVerboseLogging = mWifiManager.getVerboseLoggingLevel(); mFilter = new IntentFilter(); //wifi状态改变(wifi打开、关闭等改变时会发送广播) mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); //扫描结束时发送的广播 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); //网络ID改变 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); //正在连接的状态改变,比如添加删除网络 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); //网络配置改变 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); //链接配置改变 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); //网络状态改变 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); //Received Signal Strength Indication接收的信号强度指示;无线发送层的可选部分;用来判定链接质量;以及是否增大广播发送强度。 //信号强度改变 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);}
创建mMainHandler
private final class MainHandler extends Handler { private static final int MSG_CONNECTED_CHANGED = 0; private static final int MSG_WIFI_STATE_CHANGED = 1; private static final int MSG_ACCESS_POINT_CHANGED = 2; private static final int MSG_RESUME_SCANNING = 3; private static final int MSG_PAUSE_SCANNING = 4; public MainHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (mListener == null) { return; } switch (msg.what) { //连接状态发生变化 case MSG_CONNECTED_CHANGED: mListener.onConnectedChanged(); break; //WiFi状态发生变化 case MSG_WIFI_STATE_CHANGED: mListener.onWifiStateChanged(msg.arg1); break; //接入点发生变化 case MSG_ACCESS_POINT_CHANGED: mListener.onAccessPointsChanged(); break; //扫描开始,扫描附近可用wifi case MSG_RESUME_SCANNING: if (mScanner != null) { mScanner.resume(); } break; //扫描暂停 case MSG_PAUSE_SCANNING: if (mScanner != null) { mScanner.pause(); } break; } }}
创建WorkHandler
private final class WorkHandler extends Handler { private static final int MSG_UPDATE_ACCESS_POINTS = 0; private static final int MSG_UPDATE_NETWORK_INFO = 1; private static final int MSG_RESUME = 2; public WorkHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { //更新接入点 case MSG_UPDATE_ACCESS_POINTS: updateAccessPoints(); break; //更新网络信息 case MSG_UPDATE_NETWORK_INFO: updateNetworkInfo((NetworkInfo) msg.obj); break; case MSG_RESUME: //清空扫描的结果缓存集合、接入点地址、id。 handleResume(); break; } }}
3、onViewCreate()
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final Activity activity = getActivity(); if (activity != null) { mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header); }}
4、onStart()
@Overridepublic void onStart() { super.onStart(); // On/off switch is hidden for Setup Wizard (returns null) mWifiEnabler = createWifiEnabler();}
WifiEnabler.java这个类是负责wifi开关(SwitchBar)的设置,监听SwitchBar的状态:打开、正在打开、正在关闭、关闭等状态并随着状态的改变更新UI。
WifiSettings.java调用createWifiEnabler(),创建WifiEnabler对象。
/** * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard) *//* package */ WifiEnabler createWifiEnabler() { final SettingsActivity activity = (SettingsActivity) getActivity(); return new WifiEnabler(activity, activity.getSwitchBar());}
WifiEnabler.java的构造方法:
public WifiEnabler(Context context, SwitchBar switchBar) { mContext = context; mSwitchBar = switchBar; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); // The order matters! We really should not depend on this. :( mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); setupSwitchBar();}
从代码中可以看出这里取得了Wifi管理器,注册广播监听wifi状态、网络状态的变化,初始化了SwitchBar开关。
public void setupSwitchBar() { //获得wifi state final int state = mWifiManager.getWifiState(); //根据wifi state 设置switchbar的打开或关闭。 handleWifiStateChanged(state); if (!mListeningToOnSwitchChange) { mSwitchBar.addOnSwitchChangeListener(this); mListeningToOnSwitchChange = true; } //显示开关 mSwitchBar.show();}
取出wifi的状态,根据wifi的状态来设置SwitchBar,给SwitchBar注册监听器,监听开关的变化。
private void handleWifiStateChanged(int state) { switch (state) { //wifi正在打开 case WifiManager.WIFI_STATE_ENABLING: mSwitchBar.setEnabled(false); break; //wifi已经打开 case WifiManager.WIFI_STATE_ENABLED: setSwitchBarChecked(true); mSwitchBar.setEnabled(true); updateSearchIndex(true); break; //wifi正在关闭 case WifiManager.WIFI_STATE_DISABLING: mSwitchBar.setEnabled(false); break; //wifi已经关闭 case WifiManager.WIFI_STATE_DISABLED: setSwitchBarChecked(false); mSwitchBar.setEnabled(true); updateSearchIndex(false); break; default: setSwitchBarChecked(false); mSwitchBar.setEnabled(true); updateSearchIndex(false); }}
updateSearchIndex()
private void updateSearchIndex(boolean isWiFiOn) { mHandler.removeMessages(EVENT_UPDATE_INDEX); Message msg = new Message(); msg.what = EVENT_UPDATE_INDEX; msg.getData().putBoolean(EVENT_DATA_IS_WIFI_ON, isWiFiOn); mHandler.sendMessage(msg);}private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_UPDATE_INDEX: //取出wifi打开或关闭的标识 final boolean isWiFiOn = msg.getData().getBoolean(EVENT_DATA_IS_WIFI_ON); Index.getInstance(mContext).updateFromClassNameResource( WifiSettings.class.getName(), true, isWiFiOn); break; } }};
5、onResume()
在onStart()函数中SwitchBar已经初始化好了,UI界面也按照SwitchBar的状态显示了,那么可以开始根据SwitchBar的开关状态来决定是否启动wifi扫描附近可用的热点。接下来一步一步分析具体点过程。
@Overridepublic void onResume() { final Activity activity = getActivity(); super.onResume(); //Needed so PreferenceGroupAdapter allows AccessPointPreference to be recycled. Removed in onResume removePreference("dummy"); if (mWifiEnabler != null) { mWifiEnabler.resume(activity); } mWifiTracker.startTracking();}
5.1、首先,判断wifienable是否为空,如果不为空则调用WifiEnabler.java的resume()函数。
public void resume(Context context) { mContext = context; // Wi-Fi state is sticky, so just let the receiver update UI mContext.registerReceiver(mReceiver, mIntentFilter); if (!mListeningToOnSwitchChange) { mSwitchBar.addOnSwitchChangeListener(this); mListeningToOnSwitchChange = true; }}
5.2、其次,注册广播接收者,这个就是在WifiEnabler.java在构造方法中准备的系统发送的广播就被注册了。用于接收系统发送的关于wifi、网络状态的变化的信息。
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //wifi状态改变 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { handleWifiStateChanged(intent.getIntExtra( WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } //正在创建连接状态发生变化 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()); } }};
状态发生改变时调用的更新方法
private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) { // After the refactoring from a CheckBoxPreference to a Switch, this method is useless since // there is nowhere to display a summary. // This code is kept in case a future change re-introduces an associated text. /* // WifiInfo is valid if and only if Wi-Fi is enabled. // Here we use the state of the switch as an optimization. if (state != null && mSwitch.isChecked()) { WifiInfo info = mWifiManager.getConnectionInfo(); if (info != null) { //setSummary(Summary.get(mContext, info.getSSID(), state)); } } */}
这个方法已经被注释掉,那就不做分析了。
5.3、接着是给SwitchBar添加监听事件,监听开关的状态。开关被触发回调onSwitchChanged()函数
@Overridepublic void onSwitchChanged(Switch switchView, boolean isChecked) { //Do nothing if called as a result of a state machine event if (mStateMachineEvent) { return; } //提示在飞行模式下wifi不能打开 if (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/listenenr loop. mSwitchBar.setChecked(false); return; } // Disable tethering if enabling Wifi //如果wifi正在打开禁止热点共享 int wifiApState = mWifiManager.getWifiApState(); if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { //调用WifiManager的方法实际上是继续调用了WifiService.java类的setWifiApEnabled()方法关闭热点 mWifiManager.setWifiApEnabled(null, false); } MetricsLogger.action(mContext, isChecked ? MetricsLogger.ACTION_WIFI_ON : MetricsLogger.ACTION_WIFI_OFF); if (!mWifiManager.setWifiEnabled(isChecked)) { // Error mSwitchBar.setEnabled(true); Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show(); }}
5.4、最后调用了WifiTracker.java的startTracking()函数。那就到WifiTracker.java中分析扫描到显示的过程。
/** * Start tracking wifi networks. * Registers listeners and starts scanning for wifi networks. If this is not called * then forceUpdate() must be called to populate getAccessPoints(). */public void startTracking() { resumeScanning(); if (!mRegistered) { mContext.registerReceiver(mReceiver, mFilter); mRegistered = true; }}
5.4.1、当扫描暂停时,重新恢复扫描状态,扫描可用wifi
/** * Resume scanning for wifi networks after it has been paused. */public void resumeScanning() { //判断如果扫描器对象为空,则创建新的扫描器对象 if (mScanner == null) { mScanner = new Scanner(); } mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME); //判断wifi是否打开 if (mWifiManager.isWifiEnabled()) { //SwitchBar打开则开始扫描附近可用wifi mScanner.resume(); } mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);}
WorkHandler.MSG_RESUME:清空扫描结果缓存的集合、接入点地址集合、id
private void handleResume() { mScanResultCache.clear(); //接入点地址 mSeenBssids.clear(); mScanId = 0;}
Scanner.resmue()启动扫描,发送消息
@Overridepublic void handleMessage(Message message) { if (message.what != MSG_SCAN) return; if (mWifiManager.startScan()) { mRetry = 0; } else if (++mRetry >= 3) {//如果扫描次数操过三次,提示扫描失败,停止扫描 mRetry = 0; if (mContext != null) { Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); } return; } //每隔10秒,发起扫描的操作 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);}
WorkHandler.MSG_UPDATE_ACCESS_POINTS:扫描到可用wifi后就更新接入点,把扫描到的接入点缓存到列表中
private void updateAccessPoints() { // Swap the current access points into a cached list. //获取当前接入点列表 List<AccessPoint> cachedAccessPoints = getAccessPoints(); ArrayList<AccessPoint> accessPoints = new ArrayList<>(); /** Lookup table to more quickly update AccessPoints by only considering objects with the * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); WifiConfiguration connectionConfig = null; if (mLastInfo != null) { //获取wifi配置的网络id connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); } final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); //抓取扫描结果返回一个扫描结果集合。 final Collection<ScanResult> results = fetchScanResults(); // Clear out the configs so we don't think something is saved when it isn't. //清空缓存的接入点列表 for (AccessPoint accessPoint : cachedAccessPoints) { accessPoint.clearConfig(); } //如果wifi配置 if (configs != null) { mSavedNetworksExist = configs.size() != 0; for (WifiConfiguration config : configs) { if (config.selfAdded && config.numAssociation == 0) { continue; } AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints); if (mLastInfo != null && mLastNetworkInfo != null) { if (config.isPasspoint() == false) { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } } else { accessPoint.update(config); } if (mIncludeSaved) { if (!config.isPasspoint() || mIncludePasspoints) accessPoints.add(accessPoint); if (config.isPasspoint() == false) { apMap.put(accessPoint.getSsidStr(), accessPoint); if (results != null) { boolean inScanResults = false; for (ScanResult result : results) { if (accessPoint.matches(result)) { inScanResults = true; break; } } if (!inScanResults) { // If the ap is not in scan results, clear its rssi accessPoint.clearRssi(); } } } } else { // If we aren't using saved networks, drop them into the cache so that // we have access to their saved info. cachedAccessPoints.add(accessPoint); } } } if (results != null) { for (ScanResult result : results) { // Ignore hidden and ad-hoc networks. if (result.SSID == null || result.SSID.length() == 0 || result.capabilities.contains("[IBSS]")) { continue; } boolean found = false; for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { if (accessPoint.update(result)) { found = true; break; } } if (!found && mIncludeScans) { AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints); if (mLastInfo != null && mLastNetworkInfo != null) { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } if (result.isPasspointNetwork()) { WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result); if (config != null) { accessPoint.update(config); } } if (mLastInfo != null && mLastInfo.getBSSID() != null && mLastInfo.getBSSID().equals(result.BSSID) && connectionConfig != null && connectionConfig.isPasspoint()) { /* This network is connected via this passpoint config */ /* SSID match is not going to work for it; so update explicitly */ accessPoint.update(connectionConfig); } accessPoints.add(accessPoint); apMap.put(accessPoint.getSsidStr(), accessPoint); } } } // Pre-sort accessPoints to speed preference insertion Collections.sort(accessPoints); // Log accesspoints that were deleted if (DBG) { // Code is useful only when DBG is true. Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------"); for (AccessPoint prevAccessPoint : mAccessPoints) { if (prevAccessPoint.getSsid() == null) continue; String prevSsid = prevAccessPoint.getSsidStr(); boolean found = false; for (AccessPoint newAccessPoint : accessPoints) { if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) { found = true; break; } } if (!found) if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan"); } Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----"); } mAccessPoints = accessPoints; mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);}
5.4.2、mContext.registerReceiver(mReceiver, mFilter);注册广播接收者,接收发送的wifi相关的信息。
@VisibleForTestingfinal BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //wifi状态改变时更新wifi状态 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } //扫描结果、网络配置(当WiFi列表中的网络添加、更新或者删除)、连接参数发生变化时,更新接入点列表。 else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); } //网络状态发生变化时 else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { //获取网络信息 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( WifiManager.EXTRA_NETWORK_INFO); //设置连接状态 mConnected.set(info.isConnected()); //通知连接状态发生改变,回调onConnectedChanged() mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED); //通知更新接入点列表 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); //通知更新网络信息 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info) .sendToTarget(); } //wifi信号强度发生改变 else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { //通知更新网络信息。 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO); } }};
- WifiSettings源码分析一
- Android 5.1系统源码Wifi模块中wifiSettings源码分析
- Android 5.1系统源码Wifi模块中wifiSettings源码分析
- Android Wi-Fi 系统源码wifiSettings源码分析(Android 5.1 Base)
- quake2源码分析(一)
- Tomcat源码分析(一)
- Lua 源码分析(一)
- Jquery源码分析(一)
- 源码分析(一)
- KUIX源码分析一
- Logcat源码分析(一)
- gSOAP 源码分析(一)
- asihttp 源码分析一
- shopqi源码分析(一)
- VLC源码分析(一)
- pomelo源码分析(一)
- Struts2源码分析一
- tomcat源码分析[一]
- HTTP Status 404错误
- GYM 100971 B.Derangement(水~)
- 中共成立前,上海党组织成员俞秀松
- 【Java】Java集合框架源码和数据结构简要分析——List
- StringUtils.join方法总结
- WifiSettings源码分析一
- ubuntu(linux)下 python2.7 安装tensorflow 0.12和1.0
- css对于文本处理的几个不常用属性
- JSP Servlet中Request与Response所有成员方法的研究
- RxJava操作符
- Jmeter基础
- CREATE FUNCTION/DROP FUNCTION 语法
- Ubuntu 关于gcc/g++版本更换以及编译问题
- 奇怪的比赛