双卡手机发送短信 - 坑爹的双卡双待

最近要写一个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

你可能感兴趣的:(Android,Development)