編輯:關於Android編程
這篇文章主要是分析在Android L 源代碼中對手機漫游的處理。當然我這裡所說的漫游指的是國際漫游。通常我們判斷手機是否在國際漫游,第一個想法就是比較網絡上獲取的MCC+MNC是否與手機中的IMSI相同,如果不同就判斷為漫游了。如果是漫游的話,手機上最直觀的可以看到就是兩個地方了:
a . 手機的屏幕的狀態攔上手機信號角標的左下方是否有”R”顯示。
b . Setting --->About phone --->Status --->Roming
當然這是最粗略的比較方法,通常全球的運營商對於漫游有互相簽訂協議,所以單純用上面的方法是不夠細致的,google 為了解決這個特殊化定制的問題,在Android L 上使用了一個機制來判斷手機是否漫游,下面就從解析代碼的角度來分析這個機制。
手機注網是一個比較復雜的過程,當然漫游就在這個過程中,在這裡,我重點分析漫游這個點,注網的話,後面再另發文。
首先需要看的一個類就是:
Android_L/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
在這個類中有方法:
protected void 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. */ boolean roaming = (mGsmRoaming || mDataRoaming); 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: 0 disable network registration unsolicited result code 1 enable network registration unsolicited result code +CREG: 2 enable network registration and location information unsolicited result code +CREG: [, , [,]] : 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 5 registered, roaming : string type; two byte location area code or tracking are a code in hexadecimal format (e.g. "00C3" equals 195 in decimal) : string type; four byte GERAN/UTRAN/E-UTRAN cell ID in hexadecimal format : 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 */ public static final int RIL_REG_STATE_ROAMING = 5; /** @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; }
private boolean isSameNamedOperatorConsideredRoaming(ServiceState s) { String operatorNumeric = s.getOperatorNumeric(); String[] numericArray = mPhone.getContext().getResources().getStringArray( com.android.internal.R.array.config_sameNamedOperatorConsideredRoaming); 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( com.android.internal.R.array.config_operatorConsideredNonRoaming); 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)那上面的函數是什麼作用呢,簡單來說就是根據之前傳入的ID到Android L/frameworks/base/core/res/res目錄下獲得相應數組,先看下這個目錄下是什麼文件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; }
會根據MCC和MNC(如果有的話)去找到相應的目錄,比如MCC=234,MNC=34,那麼就去找到values-mcc234-mnc34這個目錄,並去讀取這個目錄下的配置文件,
我們之前所說的isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()這兩個方法裡對於得到數組傳入的參數不同,就體現在當前配置文件中的"name"字段,上面JNI函數返回的是一個數組,數組裡的值就是讀取的"item",這裡的item是可以根據運營商的需求去手動配置的,這個就體現了個性化定制。在得到數組後,isSameNamedOperatorConsideredRoaming()和isOperatorConsideredNonRoaming()方法中的處理邏輯都是會遍歷數組,同時與operatorNumeric作比較,如果相同則返回true.23430 - 23431
- 23432
- 23433
- 23434
- 23486
在一一分析了上面這些方法後,再回頭去看handlePollStateResult()方法最後if中的處理邏輯就一目了然了,在這裡就不多說了,讀者可以簡單推理一下。這篇博客的題目為漫游淺析,那麼實際上手機廠商對於漫游的處理不一定會采取google原生的方案,通常芯片廠商會有自己的解決方案,所以真正對於漫游的處理可能會復雜點,如果讀者有機會參與手機ROM開發,也許會接觸到更多這方面的知識,當然android也在不斷生長中,第一次寫這麼多內容,不對的地方望指正。
之前在博文中為了更好的給大家演示APP的實現效果,本人了解學習了幾種給手機錄屏的方法,今天就給大家介紹兩種我個人用的比較舒服的兩種方法:(1)配置adb環境後,使用cmd
下面介紹 “豬腳光環的” : Handler 、Message 、MessageQueue Looper。並以Java 程序模擬安卓的消息處理機制
上一篇文章總結的布局優化的問題,如果對布局優化不是很熟悉的,可以看一下Android Studido下的應用性能優化總結–布局優化 , 這周一直籌劃總結一下內
ImageSwitcher類是ViewSwitcher類的子類,它實現的效果是在完成ImageView的切換並且帶有動畫效果。要使用這個類需要以下兩個步驟:1)為Imag