雙卡手機發送短信 - 坑爹的雙卡雙待

最近要寫一個Android app,其中一個功能要發短信,直接照抄Android API Demos的例子OS\SMS Messaging,在自己的手機上測試,發現總是報錯SmsManager.RESULT_ERROR_NO_SERVICE,理解不能。

於是開始Google。發現網上很少有人提到這個錯誤,而且Android上發短信,全部都是用的API Demos的發短信的例子,或者使用Intent調用系統短信App來發短信。雖然用Intent調用系統短信App來發短信也可以當作一個workround,但用戶體驗不好,感覺不爽。我的應用裏的發短信流程應該是這樣的:用戶點擊按鈕,彈出ProgressDialog,程序在後臺悄悄的發短信,開槍的不要,然後告訴用戶短信發成功沒有。

既然網上幾乎全部的coder都用API Demos的短信例子,包括幾本ebook(《Beginning Android Application Development - 8 Messaging and Networking》、《Professional Android 4 Application Development》)都是,我覺得要麼是Google在撒謊,Android的SmsManager其實有重大BUG;要麼就是我每次打開Ecllipse的方式不對,或者我其實生活在Matrix裏。

NND,繼續深挖。於是就找到了“adb logcat -b radio”這個用法,即查看GSM模塊的通訊log。下面就是系統短信Activity和API Demos短信Activity的log比較:

