简介
本条目用于记录本人对Android framework侧数据业务的阶段学习总结。
内容包括流程图,代码分析,BUG用例等。
第一阶段学习成果
本阶段主要注重对数据连接设置管理流程的学习,掌握数据业务的基本流程与构造。同时尝试解决部分简单BUG。
数据连接设置管理介绍
通过Setting的常规设置页面中的数据连接开关,打开/关闭数据连接业务。
数据业务设置完成后如何更新到status bar上。
开机后如何自启动数据业务。
这些过程是基本数据业务之一。
数据连接设置管理流程图
数据连接设置管理代码分析
通过Settings控制数据连接开关
Settings.java文件提供动作的入口。通过ConnectivityManager实例的setMobileDataEnable方法设置此开关。
/** * Invoked on each preference click in this hierarchy, overrides * PreferenceActivity's implementation. Used to make sure we track the * preference click events. */ @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { /** TODO: Refactor and get rid of the if's using subclasses */ ... } else if (preference == mButtonDataEnabled) { if (DBG) log("onPreferenceTreeClick: preference == mButtonDataEnabled."); ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); cm.setMobileDataEnabled(mButtonDataEnabled.isChecked()); return true; ... } |
这个方法实际上调用的是ConnectivityService实例的setMobileDataEnable方法。
/** * @see ConnectivityManager#setMobileDataEnabled(boolean) */ public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); if (DBG) log("setMobileDataEnabled(" + enabled + ")"); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, (enabled ? ENABLED : DISABLED), 0)); } |
通过Handler回调机制,我们在handleMessage对消息EVENT_SET_MOBILE_DATA进行处理。
走到了handleSetMobileData方法中。
private void handleSetMobileData(boolean enabled) { if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { if (VDBG) { log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled); } mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled); } if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) { if (VDBG) { log(mNetTrackers[ConnectivityManager.TYPE_WIMAX].toString() + enabled); } mNetTrackers[ConnectivityManager.TYPE_WIMAX].setUserDataEnable(enabled); } } |
该方法根据传入的动作(enable or disable),设置对应的networkStateTracker标志位状态。
起作用的方法也就是MobileDataStateTracker实例的setUserDataEnable方法。
@Override public void setUserDataEnable(boolean enabled) { if (DBG) log("setUserDataEnable: E enabled=" + enabled); final AsyncChannel channel = mDataConnectionTrackerAc; if (channel != null) { channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE, enabled ? DctConstants.ENABLED : DctConstants.DISABLED); mUserDataEnabled = enabled; } if (VDBG) log("setUserDataEnable: X enabled=" + enabled); } |
同样使用了Handler的回调机制,通过回调之后的消息
DctConstants.CMD_SET_USER_DATA_ENABLE
找到对应的在DataConnectionTracker里的Handler处理方法线程。
case DctConstants.CMD_SET_USER_DATA_ENABLE: { final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false; if (DBG) log("CMD_SET_USER_DATA_ENABLE enabled=" + enabled); onSetUserDataEnabled(enabled); break; } |
即回调至方法onSetUserDataEnabled()中
protected void onSetUserDataEnabled(boolean enabled) { synchronized (mDataEnabledLock) { final boolean prevEnabled = getAnyDataEnabled(); if (mUserDataEnabled != enabled) { mUserDataEnabled = enabled; Settings.Global.putInt(mPhone.getContext().getContentResolver(), Settings.Global.MOBILE_DATA, enabled ? 1 : 0); if (getDataOnRoamingEnabled() == false && mPhone.getServiceState().getRoaming() == true) { if (enabled) { notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON); } else { notifyOffApnsOfAvailability(Phone.REASON_DATA_DISABLED); } } if (prevEnabled != getAnyDataEnabled()) { if (!prevEnabled) { resetAllRetryCounts(); onTrySetupData(Phone.REASON_DATA_ENABLED); } else { onCleanUpAllConnections(Phone.REASON_DATA_DISABLED); } } } } } |
这个方法主要可分成两部分理解。
传进来的enable动作影响了mUserDataEnable,同时也作为数据存放到数据库中。
如果这次动作传入有效(prevEnable与AnyDataEnable之间发生变化),
当enabled 为 TRUE(即开启数据连接开关) ,那么就有一次成功的建立链接动作(调用resetAllRetryCounts重置计数器,然后通过onTrySetupData建立链接)。
当enabled 为 FALSE (即关闭数据连接开关),那么就清空所有的链接(调用onCleanUpAllConnections)。
如果成功的话,最终这个动作走入gsmDataConnectionTracker的setupData方法。
通过bringUp方法建立数据链接。
注意带上了what=DctConstants.EVENT_DATA_SETUP_COMPLETE消息的msg方法是当该动作完成后才被回调。具体的建立链接动作进入bringUp中查看。
Message msg = obtainMessage(); msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE; msg.obj = apnContext; dc.bringUp(msg, apn); if (DBG) log("setupData: initing!"); return true; |
进到DataConnection.java中,发现这个方法仍然是个回调处理机制。
/** * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg. * Used for cellular networks that use Acesss Point Names (APN) such * as GSM networks. * * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object. * With AsyncResult.userObj set to the original msg.obj, * AsyncResult.result = FailCause and AsyncResult.exception = Exception(). * @param apn is the Access Point Name to bring up a connection to */ public void bringUp(Message onCompletedMsg, ApnSetting apn) { sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg))); } |
假设这是个建立链接的过程,而不是断开链接的过程。那么实际上走入InactiveState这个case中。
@Override public boolean processMessage(Message msg) { boolean retVal; switch (msg.what) { ... case EVENT_CONNECT: ConnectionParams cp = (ConnectionParams) msg.obj; cp.tag = mTag; if (DBG) { log("DcInactiveState msg.what=EVENT_CONNECT." + "RefCount = " + mRefCount); } mRefCount = 1; onConnect(cp); transitionTo(mActivatingState); retVal = HANDLED; break; } } |
除了上述状态外,EVENT_CONNECT时数据链路的状态还可能是ActivatingState、ActiveState以及DisconnectingState。关于这几种状态之间的转换关系,可以由下图表示(图源:再论android 2.2数据连接过程 - Armily's Tech Blog - 博客频道 - CSDN.NET):
之后进入GsmDataConnection的onCreate()方法。
/** * Begin setting up a data connection, calls setupDataCall * and the ConnectionParams will be returned with the * EVENT_SETUP_DATA_CONNECTION_DONE AsyncResul.userObj. * * @param cp is the connection parameters */ @Override protected void onConnect(ConnectionParams cp) { mApn = cp.apn; if (DBG) log("Connecting to carrier: '" + mApn.carrier + "' APN: '" + mApn.apn + "' proxy: '" + mApn.proxy + "' port: '" + mApn.port); createTime = -1; lastFailTime = -1; lastFailCause = FailCause.NONE; // msg.obj will be returned in AsyncResult.userObj; Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp); msg.obj = cp; int authType = mApn.authType; if (authType == -1) { authType = TextUtils.isEmpty(mApn.user) ? RILConstants.SETUP_DATA_AUTH_NONE : RILConstants.SETUP_DATA_AUTH_PAP_CHAP; } String protocol; if (phone.getServiceState().getRoaming()) { protocol = mApn.roamingProtocol; } else { protocol = mApn.protocol; } phone.mCM.setupDataCall( Integer.toString(getRilRadioTechnology(RILConstants.SETUP_DATA_TECH_GSM)), Integer.toString(mProfileId), mApn.apn, mApn.user, mApn.password, Integer.toString(authType), protocol, msg); } |
看到最后仍然是通过ril的方法setupDataCall以socket方式发送下去。
public void setupDataCall(String radioTechnology, String profile, String apn, String user, String password, String authType, String protocol, Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_SETUP_DATA_CALL, result); rr.mp.writeInt(7); rr.mp.writeString(radioTechnology); rr.mp.writeString(profile); rr.mp.writeString(apn); rr.mp.writeString(user); rr.mp.writeString(password); rr.mp.writeString(authType); rr.mp.writeString(protocol); if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + radioTechnology + " " + profile + " " + apn + " " + user + " " + password + " " + authType + " " + protocol); send(rr); } |
这个就是建立数据链接,从app到ril的流程。
数据连接设置成功后的消息反馈机制
接下来关注消息EVENT_DATA_SETUP_COMPLETE的回调,EVENT_DATA_SETUP_COMPLETE消息在数据连接setupData动作完成时被捕获。
其作用是对本次连接的结果进行处理。
在handleMessage@DataConnectionTracker.java中
case DctConstants.EVENT_DATA_SETUP_COMPLETE: mCidActive = msg.arg1; onDataSetupComplete((AsyncResult) msg.obj); break; |
即回调的处理方法是onDataSetupComplete()@GsmDataConnectionTracker.java。
该方法针对可能出现的连接失效情况进行异常处理或重连。
如果连接建立成功,同时其他设置皆完善的情况下,最后会以notify的方式通知Phone实例数据业务建立成功。
DataConnection dc = apnContext.getDataConnection(); ApnSetting apn = apnContext.getApnSetting(); if (DBG) { log("onDataSetupComplete: success apn=" + (apn == null ? "unknown" : apn.apn)); } if (apn != null && apn.proxy != null && apn.proxy.length() != 0) { try { String port = apn.port; if (TextUtils.isEmpty(port)) port = "8080"; ProxyProperties proxy = new ProxyProperties(apn.proxy, Integer.parseInt(port), null); dcac.setLinkPropertiesHttpProxySync(proxy); } catch (NumberFormatException e) { loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" + apn.port + "): " + e); } } // everything is setup if(TextUtils.equals(apnContext.getApnType(),PhoneConstants.APN_TYPE_DEFAULT)) { SystemProperties.set("gsm.defaultpdpcontext.active", "true"); if (canSetPreferApn && mPreferredApn == null) { if (DBG) log("onDataSetupComplete: PREFERED APN is null"); mPreferredApn = apn; if (mPreferredApn != null) { setPreferredApn(mPreferredApn.id); } } } else { SystemProperties.set("gsm.defaultpdpcontext.active", "false"); } notifyDefaultData(apnContext); |
这个方法最终调用DefaultPhoneNotifier.java实例的doNotifyDataConnection()方法。
如果上层在NetworkController.java里已经注册过对应的消息。
/** * Construct this controller object and register for updates. */ public NetworkController(Context context) { ... // telephony mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); mPhone.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | PhoneStateListener.LISTEN_DATA_ACTIVITY); } |
那么该方法用于相应这些注册消息。
try { mRegistry.notifyDataConnection( convertDataState(state), sender.isDataConnectivityPossible(tmpApnType[0]), reason, sender.getActiveApnHost(tmpApnType[0]), apnType, linkProperties, linkCapabilities, ((telephony!=null) ? telephony.getNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN), roaming); } catch (RemoteException ex) { // system process is dead } |
找到对应实现方法处。
在TelephonyRegistry.java实例的notfiyDataConnection()方法中,有
if (modified) { if (DBG) { Slog.d(TAG, "onDataConnectionStateChanged(" + mDataConnectionState + ", " + mDataConnectionNetworkType + ")"); } for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { try { r.callback.onDataConnectionStateChanged(mDataConnectionState, mDataConnectionNetworkType); } catch (RemoteException ex) { mRemoveList.add(r.binder); } } } handleRemoveListLocked(); } } broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn, apnType, linkProperties, linkCapabilities, roaming); |
注意mRecords.events实际上就是之前在NetworkController.java中注册的消息。如果这些消息存在,同时LISTEN_DATA_CONNECTION_STATE状态正常,那么就回调各自对应的onDataConnectionStateChanged()方法。
这是典型的OBSERVER模式。
最终这个回调的实现在消息所注册的类NetworkController.java中。
@Override public void onDataConnectionStateChanged(int state, int networkType) { if (DEBUG) { Slog.d(TAG, "onDataConnectionStateChanged: state=" + state + " type=" + networkType); } mDataState = state; mDataNetType = networkType; updateDataNetType(); updateDataIcon(); refreshViews(); } |
该方法实现了status bar上对于数据业务图标的显示和更新。
开机后系统自动启动数据连接业务
如果关机前用户已经开启了“数据连接”业务,且未进入飞行模式。
那么重新开机后,系统应该自动进入“数据连接”的连接态。
具体流程如下:
注意到GsmDataConnectionTracker.java中会监听已注册的关于数据连接相关的消息。在handler中进行处理:
switch (msg.what) { case EVENT_RECORDS_LOADED: onRecordsLoaded(); break; case EVENT_DATA_CONNECTION_DETACHED: onDataConnectionDetached(); break; case EVENT_DATA_CONNECTION_ATTACHED: onDataConnectionAttached(); break; case EVENT_DATA_STATE_CHANGED: onDataStateChanged((AsyncResult) msg.obj); break; case EVENT_POLL_PDP: onPollPdp(); break; case EVENT_DO_RECOVERY: doRecovery(); break; case EVENT_APN_CHANGED: onApnChanged(); break; case EVENT_PS_RESTRICT_ENABLED: /** * We don't need to explicitly to tear down the PDP context * when PS restricted is enabled. The base band will deactive * PDP context and notify us with PDP_CONTEXT_CHANGED. * But we should stop the network polling and prevent reset PDP. */ if (DBG) log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted); stopNetStatPoll(); stopDataStallAlarm(); mIsPsRestricted = true; break; case EVENT_PS_RESTRICT_DISABLED: /** * When PS restrict is removed, we need setup PDP connection if * PDP connection is down. */ if (DBG) log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted); mIsPsRestricted = false; if (isConnected()) { startNetStatPoll(); startDataStallAlarm(); } else { // TODO: Should all PDN states be checked to fail? if (mState == State.FAILED) { cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED); resetAllRetryCounts(); mReregisterOnReconnectFailure = false; } trySetupData(Phone.REASON_PS_RESTRICT_ENABLED, Phone.APN_TYPE_DEFAULT); } break; case EVENT_TRY_SETUP_DATA: onTrySetupData(msg); break; case EVENT_CLEAN_UP_CONNECTION: boolean tearDown = (msg.arg1 == 0) ? false : true; if (DBG) log("EVENT_CLEAN_UP_CONNECTION tearDown=" + tearDown); if (msg.obj instanceof ApnContext) { cleanUpConnection(tearDown, (ApnContext)msg.obj); } else { loge("EVENT_CLEAN_UP_CONNECTION request w/o apn context"); } break; case EVENT_RAT_CHANGED: onRatChanged(); break; default: // handle the message in the super class DataConnectionTracker super.handleMessage(msg); break; } |
这些消息被捕获到时,会回调对应的方法。以sim卡成功被Load为例。
将会回调onRecordsLoaded()方法。
private void onRecordsLoaded() { if (DBG) log("onRecordsLoaded: createAllApnList"); createAllApnList(); if (mPhone.mCM.getRadioState().isOn()) { if (DBG) log("onRecordsLoaded: notifying data availability"); notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED); } setupDataOnReadyApns(Phone.REASON_SIM_LOADED); } |
即将相应的reason送给了SetupDataOnReadyApn()方法。
与之类似的,其他消息被回调时,最终都会调用该方法来完成消息的传递。
SetupDataOnReadyApn()方法在确认apnContext无误之后,会调用之前提到的trySetupData()方法。
// Only check for default APN state for (ApnContext apnContext : getPrioritySortedApnContextList().toArray(new ApnContext[0])) { if (apnContext.getState() == State.FAILED) { // By this time, alarms for all failed Apns // should be stopped if any. // Make sure to set the state back to IDLE // so that setup data can happen. apnContext.setState(State.IDLE); } if (apnContext.isReady()) { if (apnContext.getState() == State.IDLE || (apnContext.getState() == State.CONNECTED && apnContext.getDataConnectionAc().getPartialSuccessStatusSync())) { apnContext.setReason(reason); trySetupData(apnContext); } } } |
最终与Settings设置的方式,将对应的消息以socket方式发给rild。
完成开机后即可以自动设置“数据连接”的动作。
至此,整个数据业务从设置到status bar的显示过程完成了。如有误请指出。