Android L 漫遊淺析
這篇文章主要是分析在Android L 原始碼中對手機漫遊的處理。當然我這裡所說的漫遊指的是國際漫遊。通常我們判斷手機是否在國際漫遊,第一個想法就是比較網路上獲取的MCC+MNC是否與手機中的IMSI相同,如果不同就判斷為漫遊了。如果是漫遊的話,手機上最直觀的可以看到就是兩個地方了:
a . 手機的螢幕的狀態攔上手機訊號角標的左下方是否有”R”顯示。
b . Setting --->About phone --->Status --->Roming
當然這是最粗略的比較方法,通常全球的運營商對於漫遊有互相簽訂協議,所以單純用上面的方法是不夠細緻的,google
手機注網是一個比較複雜的過程,當然漫遊就在這個過程中,在這裡,我重點分析漫遊這個點,注網的話,後面再另發文。
首先需要看的一個類就是:
Android_L/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
在這個類中有方法:
protectedvoid handlePollStateResult (int what, AsyncResult ar) { ...... }
什麼時候會呼叫這個方法呢?RIL層在完成ServiceStateTracker物件發起的查詢最新網路服務的狀態後,通過ServiceStateTracker建立的Message物件發起的Callback回撥。在ServiceStateTracker物件中會呼叫handlePollStateResult 和 pollStateDone 方法,將查詢得來的最新資訊儲存在ServiceStateTracker的多個屬性中 。由於GsmServiceStateTracker extends ServiceStateTracker,GsmServiceStateTracker 中的handlePollStateResult 方法會覆蓋ServiceStateTracker中的方法,下面是handlePollStateResult 方法的實現:
/**
* Handle the result of one of the pollState()-related requests
*/
@Override
protected void handlePollStateResult (int what, AsyncResult ar) {
int ints[];
String states[];
// Ignore stale requests from last poll
if (ar.userObj != mPollingContext) return;
if (ar.exception != null) {
CommandException.Error err=null;
if (ar.exception instanceof CommandException) {
err = ((CommandException)(ar.exception)).getCommandError();
}
if (err == CommandException.Error.RADIO_NOT_AVAILABLE) {
// Radio has crashed or turned off
cancelPollState();
return;
}
if (!mCi.getRadioState().isOn()) {
// Radio has crashed or turned off
cancelPollState();
return;
}
if (err != CommandException.Error.OP_NOT_ALLOWED_BEFORE_REG_NW) {
loge("RIL implementation has returned an error where it must succeed" +
ar.exception);
}
} else try {
switch (what) {
case EVENT_POLL_STATE_REGISTRATION: {
states = (String[])ar.result;
int lac = -1;
int cid = -1;
int type = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
int regState = ServiceState.RIL_REG_STATE_UNKNOWN;
int reasonRegStateDenied = -1;
int psc = -1;
if (states.length > 0) {
try {
regState = Integer.parseInt(states[0]);
if (states.length >= 3) {
if (states[1] != null && states[1].length() > 0) {
lac = Integer.parseInt(states[1], 16);
}
if (states[2] != null && states[2].length() > 0) {
cid = Integer.parseInt(states[2], 16);
}
// states[3] (if present) is the current radio technology
if (states.length >= 4 && states[3] != null) {
type = Integer.parseInt(states[3]);
}
}
if (states.length > 14) {
if (states[14] != null && states[14].length() > 0) {
psc = Integer.parseInt(states[14], 16);
}
}
} catch (NumberFormatException ex) {
loge("error parsing RegistrationState: " + ex);
}
}
mGsmRoaming = regCodeIsRoaming(regState);
mNewSS.setState(regCodeToServiceState(regState));
mNewSS.setRilVoiceRadioTechnology(type);
boolean isVoiceCapable = mPhoneBase.getContext().getResources()
.getBoolean(com.android.internal.R.bool.config_voice_capable);
if ((regState == ServiceState.RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED
|| regState == ServiceState.RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED
|| regState == ServiceState.RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED
|| regState == ServiceState.RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED)
&& isVoiceCapable) {
mEmergencyOnly = true;
} else {
mEmergencyOnly = false;
}
// LAC and CID are -1 if not avail
mNewCellLoc.setLacAndCid(lac, cid);
mNewCellLoc.setPsc(psc);
break;
}
case EVENT_POLL_STATE_GPRS: {
states = (String[])ar.result;
int type = 0;
int regState = ServiceState.RIL_REG_STATE_UNKNOWN;
mNewReasonDataDenied = -1;
mNewMaxDataCalls = 1;
if (states.length > 0) {
try {
regState = Integer.parseInt(states[0]);
// states[3] (if present) is the current radio technology
if (states.length >= 4 && states[3] != null) {
type = Integer.parseInt(states[3]);
}
if ((states.length >= 5 ) &&
(regState == ServiceState.RIL_REG_STATE_DENIED)) {
mNewReasonDataDenied = Integer.parseInt(states[4]);
}
if (states.length >= 6) {
mNewMaxDataCalls = Integer.parseInt(states[5]);
}
} catch (NumberFormatException ex) {
loge("error parsing GprsRegistrationState: " + ex);
}
}
int dataRegState = regCodeToServiceState(regState);
mNewSS.setDataRegState(dataRegState);
mDataRoaming = regCodeIsRoaming(regState);
mNewSS.setRilDataRadioTechnology(type);
if (DBG) {
log("handlPollStateResultMessage: GsmSST setDataRegState=" + dataRegState
+ " regState=" + regState
+ " dataRadioTechnology=" + type);
}
break;
}
case EVENT_POLL_STATE_OPERATOR: {
String opNames[] = (String[])ar.result;
if (opNames != null && opNames.length >= 3) {
mNewSS.setOperatorName (opNames[0], opNames[1], opNames[2]);
}
break;
}
case EVENT_POLL_STATE_NETWORK_SELECTION_MODE: {
ints = (int[])ar.result;
mNewSS.setIsManualSelection(ints[0] == 1);
break;
}
}
} catch (RuntimeException ex) {
loge("Exception while polling service state. Probably malformed RIL response." + ex);
}
mPollingContext[0]--;
if (mPollingContext[0] == 0) {
/**
* Since the roaming state of gsm service (from +CREG) and
* data service (from +CGREG) could be different, the new SS
* is set to roaming when either is true.
*
* There are exceptions for the above rule.
* The new SS is not set as roaming while gsm service reports
* roaming but indeed it is same operator.
* And the operator is considered non roaming.
*
* The test for the operators is to handle special roaming
* agreements and MVNO's.
*/
<span style="color:#FF0000;"> boolean roaming = (mGsmRoaming || mDataRoaming);</span>
if ((mGsmRoaming && isSameNamedOperators(mNewSS)
&& !isSameNamedOperatorConsideredRoaming(mNewSS))
|| isOperatorConsideredNonRoaming(mNewSS)) {
roaming = false;
}
mNewSS.setRoaming(roaming);
mNewSS.setEmergencyOnly(mEmergencyOnly);
pollStateDone();
}
}
這個方法主要完成了三件事情:
1,RIL返回的查詢結果異常處理;
2,根據返回的4種不同網路服務查詢型別,作不同處理;
3,呼叫pollDtateDone方法完成後面的工作。
在這裡我們只關注 Roaming , 看方法中對於漫遊定義:
boolean roaming = (mGsmRoaming || mDataRoaming);
那麼mGsmRoaming 和 mDataRoaming 分別是什麼呢?在類的最前面對於變數的定義中有:
/**
* GSM roaming status solely based on TS 27.007 7.2 CREG. Only used by
* handlePollStateResult to store CREG roaming result.
*/
private boolean mGsmRoaming = false;
/**
* Data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by
* handlePollStateResult to store CGREG roaming result.
*/
private boolean mDataRoaming = false;
根據Google加的註釋可以看到,我們應該需要參考3GPP文件TS 27.007的相關章節,大家可以去3GPP官網下載該文件看看。
對上面兩個變數的賦值,主要是在:
mGsmRoaming = regCodeIsRoaming(regState);
mDataRoaming = regCodeIsRoaming(regState);
這裡傳入的引數 regState 網路狀態編碼 是一個很重要的引數,手機當前狀態的很多屬性都是根據這個引數來判斷的。同樣是在TS 27.007文件的7.2節有定義對應關係。
Defined values
<n>:
0 disable network registration unsolicited result code
1 enable network registration unsolicited result code +CREG: <stat>
2 enable network registration and location information unsolicited result code +CREG:
<stat>[,<lac>,<ci>[,<AcT>]]
<stat>: circuit mode registration status
0 not registered, MT is not currently searching a new operator to register to
1 registered, home network
2 not registered, but MT is currently searching a new operator to register to
3 registration denied
4 unknown
<span style="color:#FF0000;">5 registered, roaming</span>
<lac>: string type; two byte location area code or tracking are a code in hexadecimal format (e.g. "00C3" equals
195 in decimal)
<ci>: string type; four byte GERAN/UTRAN/E-UTRAN cell ID in hexadecimal format
<AcT>: access technology of the registered network
從上面的定義可以看出,code為5的時候是漫遊狀態,找到方法regCodeIsRoaming()
/**
* code is registration state 0-5 from TS 27.007 7.2
* returns true if registered roam, false otherwise
*/
private boolean regCodeIsRoaming (int code) {
return ServiceState.RIL_REG_STATE_ROAMING == code;
}
從程式碼中可以看到,當code為ServiceState.RIL_REG_STATE_ROAMING 時,返回值為true。找到這個常量的定義:
Android_L/frameworks/base/telephony/java/android/telephony/ServiceState.java
/**
* RIL level registration state values from ril.h
* ((const char **)response)[0] is registration state 0-6,
* 0 - Not registered, MT is not currently searching
* a new operator to register
* 1 - Registered, home network
* 2 - Not registered, but MT is currently searching
* a new operator to register
* 3 - Registration denied
* 4 - Unknown
* 5 - Registered, roaming
* 10 - Same as 0, but indicates that emergency calls
* are enabled.
* 12 - Same as 2, but indicates that emergency calls
* are enabled.
* 13 - Same as 3, but indicates that emergency calls
* are enabled.
* 14 - Same as 4, but indicates that emergency calls
* are enabled.
* @hide
*/
public static final int RIL_REG_STATE_NOT_REG = 0;
/** @hide */
public static final int RIL_REG_STATE_HOME = 1;
/** @hide */
public static final int RIL_REG_STATE_SEARCHING = 2;
/** @hide */
public static final int RIL_REG_STATE_DENIED = 3;
/** @hide */
public static final int RIL_REG_STATE_UNKNOWN = 4;
/** @hide */
<span style="color:#FF0000;"> public static final int RIL_REG_STATE_ROAMING = 5;</span>
/** @hide */
public static final int RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED = 10;
/** @hide */
public static final int RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED = 12;
/** @hide */
public static final int RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED = 13;
/** @hide */
public static final int RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED = 14;
可以看到程式碼中這些定義的code值與名稱的對應關係,RIL_REG_STATE_ROAMING常量的值為5 。所以當傳入的引數regState為5的時候,為漫遊。
現在我們關注下面這段程式碼:
if (mPollingContext[0] == 0) {
/**
* Since the roaming state of gsm service (from +CREG) and
* data service (from +CGREG) could be different, the new SS
* is set to roaming when either is true.
*
* There are exceptions for the above rule.
* The new SS is not set as roaming while gsm service reports
* roaming but indeed it is same operator.
* And the operator is considered non roaming.
*
* The test for the operators is to handle special roaming
* agreements and MVNO's.
*/
boolean roaming = (mGsmRoaming || mDataRoaming);
if ((mGsmRoaming && isSameNamedOperators(mNewSS)
&& !isSameNamedOperatorConsideredRoaming(mNewSS))
|| isOperatorConsideredNonRoaming(mNewSS)) {
roaming = false;
}
mNewSS.setRoaming(roaming);
mNewSS.setEmergencyOnly(mEmergencyOnly);
pollStateDone();
}
可以先看看google加在前面的註釋,大概的意思是:
由於GSM服務和資料服務的漫遊狀態可能不同,所以只要這二者其中之一是漫遊就將New SS(最新的ServiceState)設定為漫遊。
對於上面的規則有一個說明。
當GSM服務被認為是漫遊但事實上他們是同一個運營商,且時運營商決定不漫遊,這個時候new SS不會設定為漫遊。
對於運營商的測試是為了處理特殊的漫遊協議和移動虛擬網路運營商。
下面分析下if條件中的幾個方法,在這裡傳入的引數都是mNewSS,簡單的說一下這個物件:
ServiceState意思是服務狀態,手機插入SIM卡成功啟動後,BP Modem會讀取SIM卡中的IMSI資訊完成SIM卡中資訊的驗證和運營商行動網路的註冊,這樣手機才能正常使用運營商提供的服務,程式碼中ServiceState儲存SIM卡註冊成功後運營商網路的一些基本服務資訊,具體可以看這個類中常量的定義,顯然,漫遊也在其中。
(1)isSameNamedOperators():從註釋中知道如果運營商網路的MCC和SIM卡的MCC一樣,同時ons(檢視相關的協議文件)和spn(spn是寫在sim卡中的值,具體請查詢文件)不同則設定為漫遊狀態,即返回值為true。
/**
* Set roaming state if operator mcc is the same as sim mcc
* and ons is different from spn
*
* @param s ServiceState hold current ons
* @return true if same operator
*/
private boolean isSameNamedOperators(ServiceState s) {
String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty"); //獲得SimCard中的spn,如果沒有,返回“empty”
String onsl = s.getOperatorAlphaLong(); //獲得當前註冊的運營商網路的長名
String onss = s.getOperatorAlphaShort(); //獲取當前註冊的運營商網路的短名
boolean equalsOnsl = onsl != null && spn.equals(onsl); //onsl不為空,且spn和onsl相同時,equalsOnsl為true
boolean equalsOnss = onss != null && spn.equals(onss); //onss不為空,且spn和onss相同時,equalsOnsl為true
return currentMccEqualsSimMcc(s) && (equalsOnsl || equalsOnss);
}
看看這個返回值的邏輯。只有當equalsOnsl或者equalsOnss其中一個為true且currentMccEqualsSimMcc()返回值為true時,上面這個方法才返回true,下面看看
currentMccEqualsSimMcc()這個方法,該方法用來比較SIM卡的MCC和網路上的MCC,即比較simNumeric和operatorNumeric的前三位,相同則返回true。
/**
* Compare SIM MCC with Operator MCC
*
* @param s ServiceState hold current ons
* @return true if both are same
*/
private boolean currentMccEqualsSimMcc(ServiceState s) {
String simNumeric = SystemProperties.get( //獲得SIM Number
TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
String operatorNumeric = s.getOperatorNumeric(); //獲得Operator Number
boolean equalsMcc = true;
try {
equalsMcc = simNumeric.substring(0, 3).
equals(operatorNumeric.substring(0, 3));
} catch (Exception e){
}
return equalsMcc;
}
下面同時看看isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()這兩個方法。
private boolean isSameNamedOperatorConsideredRoaming(ServiceState s) {
String operatorNumeric = s.getOperatorNumeric();
String[] numericArray = mPhone.getContext().getResources().getStringArray(
<span style="color:#FF0000;">com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming</span>);
if (numericArray.length == 0 || operatorNumeric == null) {
return false;
}
for (String numeric : numericArray) {
if (operatorNumeric.startsWith(numeric)) {
return true;
}
}
return false;
}
/**
* Do not set roaming state in case of oprators considered non-roaming.
*
+ Can use mcc or mcc+mnc as item of config_operatorConsideredNonRoaming.
* For example, 302 or 21407. If mcc or mcc+mnc match with operator,
* don't set roaming state.
*
* @param s ServiceState hold current ons
* @return false for roaming state set
*/
private boolean isOperatorConsideredNonRoaming(ServiceState s) {
String operatorNumeric = s.getOperatorNumeric();
String[] numericArray = mPhone.getContext().getResources().getStringArray(
<span style="color:#FF0000;">com.android.internal.R.array.config_operatorConsideredNonRoaming</span>);
if (numericArray.length == 0 || operatorNumeric == null) {
return false;
}
for (String numeric : numericArray) {
if (operatorNumeric.startsWith(numeric)) {
return true;
}
}
return false;
}
通過比較發現這兩個方法的程式碼邏輯中只有getStringArray()這個方法中傳遞的引數不同,那我們下意識的跟進這個方法,來到:
Android L/frameworks/base/core/java/android/content/res/Resources.java
/**
* Return the string array associated with a particular resource ID.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
* @return The string array associated with the resource.
*/
public String[] getStringArray(int id) throws NotFoundException {
String[] res = mAssets.getResourceStringArray(id);
if (res != null) {
return res;
}
throw new NotFoundException("String array resource ID #0x"
+ Integer.toHexString(id));
}
從註釋來看,返會的是一個與特有資源ID相關聯的字元陣列,我們繼續往下跟進getResourceStringArray()這個方法,來到:
Android L/frameworks/base/core/java/android/content/res/AssetManager.java
/**
* Retrieve the string array associated with a particular resource
* identifier.
* @param id Resource id of the string array
*/
/*package*/ final String[] getResourceStringArray(final int id) {
String[] retArray = getArrayStringResource(id);
return retArray;
}
那AssetManager.java是一個怎樣的類呢,注意到程式碼中對於該類有一個註釋。
/**
* Provides access to an application's raw asset files; see {@link Resources}
* for the way most applications will want to retrieve their resource data.
* This class presents a lower-level API that allows you to open and read raw
* files that have been bundled with the application as a simple stream of
* bytes.
*/
public final class AssetManager {
......
}
大概的意思是說這個類為應用提供一個通往原始資原始檔的通道,通過這種方式應用可以重新獲得它們的資原始檔。這個類提供了一種輕量級的API,
能夠讓你開啟和讀那些已經與應用繫結在一起作為簡單位元組流的原始資原始檔。這個翻譯起來比較繞口,樓主英語也是渣,所以就直接看效果了,
再看看getArrayStringResource()這個方法的定義:
private native final String[] getArrayStringResource(int arrayRes);
注意到這是一個native方法,那JNI是如何實現的呢?來到:
/home/simon/Android L/frameworks/base/core/jni/android_util_AssetManager.cpp
程式碼中有如下函式:
static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
jint arrayResId)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return NULL;
}
const ResTable& res(am->getResources());
const ResTable::bag_entry* startOfBag;
const ssize_t N = res.lockBag(arrayResId, &startOfBag);
if (N < 0) {
return NULL;
}
jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);
if (env->ExceptionCheck()) {
res.unlockBag(startOfBag);
return NULL;
}
Res_value value;
const ResTable::bag_entry* bag = startOfBag;
size_t strLen = 0;
for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
value = bag->map.value;
jstring str = NULL;
// Take care of resolving the found resource to its final value.
ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return array;
}
#endif
if (value.dataType == Res_value::TYPE_STRING) {
const ResStringPool* pool = res.getTableStringBlock(block);
const char* str8 = pool->string8At(value.data, &strLen);
if (str8 != NULL) {
str = env->NewStringUTF(str8);
} else {
const char16_t* str16 = pool->stringAt(value.data, &strLen);
str = env->NewString(str16, strLen);
}
// If one of our NewString{UTF} calls failed due to memory, an
// exception will be pending.
if (env->ExceptionCheck()) {
res.unlockBag(startOfBag);
return NULL;
}
env->SetObjectArrayElement(array, i, str);
// str is not NULL at that point, otherwise ExceptionCheck would have been true.
// If we have a large amount of strings in our array, we might
// overflow the local reference table of the VM.
env->DeleteLocalRef(str);
}
}
res.unlockBag(startOfBag);
return array;
}
那上面的函式是什麼作用呢,簡單來說就是根據之前傳入的ID到Android L/frameworks/base/core/res/res目錄下獲得相應陣列,先看下這個目錄下是什麼檔案
會根據MCC和MNC(如果有的話)去找到相應的目錄,比如MCC=234,MNC=34,那麼就去找到values-mcc234-mnc34這個目錄,並去讀取這個目錄下的配置檔案,
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Don't use roaming icon for considered operators -->
<string-array translatable="false" name="<span style="color:#FF0000;">config_operatorConsideredNonRoaming</span>">
<item>23430</item>
<item>23431</item>
<item>23432</item>
<item>23433</item>
<item>23434</item>
<item>23486</item>
</string-array>
</resources>
我們之前所說的isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()這兩個方法裡對於得到陣列傳入的引數不同,就體現在當前配置檔案中的"name"欄位,上面JNI函式返回的是一個數組,數組裡的值就是讀取的"item",這裡的item是可以根據運營商的需求去手動配置的,這個就體現了個性化定製。在得到陣列後,isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()方法中的處理邏輯都是會遍歷陣列,同時與operatorNumeric作比較,如果相同則返回true.
在一一分析了上面這些方法後,再回頭去看handlePollStateResult()方法最後if中的處理邏輯就一目瞭然了,在這裡就不多說了,讀者可以簡單推理一下。這篇部落格的題目為漫遊淺析,那麼實際上手機廠商對於漫遊的處理不一定會採取google原生的方案,通常晶片廠商會有自己的解決方案,所以真正對於漫遊的處理可能會複雜點,如果讀者有機會參與手機ROM開發,也許會接觸到更多這方面的知識,當然android也在不斷生長中,第一次寫這麼多內容,不對的地方望指正。