1、前言
在前段時間的專案中遇到客戶的設備出現APN斷開的情況沒有自動連接,后來折騰了一段時間解決了這個問題,現在用這篇博客記錄一下APN的選擇和連接流程,
2、名詞決議
- APN:APN指一種網路接入技術,是通過手機上網時必須配置的一個引數,它決定了手機通過哪種接入方式來訪問網路,
對于手機用戶來說,可以訪問的外部網路型別有很多,例如:Internet、WAP網站、集團企業內部網路、行業內部專用網路,而不同的接入點所能訪問的范圍以及接入的方式是不同的,網路側如何知道手機激活以后要訪問哪個網路從而分配哪個網段的IP呢,這就要靠APN來區分了,即APN決定了用戶的手機通過哪種接入方式來訪問什么樣的網路,
- PDN:Packet data network,分組資料網,即常說的Internet,在GPRS網路中代表外部資料網路的總稱
3、Android 中APN的存盤形式
Android 系統中APN是以apns-conf.xml檔案的形式配置的,位于設備中的system/etc目錄下,
apn 的配置資訊如下:
<apn carrier="ENTEL4G" //運營商
mcc="736" //Mobile Country Code,移動國家碼
mnc="02" //Mobile Network Code,移動網路碼
apn="4g.entel" //apn名稱
user=""
password=""
proxy="" //代理
port="" //埠
authtype="2"
type="default,supl" //apn型別
/>
4、初識TelephonyProvider
4.1 apn檔案是怎么被決議的呢,設定中的apn資訊怎么保存的呢?
這里就輪到TelephonyProvider 出場了,
TelephonyProvider繼承自ContentProvider,在android中的代碼路徑為:
packages/providers/TelephonyProvider,
它的UML類圖如下:

在AndroidManifest.xml中可以看到application 標簽中定義了android:process=“com.android.phone” ,這樣TelephonyProvider就運行在com.android.phone行程中,也就是packages\services\Telephony 目錄下的Telephony服務,
4.2 TelephonyProvider 的onCreate()函式
代碼如下:
@Override
public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext());
// Call getReadableDatabase() to make sure onUpgrade is called
if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
// Update APN db on build update
String newBuildId = SystemProperties.get("ro.build.id", null);
if (!TextUtils.isEmpty(newBuildId)) {
// Check if build id has changed
SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,
Context.MODE_PRIVATE);
String oldBuildId = sp.getString(RO_BUILD_ID, "");
if (!newBuildId.equals(oldBuildId)) {
if (DBG) log("onCreate: build id changed from " + oldBuildId + " to " + newBuildId);
// Get rid of old preferred apn shared preferences
SubscriptionManager sm = SubscriptionManager.from(getContext());
if (sm != null) {
List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
for (SubscriptionInfo subInfo : subInfoList) {
SharedPreferences spPrefFile = getContext().getSharedPreferences(
PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
if (spPrefFile != null) {
SharedPreferences.Editor editor = spPrefFile.edit();
editor.clear();
editor.apply();
}
}
}
// Update APN DB
updateApnDb();
} else {
if (VDBG) log("onCreate: build id did not change: " + oldBuildId);
}
sp.edit().putString(RO_BUILD_ID, newBuildId).apply();
} else {
if (VDBG) log("onCreate: newBuildId is empty");
}
if (VDBG) log("onCreate:- ret true");
return true;
}
從上面的代碼,我們知道TelephonyProvider初始化時的主要作業包括:
- new DatabaseHelper 創建出資料庫;
- 根據build_id的值,如果跟之前的不同則重新load apn xml檔案寫入到資料庫中,并將之前選中的sharepreference記錄選中的 apn清除,并且最后將資料中不在apn xml檔案中的資料行全部洗掉,
4.3 TelephonyProvider 的內部類 DatabaseHelper
DatabaseHelper是 TelephonyProvider 的一個內部類,在TelephonyProvider 的onCreate函式中首先被創建,
4.3.1 DatabaseHelper的建構式中會傳入資料的名字用于創建資料庫
這個DATABASE_NAME 就是"telephony.db",創建路徑位于:/data/user_de/0/com.android.providers.telephony/databases/telephony.db
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, getVersion(context));
mContext = context;
}
4.3.2 DatabaseHelper 的onCreate函式
@Override
public void onCreate(SQLiteDatabase db) {
if (DBG) log("dbh.onCreate:+ db=" + db);
createSimInfoTable(db);
createCarriersTable(db, CARRIERS_TABLE);
initDatabase(db);
if (DBG) log("dbh.onCreate:- db=" + db);
}
這里可以看到它作了三件事:
- 1、創建SIM卡資訊的表
- 2、創建運營商資訊的表
- 3、初始化資料庫,這里是重點,
4.3.2 DatabaseHelper 的 initDatabase()初始化作了哪些事
從這個代碼中可以看到函式中主要是:
- 1、使用XML 決議apn-conf.xml檔案并寫入到資料庫中,
- 2、將資料庫中不是xml中的資料清除掉,
private void initDatabase(SQLiteDatabase db) {
if (VDBG) log("dbh.initDatabase:+ db=" + db);
// Read internal APNS data
Resources r = mContext.getResources();
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
int publicversion = -1;
try {
XmlUtils.beginDocument(parser, "apns");
publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
loadApns(db, parser);
} catch (Exception e) {
loge("Got exception while loading APN database." + e);
} finally {
parser.close();
}
// Read external APNS data (partner-provided)
XmlPullParser confparser = null;
File confFile = getApnConfFile();
FileReader confreader = null;
if (DBG) log("confFile = " + confFile);
try {
confreader = new FileReader(confFile);
confparser = Xml.newPullParser();
confparser.setInput(confreader);
XmlUtils.beginDocument(confparser, "apns");
// Sanity check. Force internal version and confidential versions to agree
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
log("initDatabase: throwing exception due to version mismatch");
throw new IllegalStateException("Internal APNS file version doesn't match "
+ confFile.getAbsolutePath());
}
loadApns(db, confparser);
} catch (FileNotFoundException e) {
// It's ok if the file isn't found. It means there isn't a confidential file
// Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
} catch (Exception e) {
loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
e);
} finally {
// Get rid of user/carrier deleted entries that are not present in apn xml file.
// Those entries have edited value USER_DELETED/CARRIER_DELETED.
if (VDBG) {
log("initDatabase: deleting USER_DELETED and replacing "
+ "DELETED_BUT_PRESENT_IN_XML with DELETED");
}
// Delete USER_DELETED
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);
// Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
ContentValues cv = new ContentValues();
cv.put(EDITED, USER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);
// Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
cv = new ContentValues();
cv.put(EDITED, CARRIER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);
if (confreader != null) {
try {
confreader.close();
} catch (IOException e) {
// do nothing
}
}
// Update the stored checksum
setApnConfChecksum(getChecksum(confFile));
}
if (VDBG) log("dbh.initDatabase:- db=" + db);
}
apn的xml 有那些呢 ?根據getApnConfFile 函式可以知道有如下這些目錄,
private File getApnConfFile() {
// Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);
File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
confFile = getNewerFile(confFile, oemConfFile);
confFile = getNewerFile(confFile, updatedConfFile);
return confFile;
}
private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
private static final String OEM_APNS_PATH = "telephony/apns-conf.xml";
private static final String OTA_UPDATED_APNS_PATH = "misc/apns-conf.xml";
private static final String OLD_APNS_PATH = "etc/old-apns-conf.xml";
到這里TelephonyProvider的業務就很清晰了,他就要就是開機的時候會根據build id來判斷是否試正常開機還是升級之后的不同版本,如果不同,則重新創建telephony.db資料庫,并重新決議加載apn.xml檔案寫入到數庫中,
5、開機之后APN界面是怎么自動選擇APN連接的呢 ?
剛開始我以為設定界面開機之后會默認選擇一個已連接的APN的邏輯是在設定里面做的,看了一下設定ApnSettings.java相關的代碼發現并沒有如何選擇apn去撥號的操作,
代碼路徑:packages\apps\Settings\src\com\android\settings\ApnSettings.java
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Activity activity = getActivity();
final int subId = activity.getIntent().getIntExtra(SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mMobileStateFilter = new IntentFilter(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
setIfOnlyAvailableForAdmins(true);
mSubscriptionInfo = SubscriptionManager.from(activity).getActiveSubscriptionInfo(subId);
mUiccController = UiccController.getInstance();
CarrierConfigManager configManager = (CarrierConfigManager)
getSystemService(Context.CARRIER_CONFIG_SERVICE);
PersistableBundle b = configManager.getConfig();
mHideImsApn = b.getBoolean(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL);
mAllowAddingApns = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL);
mUserManager = UserManager.get(activity);
}
如果不是上層應用自動撥號的,那就是底層自動選擇的了 ?帶著疑問我看了一下開機后的log列印流程發現了端倪:在DCTracker的log中發現DcTracker物件被創建后會注冊監聽RILD上報事件,當上報EVENT_DATA_CONNECTION_ATTACHED 就會創建apn串列并且獲取preference apn 進行撥號,當然第一次沒有設定是沒有preference apn 的,這里就從 apn 串列中選擇apn 進行撥號,按著這條線索繼續看一下DCTracker 的代碼邏輯吧,
6、DCTracker 登場
這里先放一張DCTracker 的相關類圖,它是Telephony架構中監聽SIM狀態的狀態變化和撥號的結果,類似于WifiTrakcker或者NetworkMonitor這樣的角色,它是跟隨著com.android.phone行程啟動的時候創建的,不同型別phone 對應著一種DcTracker,