系統短信Activity的log
06-10 15:18:26.058 D/SMS     (28645): encoding detail>TextEncodingDetails { msgCount=1, codeUnitCount=2, codeUnitsRemaining=68, codeUnitSize=3, languageTable=0, languageShiftTable=0 }
06-10 15:18:26.178 D/RILJ_GSM(  418): [3085]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:26.178 D/RILJ    (  418): [3086]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:26.188 D/RIL_SWITCH(  100): CT_C+W_enable is NULL, set the value to disable.
06-10 15:18:26.188 D/RIL_SWITCH(  100): ril switch GO HTC RIL
06-10 15:18:26.188 D/RILJ    (  418): [3086]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:26.208 D/RILJ_GSM(  418): [3085]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:26.218 D/RILJ_GSM(  418): [3087]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:26.218 D/RILJ    (  418): [3088]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:26.228 D/RIL_SWITCH(  100): CT_C+W_enable is NULL, set the value to disable.
06-10 15:18:26.228 D/RIL_SWITCH(  100): ril switch GO HTC RIL
06-10 15:18:26.228 D/RILJ    (  418): [3088]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:26.238 D/RILJ_GSM(  418): [3087]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:26.679 D/GSM     (  418): laugnagetable/shifttable: 0/0
06-10 15:18:26.679 D/GSM     (  418): GEP countGsmSeptets: -1
06-10 15:18:26.679 D/SMS     (  418): sendRawPduWithBundle
06-10 15:18:26.689 D/SMS     (  418): checkInSegmentToRIL> SmsTracker@411f02c0, RetryCnt> 0
06-10 15:18:26.689 D/RILJ_GSM(  418): sendSMS pdu : 01000b813145189164f700080454755475
06-10 15:18:26.689 D/RILJ_GSM(  418): [3089]> SEND_SMS
06-10 15:18:27.410 D/RILJ_GSM(  418): [3090]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:27.410 D/RILJ    (  418): [3091]> REPORT_SMS_MEMORY_STATUS: true
06-10 15:18:27.410 D/RIL_SWITCH(  100): CT_C+W_enable is NULL, set the value to disable.
06-10 15:18:27.410 D/RIL_SWITCH(  100): ril switch GO HTC RIL
06-10 15:18:27.410 D/RILJ    (  418): [3091]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:29.812 D/RILMUX  (  744): main(2656) GSM0710 buffer. Stored 0
06-10 15:18:29.812 D/RILMUX  (  744): main(2657) Frames received/dropped: 8632/0
06-10 15:18:31.333 D/RILJ_GSM(  418): [3089]< SEND_SMS { messageRef = 232, errorCode = -1, ackPdu = null}
06-10 15:18:31.333 D/SMS     (  418): handleMessage > 2
06-10 15:18:31.333 D/SMS     (  418): pre error Code: -1
06-10 15:18:31.333 D/SMS     (  418): msgRef> 232, trytpmr> 0
06-10 15:18:31.333 D/SMS     (  418): send complete: SmsTracker@411f02c0
06-10 15:18:31.333 D/SMS     (  418): SMS send complete. Broadcasting intent: PendingIntent{411ce330: android.os.BinderProxy@40eb9c80}
06-10 15:18:31.333 D/SMS     (  418): framework sent intent: SMS_MO/number/1402384711344/1
06-10 15:18:31.433 D/RILJ_GSM(  418): [3090]< REPORT_SMS_MEMORY_STATUS
06-10 15:18:31.794 D/RILJ_GSM(  418): [UNSL]< UNSOL_RESPONSE_NEW_SMS
06-10 15:18:31.794 D/RILJ_GSM(  418): RIL_UNSOL_RESPONSE_NEW_SMS pdu : 0891683108200805F0040D91683145189164F70008416001518142230454755475
06-10 15:18:31.794 D/GSM     (  418): SMS SC address: +8613800280500
06-10 15:18:31.794 D/GSM     (  418): SMS SC timestamp: 1402384704000
06-10 15:18:31.804 V/RILC_IMC(  104): processWakeupCallback
06-10 15:18:31.804 D/SMS     (  418): handleMessage > 1
API Demos短信Activity的log
06-10 14:20:05.949 D/SMS     (32003): encoding detail>TextEncodingDetails { msgCount=1, codeUnitCount=13, codeUnitsRemaining=147, codeUnitSize=1, languageTable=0, languageShiftTable=0 }
06-10 14:20:05.959 D/GSM     (32003): SMS status report requested
06-10 14:20:05.959 D/GSM     (32003): laugnagetable/shifttable: 0/0
06-10 14:20:05.959 D/GSM     (32003): GEP countGsmSeptets: 13
06-10 14:20:05.969 D/GSM     (32003): charToLanguageTable/shifttable: android.util.SparseIntArray@4100bf78/android.util.SparseIntArray@41014aa8
06-10 14:20:05.969 D/GSM     (32003): htc septets count/septets: 13/13
06-10 14:20:05.969 D/CDMA    (  418): [RuimSmsInterfaceManager] sendRawPdu: smsc=null pdu=[B@40f6a400 sentIntentPendingIntent{40f6a430: android.os.BinderProxy@40d86ca0} deliveryIntentPendingIntent{40f6a450: android.os.BinderProxy@40d86d00}
06-10 14:20:05.969 D/SMS     (  418): sendRawPduWithBundle
06-10 14:20:05.969 D/SMS     (  418): handleNotInService, message send fail ss : 1

請恕我眼拙,沒能從上面的log裏看出究竟API Demos短信Activity到底哪裏出錯了。

於是繼續Google,發現了不少有意思的東西:

  1. SilentSMS:作者用reflection調用了IccSmsInterfaceManager來操作發送短信。雖然看起來很酷,可App的安裝需要root權限,所以我沒有急着測試這個project;
  2. Android SMS/MMS/Google Voice Sending Library:作者override了很多Android telephony相關的類,還是beta版本。感覺爲了發一個短信而已,用不着這麼大的lib吧?
  3. text+:一款用WIFI來發短信的免費Android App。還有很多類似的產品。其實這類產品已經脫離裏簡簡單單的短信功能了,整個一社交型應用了,國內類似的應用也很多,如微信、QQ等。只不過text+等還是支持將message以SMS發到沒有安裝text+的手機上。

抱怨這麼多,其實就是糾結於爲什麼網上都能用SmsManager這個簡單的API來發短信,而我這邊就是不行?!原因究竟何在?!!!

於是繼續鬱悶地測試,刪除系統短信草稿箱裏的草稿,看到菜單“設置->短信(SMS)”,於是手賤地點進去:

發送報告
爲您發送的每條信息請求一個發送報告
服務中心(卡槽一)
+8613800XXXXXX
服務中心(卡槽二)
管理 UIM 卡信息
管理 CDMA UIM 卡中存儲的信息
管理 SIM 卡信息
管理 GSM SIM 卡中存儲的信息

發送報告,唔,這個勾沒打,估計會收不到delivery回饋……卡槽一卡槽二,唔,我這個是雙卡雙待的手機,是有兩個卡槽的…………wait,我了個去的,不會吧,難道是因爲我這個雙卡雙待的手機沒有插電信的卡而電信的卡又是主卡SmsManager就TMD直接連到主卡上然後報錯了吧?!SmsManager,你能更brief點嗎?

立馬找同事的單卡手機跑了下API Demos,短信發送成功……

又找了另一個同事的手機,雙卡雙待,副卡槽空的,主卡槽是電信的,插了電信卡,跑API Demos,短信發送成功……

心中那個神獸奔騰啊

Google了三天,看了一堆資料,原來是這個原因……


OK,現在問題明朗了,後面的流程就是找找怎麼在雙卡雙待而且只插了一張卡或菏澤插了兩張卡、三張卡的手機上用SmsManager發、短、信。


找了一圈,發現還是要用reflection發掘SmsManager的隱藏API,寫了個reflect的工具:


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;

import android.util.Log;

public class ClassSpy {

    private static final String TAG = "ClassSpy";

    private static void printMembers(Member[] mbrs, String s) {
        Log.d(TAG, s);
        for (Member mbr : mbrs) {
            if (mbr instanceof Field) {
                Log.d(TAG, "    " + ((Field) mbr).toGenericString());
            } else if (mbr instanceof Constructor) {
                Log.d(TAG, "    " + ((Constructor) mbr).toGenericString());
            } else if (mbr instanceof Method) {
                Log.d(TAG, "    " + ((Method) mbr).toGenericString());
            }
        }
        if (mbrs.length == 0) {
            Log.d(TAG, "      -- No " + s);
        }
    }

    private static void printClasses(Class<?> c) {
        Log.d(TAG, "Classes:");
        Class<?>[] clss = c.getClasses();
        for (Class<?> cls : clss) {
            Log.d(TAG, "    " + cls.getCanonicalName());
        }
        if (clss.length == 0) {
            Log.d(TAG, "      -- No member interfaces, classes, or enums --");
        }
    }

    public static void showInfos(List classNames) {
        for (String clsname : classNames) {
            Class<?> c;
            try {
                c = Class.forName(clsname);
                Log.d(TAG, "--------------------------------------------------------------------------");
                Log.d(TAG, "Class:" + clsname);
                Package p = c.getPackage();
                Log.d(TAG, "Package:" + (p != null ? p.getName() : "-- No Package --"));
                printMembers(c.getConstructors(), "Constuctors");
                printMembers(c.getFields(), "Fields");
                printMembers(c.getMethods(), "Methods");
                printClasses(c);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void test() {
        List clsnames = new ArrayList();
        clsnames.add("android.telephony.TelephonyManager");
        clsnames.add("android.telephony.SmsManager");
        showInfos(clsnames);
    }
}

同時dump了Android TelephonyManager所有方法的返回值,發現一些有用的信息:

 tm.getCallState()=CALL_STATE_IDLE
 tm.getDataActivity()=DATA_ACTIVITY_NONE
 tm.getDataState()=DATA_DISCONNECTED
 tm.getDeviceSoftwareVersion()=00
 tm.getNeighboringCellInfo()=[]
 tm.getNetworkCountryIso()=cn
 tm.getNetworkOperator()=46000
 tm.getNetworkOperatorName()=中國移動
 tm.getNetworkType()=NETWORK_TYPE_GPRS
 tm.getPhoneType()=PHONE_TYPE_GSM
 tm.getSimCountryIso()=cn
 tm.getSimOperator()=46000
 tm.getSimOperatorName()=CMCC
 tm.getSimState()=SIM_STATE_READY
 tm.getVoiceMailAlphaTag()=語音信箱
 tm.getVoiceMailNumber()=null
 tm.hasIccCard()=true
 tm.isNetworkRoaming()=false

參考了《android 雙卡雙待 發送短信 》,用reflect出來的SmsManager的send方法還是發送失敗。

暫時不研究了,至少目前單卡機上是可以發送短信的,雙卡雙待機就用walkround吧:

  1. 用Android公開的SmsManager方法發送短信
  2. 如果上一步失敗,就用reflect出來的SmsManager方法發送短信
  3. 如果還是失敗,就用Intent啓動本地SMS應用發短信

To be continued

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章