前面我们已经分析了android在进行数据业务拨号前,进行相关准备工作的流程,现在我们可以分析一下整个数据业务长连接拨号在框架部分的流程。
长连接的“长”,是相对于终端进行彩信发送等操作时,建立的临时数据连接而言的(这种临时数据连接在业务执行完毕后,会主动断开),是能够长时间存在的数据连接。
1 CellDataPreference
我们从点击UI界面的数据拨号开关开始分析整个流程。
在原生的Android代码中,数据开关作为设置的一部分,相关的操作定义于CellDataPreference.java中,定义于packages/apps/settings/src/com/android/settings/datausage目录下。
我们看看处理点击操作的performClick函数:
@Override
protected void performClick(View view) {
.............
//开关处于开启状态
if (mChecked) {
//当前subId对应的卡信息(卡需要处于激活状态,即相关信息已经加载)
final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
//默认数据卡对应的卡状态
final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo();
//showSimCardTile判断手机是否支持多卡,支持的话返回true
//整个If的含义就是:仅支持单卡,或者默认数据卡与当前的卡信息一致
if (!Utils.showSimCardTile(getContext()) || (nextSir != null && currentSir != null &&
currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) {
//关闭数据业务(开关处于开启态,再点击一次,变成关闭态)
setMobileDataEnabled(false);
if (nextSir != null && currentSir != null &&
currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
//双卡的情况下,还要关闭另一张卡的数据业务(当前卡为默认数据卡,这里是以防万一)
disableDataForOtherSubscriptions(mSubId);
}
return;
}
............
super.performClick(view);
} else {
//这里是从关到开的过程,多卡的情况
if (Utils.showSimCardTile(getContext())) {
//将标志位置为true
mMultiSimDialog = true;
//调用父类方法;在父类方法中最终将调用子类实现的onClick方法
super.performClick(view);
} else {
//单卡时直接开始拨号
setMobileDataEnabled(true);
}
}
}
从上面的代码我们可以看出,在多卡的情况下,将开关从关闭置为打开,将由CellDataPreference的onClick函数进行处理:
@Override
protected void onClick(DialogInterface dialog, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
return;
}
//在前面的代码中,mMultiSimDialog已经置为true,表示手机支持多卡
if (mMultiSimDialog) {
//将当前CellDataPreference对应卡设为默认数据卡
mSubscriptionManager.setDefaultDataSubId(mSubId);
//开始数据拨号
setMobileDataEnabled(true);
//关闭另一张卡的数据业务
disableDataForOtherSubscriptions(mSubId);
} else {
// TODO: extend to modify policy enabled flag.
setMobileDataEnabled(false);
}
}
private void setMobileDataEnabled(boolean enabled) {
if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled(" + enabled + "," + mSubId + ")");
//调用TelephonyManager的接口
mTelephonyManager.setDataEnabled(mSubId, enabled);
//更改界面
setChecked(enabled);
}
2 TelephonyManager
根据上文的代码,我们知道设置界面最终通过调用TelephonyManager开启拨号流程。
//传入参数subId为数据卡对应的subId
//enable为true表示开启数据业务;false表示关闭数据业务
@SystemApi
public void setDataEnabled(int subId, boolean enable) {
try {
Log.d(TAG, "setDataEnabled: enabled=" + enable);
//获取binder代理对象
ITelephony telephony = getITelephony();
if (telephony != null)
//通过binder通信调用接口
telephony.setDataEnabled(subId, enable);
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setDataEnabled", e);
}
}
private ITelephony getITelephony() {
//Context.TELEPHONY_SERVICE对应字符串"phone"
return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}
上面的代码较为简单,唯一值得关注的是找到binder通信对应的服务提供者。
实际上我们在之前的blog中已经提到过了,在PhoneApp启动时会创建PhoneGlobals,而PhoneGlobals会创建PhoneInterfaceManager:
3 PhoneInterfaceManager
.......
phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
......
我们来看看PhoneInterfaceManager的定义:
//可以看到PhoneInterfaceManager继承ITelephony.Stub,与前面呼应起来了
public class PhoneInterfaceManager extends ITelephony.Stub {
.............
static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
//创建PhoneInterfaceManager
sInstance = new PhoneInterfaceManager(app, phone);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
...........
publish();
}
private void publish() {
if (DBG) log("publish: " + this);
//publish服务名为"phone",与前面对应起来了
ServiceManager.addService("phone", this);
}
...........
}
至此,我们知道了TelephonyManager通过Binder通信调用的实际上是PhoneInterfaceManager中的接口。
@Override
public void setDataEnabled(int subId, boolean enable) {
//检查权限
enforceModifyPermission();
//利用subId映射得到phoneId
int phoneId = mSubscriptionController.getPhoneId(subId);
if (DBG) log("getDataEnabled: subId=" + subId + " phoneId=" + phoneId);
//根据phoneId得到PhoneFactory中记录的phone对象
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone != null) {
if (DBG) log("setDataEnabled: subId=" + subId + " enable=" + enable);
//调用phone对象的setDataEnabled方法
phone.setDataEnabled(enable);
} else {
loge("setDataEnabled: no phone for subId=" + subId);
}
}
4 GsmCdmaPhone
与之前的版本不一样,Android 7.0中新增了GsmCdmaPhone对象,并且将Phone变成了一个抽象类,新增了PhoneInternaInterface接口:
public abstract class Phone extends Handler implements PhoneInternalInterface {
...........
}
public class GsmCdmaPhone extends Phone {
...........
}
这种继承结构的变化带来的影响,自己目前也还没有深入研究,今后有流程涉及时,再做进一步的分析。
我们目前还是关注数据拨号流程:
@Override
public void setDataEnabled(boolean enable) {
mDcTracker.setDataEnabled(enable);
}
可以看到在拨号流程中,GsmCdmaPhone的工作很简单,直接调用DcTracker的接口即可。
5 DcTracker
在Android 7.0中,去掉了DcTrackerBase对象,DcTracker直接继承Handler。
public class DcTracker extends Handler {
.......
public void setDataEnabled(boolean enable) {
Message msg = obtainMessage(DctConstants.CMD_SET_USER_DATA_ENABLE);
msg.arg1 = enable ? 1 : 0;
if (DBG) log("setDataEnabled: sendMessage: enable=" + enable);
//发送消息给自己,将调用onSetUserDataEnabled进行处理
sendMessage(msg);
}
........
private void onSetUserDataEnabled(boolean enabled) {
synchronized (mDataEnabledLock) {
//新设定的状态,与旧状态不一样时,才需要继续处理
if (mUserDataEnabled != enabled) {
mUserDataEnabled = enabled;
// 更新数据库,注意到单、双卡更新字段的区别
if (TelephonyManager.getDefault().getSimCount() == 1) {
Settings.Global.putInt(mResolver, Settings.Global.MOBILE_DATA, enabled ? 1 : 0);
} else {
int phoneSubId = mPhone.getSubId();
Settings.Global.putInt(mResolver, Settings.Global.MOBILE_DATA + phoneSubId,
enabled ? 1 : 0);
}
//根据系统属性判断终端是否允许在漫游状态使用数据业务
if (getDataOnRoamingEnabled() == false &&
mPhone.getServiceState().getDataRoaming() == true) {
if (enabled) {
//仅为不可用的APN发送通知
notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
} else {
notifyOffApnsOfAvailability(Phone.REASON_DATA_DISABLED);
}
}
if (enabled) {
//开启数据业务时,调用该函数
onTrySetupData(Phone.REASON_DATA_ENABLED);
} else {
//关闭数据业务时,调用该函数
onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
}
}
}
}
..............
}
onTrySetupData的内容较为简单,直接调用setupDataOnConnectableApns:
private boolean onTrySetupData(String reason) {
if (DBG) log("onTrySetupData: reason=" + reason);
//顾名思义,将利用可连接的APN进行拨号
setupDataOnConnectableApns(reason);
return true;
}
private void setupDataOnConnectableApns(String reason) {
//这里RetryFailures.ALWAYS表示连网失败话,会一直重试
setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);
}
private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
...............
//介绍Phone拨号前的准备工作时,我们已经已经mPrioritySortedApnContexts是通过解析xml文件形成的
for (ApnContext apnContext : mPrioritySortedApnContexts) {
//如果apnContext之前用过,不处于Idle态(apnContext初始时处于Idle态),那么按需释放对应的数据连接,这一部分我们目前不用太关注
......................
//注意到apnContext的isConnectable返回true时,拨号流程才能继续下去
if (apnContext.isConnectable()) {
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
//首次使用的ApnContext, waitingApns为null
trySetupData(apnContext, waitingApns);
}