6.1 DcTracker的建構式
代碼路徑:frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
建構式很長,但是總結一下就主要干了這幾件事:
- 1、初始化mUiccController 并注冊監聽SIM狀態,
- 2、registerForAllEvents() 注冊監聽事件,
- 3、監聽資料庫變化以及其他的一些初始化作業,
這里看看registerForAllEvents 監聽事件,
//***** Constructor
public DcTracker(Phone phone) {
super();
mPhone = phone;
if (DBG) log("DCT.constructor");
mResolver = mPhone.getContext().getContentResolver();
mUiccController = UiccController.getInstance();
mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
mAlarmManager =
(AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
mCm = (ConnectivityManager) mPhone.getContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(INTENT_DATA_STALL_ALARM);
filter.addAction(INTENT_PROVISIONING_APN_ALARM);
// TODO - redundent with update call below?
mDataEnabledSettings.setUserDataEnabled(getDataEnabled());
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
mAutoAttachOnCreation.set(sp.getBoolean(Phone.DATA_DISABLED_ON_BOOT_KEY, false));
mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
HandlerThread dcHandlerThread = new HandlerThread("DcHandlerThread");
dcHandlerThread.start();
Handler dcHandler = new Handler(dcHandlerThread.getLooper());
mDcc = DcController.makeDcc(mPhone, this, dcHandler);
mDcTesterFailBringUpAll = new DcTesterFailBringUpAll(mPhone, dcHandler);
mDataConnectionTracker = this;
registerForAllEvents();
update();
mApnObserver = new ApnChangeObserver();
phone.getContext().getContentResolver().registerContentObserver(
Telephony.Carriers.CONTENT_URI, true, mApnObserver);
initApnContexts();
for (ApnContext apnContext : mApnContexts.values()) {
// Register the reconnect and restart actions.
filter = new IntentFilter();
filter.addAction(INTENT_RECONNECT_ALARM + '.' + apnContext.getApnType());
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
}
// Add Emergency APN to APN setting list by default to support EPDN in sim absent cases
initEmergencyApnSetting();
addEmergencyApnSetting();
mProvisionActionName = "com.android.internal.telephony.PROVISION" + phone.getPhoneId();
mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
registerSettingsObserver();
//[FEATURE]-Add-BEGIN by (songzhihao@paxsz.com) 2021/03/30 for network occour problem.
SystemProperties.set(paxRestRadio, "120000");
//[FEATURE]-Add-END by (songzhihao@paxsz.com) 2021/03/30 for network occour problem.
}
6.2 registerForAllEvents監聽事件
這里最重要的還是EVENT_DATA_CONNECTION_ATTACHED 表示modem 注網完成接下來進行撥號的狀態,
private void registerForAllEvents() {
mPhone.mCi.registerForAvailable(this, DctConstants.EVENT_RADIO_AVAILABLE, null);
mPhone.mCi.registerForOffOrNotAvailable(this,
DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
mPhone.mCi.registerForDataNetworkStateChanged(this,
DctConstants.EVENT_DATA_STATE_CHANGED, null);
// Note, this is fragile - the Phone is now presenting a merged picture
// of PS (volte) & CS and by diving into its internals you're just seeing
// the CS data. This works well for the purposes this is currently used for
// but that may not always be the case. Should probably be redesigned to
// accurately reflect what we're really interested in (registerForCSVoiceCallEnded).
mPhone.getCallTracker().registerForVoiceCallEnded(this,
DctConstants.EVENT_VOICE_CALL_ENDED, null);
mPhone.getCallTracker().registerForVoiceCallStarted(this,
DctConstants.EVENT_VOICE_CALL_STARTED, null);
registerServiceStateTrackerEvents();
// SubscriptionManager.registerForDdsSwitch(this,
// DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS, null);
mPhone.mCi.registerForPcoData(this, DctConstants.EVENT_PCO_DATA_RECEIVED, null);
}
public void registerServiceStateTrackerEvents() {
mPhone.getServiceStateTracker().registerForDataConnectionAttached(this,
DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
mPhone.getServiceStateTracker().registerForDataConnectionDetached(this,
DctConstants.EVENT_DATA_CONNECTION_DETACHED, null);
mPhone.getServiceStateTracker().registerForDataRoamingOn(this,
DctConstants.EVENT_ROAMING_ON, null);
mPhone.getServiceStateTracker().registerForDataRoamingOff(this,
DctConstants.EVENT_ROAMING_OFF, null);
mPhone.getServiceStateTracker().registerForPsRestrictedEnabled(this,
DctConstants.EVENT_PS_RESTRICT_ENABLED, null);
mPhone.getServiceStateTracker().registerForPsRestrictedDisabled(this,
DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(this,
DctConstants.EVENT_DATA_RAT_CHANGED, null);
}
6.3 撥號前的準備作業setupDataOnConnectableApns
RILD注網成功之后會上報Attached 事件,也就是DcTracker監聽到EVENT_DATA_CONNECTION_ATTACHED 事件之后會 執行onDataConnectionAttached.
case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
onDataConnectionAttached();
break;
onDataConnectionAttached()函式做一些notify phone 的操作后會執行setupDataOnConnectableApns,這里就開始準備撥號作業了,
這里APN的型別可能有多種,進行遍歷之后清除APN的連接狀態,最后根據APN的狀態判斷如果當前APN的型別是isConnectable就開始呼叫trySetupData(apnContext, waitingApns)正式進入撥號流程了,因為呼叫setupDataOnConnectableApns時傳入的引數是RetryFailures.ALWAYS,所以這里waitingApns 是null,
private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) {
if (VDBG) log("setupDataOnConnectableApns: " + reason);
if (DBG && !VDBG) {
StringBuilder sb = new StringBuilder(120);
for (ApnContext apnContext : mPrioritySortedApnContexts) {
sb.append(apnContext.getApnType());
sb.append(":[state=");
sb.append(apnContext.getState());
sb.append(",enabled=");
sb.append(apnContext.isEnabled());
sb.append("] ");
}
log("setupDataOnConnectableApns: " + reason + " " + sb);
}
for (ApnContext apnContext : mPrioritySortedApnContexts) {
ArrayList<ApnSetting> waitingApns = null;
if (VDBG) log("setupDataOnConnectableApns: apnContext " + apnContext);
if (apnContext.getState() == DctConstants.State.FAILED
|| apnContext.getState() == DctConstants.State.SCANNING) {
if (retryFailures == RetryFailures.ALWAYS) {
apnContext.releaseDataConnection(reason);
} else if (apnContext.isConcurrentVoiceAndDataAllowed() == false &&
mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
// RetryFailures.ONLY_ON_CHANGE - check if voice concurrency has changed
apnContext.releaseDataConnection(reason);
} else {
// RetryFailures.ONLY_ON_CHANGE - check if the apns have changed
int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
ArrayList<ApnSetting> originalApns = apnContext.getWaitingApns();
if (originalApns != null && originalApns.isEmpty() == false) {
waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
if (originalApns.size() != waitingApns.size() ||
originalApns.containsAll(waitingApns) == false) {
apnContext.releaseDataConnection(reason);
} else {
continue;
}
} else {
continue;
}
}
}
if (apnContext.isConnectable()) {
log("isConnectable() call trySetupData");
apnContext.setReason(reason);
trySetupData(apnContext, waitingApns);
}
}
}
6.4 、開始撥號作業trySetupData
trySetupData()函式主要做兩件事
- 1、判斷APN狀態是DctConstants.State.IDLE 的時候呼叫buildWaitingApns 構建撥號APN串列 并通過apnContext.setWaitingApns(waitingApns)將waitingApns串列設定到apnContext 中,
- 2、呼叫setupData(apnContext, radioTech)使用apn進行撥號連接,
if (apnContext.getState() == DctConstants.State.IDLE) {
if (waitingApns == null) {
waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
}
if (waitingApns.isEmpty()) {
notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
String str = "trySetupData: X No APN found retValue=false";
if (DBG) log(str);
apnContext.requestLog(str);
return false;
} else {
apnContext.setWaitingApns(waitingApns);
isReconnectedFinsh = false;
if (DBG) {
log ("trySetupData: Create from mAllApnSettings : "
+ apnListToString(mAllApnSettings)+" isReconnectedFinsh "+isReconnectedFinsh);
}
}
}
6.5、buildWaitingApns分析
buildWaitingApns函式新建的一個WaitingApns集合的APN來源有兩個:
-
1、getPreferredApn()從檔案存盤中去獲取之前選中過的APN,如果該APN 的Type型別為上網的型別并且numeric 國家碼和SIM卡的國家碼一致,就將此apn添加到apn撥號串列中,如果不同就清除保存的preferenceAPN為-1,這樣可以防止換了SIM卡的情況,當然恢復出廠設定或者刷機第一次起來的時候這個preferenceAPN也是NULL的,因為沒有設定過,
-
2、從mAllApnSettings 中遍歷選中APN的type 為撥號相同type的APN 添加到撥號串列中,mAllApnSettings 自動從代碼里面搜一下可以看到它是從資料庫中搜索而來的,根據MMC國家碼來判斷如果和SIM卡相同就添加到mAllApnSettings 串列中,這樣mAllApnSettings 串列中實際上是有很多不同type 比如上網,短信等等型別的apn,
/**
* Build a list of APNs to be used to create PDP's.
*
* @param requestedApnType
* @return waitingApns list to be used to create PDP
* error when waitingApns.isEmpty()
*/
private ArrayList<ApnSetting> buildWaitingApns(String requestedApnType, int radioTech) {
if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
ApnSetting dun = fetchDunApn();
if (dun != null) {
apnList.add(dun);
if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
return apnList;
}
}
IccRecords r = mIccRecords.get();
String operator = (r != null) ? r.getOperatorNumeric() : "";
// This is a workaround for a bug (7305641) where we don't failover to other
// suitable APNs if our preferred APN fails. On prepaid ATT sims we need to
// failover to a provisioning APN, but once we've used their default data
// connection we are locked to it for life. This change allows ATT devices
// to say they don't want to use preferred at all.
boolean usePreferred = true;
try {
usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
internal.R.bool.config_dontPreferApn);
} catch (Resources.NotFoundException e) {
if (DBG) log("buildWaitingApns: usePreferred NotFoundException set to true");
usePreferred = true;
}
if (usePreferred) {
//查詢資料庫看是否有志氣設定的已經選中過的APN
mPreferredApn = getPreferredApn();
}
if (DBG) {
log("buildWaitingApns: usePreferred=" + usePreferred
+ " canSetPreferApn=" + mCanSetPreferApn
+ " mPreferredApn=" + mPreferredApn
+ " operator=" + operator + " radioTech=" + radioTech
+ " IccRecords r=" + r);
}
if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
mPreferredApn.canHandleType(requestedApnType)) {
if (DBG) {
log("buildWaitingApns: Preferred APN:" + operator + ":"
+ mPreferredApn.numeric + ":" + mPreferredApn);
}
if (mPreferredApn.numeric.equals(operator)) {
if (ServiceState.bitmaskHasTech(mPreferredApn.bearerBitmask, radioTech)) {
apnList.add(mPreferredApn);
if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
return apnList;
} else {
if (DBG) log("buildWaitingApns: no preferred APN");
setPreferredApn(-1);
mPreferredApn = null;
}
} else {
if (DBG) log("buildWaitingApns: no preferred APN");
setPreferredApn(-1);
mPreferredApn = null;
}
}
if (mAllApnSettings != null) {
if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
for (ApnSetting apn : mAllApnSettings) {
if (apn.canHandleType(requestedApnType)) {
if (ServiceState.bitmaskHasTech(apn.bearerBitmask, radioTech)) {
if (DBG) log("buildWaitingApns: adding apn=" + apn);
apnList.add(apn);
} else {
if (DBG) {
log("buildWaitingApns: bearerBitmask:" + apn.bearerBitmask + " does " +
"not include radioTech:" + radioTech);
}
}
} else if (DBG) {
log("buildWaitingApns: couldn't handle requested ApnType="
+ requestedApnType);
}
}
} else {
loge("mAllApnSettings is null!");
}
if (DBG) log("buildWaitingApns: " + apnList.size() + " APNs in the list: " + apnList);
return apnList;
}
6.6 、apnContext.setWaitingApns(waitingApns)
setWaitingApns是RetryManager.java的一個方法,代碼路徑是:frameworks/opt/telephony/src/java/com/android/internal/telephony/RetryManager.java
設定apnContext的waitingApns 時會配置RetryManager的config 資訊,debug 版本可以通過SystemProperties.get("test.data_retry_config")配置測驗,我們這里用默認的SIM配置,包含apn重試最大次數,apn重試延遲時間等等,和獲取當前重試apn串列的mCurrentApnIndex 索引,在每次設定setWaitingApns都會將這些config資訊全部reset,這就意味著如果設定一次setWaitingApns,所有的重試策略都會重置,apn串列選擇重新開始,這里其實是有點問題的,后面再說,
private void reset() {
mMaxRetryCount = 0;
mRetryCount = 0;
mCurrentApnIndex = -1;
mSameApnRetryCount = 0;
mModemSuggestedDelay = NO_SUGGESTED_RETRY_DELAY;
mRetryArray.clear();
}
6.7、setupData()撥號函式,
- 1、setupData撥號的時候首先通過apnSetting = apnContext.getNextApnSetting() 從apnContext中獲取撥號串列中的apn,
- 2、這里有幾種情況下會替換apnContext 中的apnSetting:
1>還有dataConnection沒有斷開,直接dcacApnSetting = dcac.getApnSettingSync()獲取dcac的apnSetting 替換用來撥號,
2> dcac斷開的情況下,如果isOnlySingleDcAllowed或者isHigherPriorityApnContextActive ,就是如果是只允許單dcac模式或者有更高優先級的apnContext也是不用getNextApnSetting得到的apnSetting直接return回傳停止撥號,
3>上述情況都不滿足的情況下會cleanUpAllConnections斷開 當前所以apnContext連接,如果是不是IDLE或者FAILED狀態則表示cleanUpAllConnections沒有完成清除,這種情況下也是直接return停止撥號操作,
- 3、以上判斷走完后就會將當前的apnContext設定為引數通過EVENT_DATA_SETUP_COMPLETE 發送給RILD最終完成撥號,
Message msg = obtainMessage();
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
dcac.bringUp(apnContext, profileId, radioTech, msg, generation);
private boolean setupData(ApnContext apnContext, int radioTech) {
if (DBG) log("setupData: apnContext=" + apnContext);
apnContext.requestLog("setupData");
ApnSetting apnSetting;
DcAsyncChannel dcac = null;
apnSetting = apnContext.getNextApnSetting();
log("try this apnSetting:"+apnSetting);
if (apnSetting == null) {
if (DBG) log("setupData: return for no apn found!");
return false;
}
int profileId = apnSetting.profileId;
if (profileId == 0) {
profileId = getApnProfileID(apnContext.getApnType());
}
// On CDMA, if we're explicitly asking for DUN, we need have
// a dun-profiled connection so we can't share an existing one
// On GSM/LTE we can share existing apn connections provided they support
// this type.
if (apnContext.getApnType() != PhoneConstants.APN_TYPE_DUN ||
teardownForDun() == false) {
dcac = checkForCompatibleConnectedApnContext(apnContext);
if (dcac != null) {
// Get the dcacApnSetting for the connection we want to share.
ApnSetting dcacApnSetting = dcac.getApnSettingSync();
if (dcacApnSetting != null) {
// Setting is good, so use it.
log("dcac still connect, dcac.getApnSettingSync():"+dcacApnSetting);
apnSetting = dcacApnSetting;
}
}
}
if (dcac == null) {
if (isOnlySingleDcAllowed(radioTech)) {
if (isHigherPriorityApnContextActive(apnContext)) {
if (DBG) {
log("setupData: Higher priority ApnContext active. Ignoring call");
}
return false;
}
// Only lower priority calls left. Disconnect them all in this single PDP case
// so that we can bring up the requested higher priority call (once we receive
// response for deactivate request for the calls we are about to disconnect
if (cleanUpAllConnections(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
// If any call actually requested to be disconnected, means we can't
// bring up this connection yet as we need to wait for those data calls
// to be disconnected.
if (DBG) log("setupData: Some calls are disconnecting first. Wait and retry");
return false;
}
// No other calls are active, so proceed
if (DBG) log("setupData: Single pdp. Continue setting up data call.");
}
dcac = findFreeDataConnection();
if (dcac == null) {
dcac = createDataConnection();
}
if (dcac == null) {
if (DBG) log("setupData: No free DataConnection and couldn't create one, WEIRD");
return false;
}
}
final int generation = apnContext.incAndGetConnectionGeneration();
if (DBG) {
log("setupData: dcac=" + dcac + " apnSetting=" + apnSetting + " gen#=" + generation);
}
apnContext.setDataConnectionAc(dcac);
apnContext.setApnSetting(apnSetting);
apnContext.setState(DctConstants.State.CONNECTING);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
Message msg = obtainMessage();
msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
dcac.bringUp(apnContext, profileId, radioTech, msg, generation);
if (DBG) log("setupData: initing!");
return true;
}
private void setInitialAttachApn() {
ApnSetting iaApnSetting = null;
ApnSetting defaultApnSetting = null;
ApnSetting firstApnSetting = null;
log("setInitialApn: E mPreferredApn=" + mPreferredApn);
if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) {
firstApnSetting = mAllApnSettings.get(0);
log("setInitialApn: firstApnSetting=" + firstApnSetting);
// Search for Initial APN setting and the first apn that can handle default
for (ApnSetting apn : mAllApnSettings) {
// Can't use apn.canHandleType(), as that returns true for APNs that have no type.
if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_IA) &&
apn.carrierEnabled) {
// The Initial Attach APN is highest priority so use it if there is one
log("setInitialApn: iaApnSetting=" + apn);
iaApnSetting = apn;
break;
} else if ((defaultApnSetting == null)
&& (apn.canHandleType(PhoneConstants.APN_TYPE_DEFAULT))) {
// Use the first default apn if no better choice
log("setInitialApn: defaultApnSetting=" + apn);
defaultApnSetting = apn;
}
}
}
// The priority of apn candidates from highest to lowest is:
// 1) APN_TYPE_IA (Initial Attach)
// 2) mPreferredApn, i.e. the current preferred apn
// 3) The first apn that than handle APN_TYPE_DEFAULT
// 4) The first APN we can find.
ApnSetting initialAttachApnSetting = null;
if (iaApnSetting != null) {
if (DBG) log("setInitialAttachApn: using iaApnSetting");
initialAttachApnSetting = iaApnSetting;
} else if (mPreferredApn != null) {
if (DBG) log("setInitialAttachApn: using mPreferredApn");
initialAttachApnSetting = mPreferredApn;
} else if (defaultApnSetting != null) {
if (DBG) log("setInitialAttachApn: using defaultApnSetting");
initialAttachApnSetting = defaultApnSetting;
} else if (firstApnSetting != null) {
if (DBG) log("setInitialAttachApn: using firstApnSetting");
initialAttachApnSetting = firstApnSetting;
}
if (initialAttachApnSetting == null) {
if (DBG) log("setInitialAttachApn: X There in no available apn");
} else {
if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting);
mPhone.mCi.setInitialAttachApn(initialAttachApnSetting.apn,
initialAttachApnSetting.protocol, initialAttachApnSetting.authType,
initialAttachApnSetting.user, initialAttachApnSetting.password, null);
}
}
7、撥號完成,4G網路可以正常使用
framework撥號設定完成以后,RILD開始撥號分配ip 這些鏈路資訊成功后會回傳給DataConnection EVENT_SETUP_DATA_CONNECTION_DONE事件,同時DataConnection更新自己的networkAgent通知ConnectivityService改變網路狀態為Connected,到這里整個撥號流程就全部結束了.
代碼路徑:frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
private class DcActivatingState extends State {
@Override
public boolean processMessage(Message msg) {
boolean retVal;
AsyncResult ar;
ConnectionParams cp;
if (DBG) log("DcActivatingState: msg=" + msgToString(msg));
switch (msg.what) {
case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED:
case EVENT_CONNECT:
// Activating can't process until we're done.
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_SETUP_DATA_CONNECTION_DONE:
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
if (result != DataCallResponse.SetupResult.ERR_Stale) {
if (mConnectionParams != cp) {
loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
+ " != cp:" + cp);
}
}
if (DBG) {
log("DcActivatingState onSetupConnectionCompleted result=" + result
+ " dc=" + DataConnection.this);
}
if (cp.mApnContext != null) {
cp.mApnContext.requestLog("onSetupConnectionCompleted result=" + result);
}
8、結尾
6.6和6.7中提到的撥號的問題:
我們看一下6.6中 trySetupData 函式里面判斷只要是當前apnContext狀態為DctConstants.State.IDLE就會去 buildWaitingApns并且設定到apnContext 中,前面我們也提到
apnContext中每次設定setWaitingApns的時候都會重置currentApnSettingIndex腳本,這樣每次重試的時候都是重頭開始去apn撥號,而且6.7中有很多種情況會替換到從apnContext中取到的apnSetting,比如當前dcac沒有斷開連接,當前連接正在斷開等等狀態都會導致撥號跳過,好家伙,這么坑的漏洞就導致客戶那邊出現的每次撥號都是使用第一個APN撥號,串列的其他apn沒有用到,因為都跳過了,導致撥號一直失敗,自然設定界面的apn串列當然沒有apn被連接上,
這個問題的解決辦法就是盡量不要多次呼叫apnContext.setWaitingApns防止RetryManager的config引數被重置,另外setUpData撥號函式里面如果retrun跳過當前apn的話記錄currentIndex這樣下次還是使用這個apn撥號,這樣就可以保證apn串列回圈嘗試了,
if (apnContext.getState() == DctConstants.State.IDLE) {
if (waitingApns == null) {
waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech);
}
if (waitingApns.isEmpty()) {
notifyNoData(DcFailCause.MISSING_UNKNOWN_APN, apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
String str = "trySetupData: X No APN found retValue=false";
if (DBG) log(str);
apnContext.requestLog(str);
return false;
} else {
apnContext.setWaitingApns(waitingApns);
}
文章到這里就全部結束了,如果有不對的地方歡迎評論指正,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/287631.html
標籤:其他
上一篇:Android 11.0原始碼系列之IMS(四)InputChannel
下一篇:原生與Flutter通信
