当前位置: 首页 > news >正文

Android 4.x–6.x短信功能快速验证包:含可运行APK与Eclipse工程源码

本文还有配套的精品资源,点击获取

简介:直接安装就能测试短信收发的Android工程,内置ex_SMS.apk,支持读取收件箱、手动发送短信、接收新消息三大基础功能。源码结构清晰,Java代码放在src/com/ouling下,main.xml定义界面布局,strings.xml管理文本内容,AndroidManifest.xml已预配READ_SMS、SEND_SMS、RECEIVE_SMS等必要权限和BroadcastReceiver声明。编译产物齐全:classes.dex、resources.ap_、proguard.cfg混淆配置,以及ldpi/mdpi/hdpi三套ic_launcher.png图标。所有资源按标准Eclipse ADT项目组织,无需额外配置即可导入调试,适合在Android 4.0到6.0真机或模拟器上快速验证短信API调用逻辑、排查权限问题或作为二次开发起点。

1. 项目概述:为什么这个“老系统短信包”至今仍有不可替代的价值

你手头正调试一台Android 4.4的工控面板,或者在维护一批还在跑Android 5.1的车载终端设备,突然发现系统级短信功能异常——发不出去、收不到回执、收件箱读取为空。这时候,你不会想去翻Android 12的文档,也不会指望用Android Studio新建一个空项目再一点点堆权限和广播接收器。你需要的是:三分钟内装上、五秒钟内点开、十秒内验证“是不是API调用本身出了问题”。这个ex_SMS.apk包,就是为这种场景而生的。

它不是教学Demo,不是炫技样板,而是一把被磨得锃亮的螺丝刀——专治Android 4.x到6.x(即Ice Cream Sandwich到Marshmallow)这一段被主流开发社区逐渐淡忘、却仍在大量工业设备、老旧POS机、定制化教育终端中稳定服役的系统层短信顽疾。关键词里反复出现的“Eclipse工程”不是怀旧,而是精准锚定:那个年代的ADT插件、ant编译链、R.java自动生成机制、以及对AndroidManifest.xml中<uses-permission><receiver>声明近乎苛刻的耦合要求,决定了它无法简单迁移到现代AS环境,但恰恰是这套“过时”结构,让它成为排查兼容性问题最干净的对照组。

我做过不下二十个现场支持,客户说“短信发不出”,第一反应不是看代码,而是立刻把ex_SMS.apk推到设备上。如果它能发,说明系统短信服务正常、SIM卡状态OK、基带通信链路通畅;如果它也发不出,那问题大概率出在硬件层或运营商配置;如果它能发但客户的App不能,那90%是权限声明遗漏、targetSdkVersion误设导致运行时权限弹窗缺失,或是BroadcastReceiver未正确注册。这个包的价值,不在于它多先进,而在于它把所有变量都锁死在一个已知、可控、可复现的状态里——src下只有3个Java类,res里没有一行多余资源,AndroidManifest.xml里连一个注释都没有,干干净净,像一张白纸,让你一眼看清自己写的逻辑到底卡在哪一步。

它解决的不是“如何写短信功能”的问题,而是“如何快速排除短信功能不是我的问题”的问题。尤其当你面对的是一个连adb logcat都打不出完整日志的老设备,或者客户只给你一台锁Bootloader的机器时,这个APK就是你的听诊器和探针。

2. 整体设计思路拆解:为何坚持Eclipse结构与4.x–6.x限定

2.1 为何不升级到Android Studio?——兼容性即生命线

看到“Eclipse工程”四个字,很多新入行的开发者第一反应是“太老了,该淘汰了”。但在这个特定场景下,放弃Eclipse不是进步,而是自断经脉。原因很实在:

  • 编译链差异直接导致行为偏移:Android 4.x–6.x的SDK Build Tools(如23.0.3)与现代Gradle插件(7.0+)在资源打包(aapt vs aapt2)、Dex生成(dx vs d8)、甚至R.java常量生成顺序上存在细微但致命的差异。我们曾尝试将此工程用AS导入并启用“Use legacy Eclipse project structure”,结果在Android 5.0真机上,getMessagesFromSim()返回空列表——查了一整天,最终发现是aapt2对<string-array>资源的索引处理方式不同,导致R.array.sim_messages指向了错误内存地址。而原版ant build用dx生成的classes.dex,在同一台设备上运行完美。这不是代码bug,是工具链的“时代错位”。

  • 权限模型过渡期的精确模拟:Android 6.0(API 23)是运行时权限的分水岭,但它的引入是渐进式的。这个包明确限定到6.x,意味着它必须同时兼容两种模式:对于targetSdkVersion ≤ 22的App,所有权限在安装时授予;对于targetSdkVersion = 23且运行在6.0+设备上的App,则需手动请求。工程中AndroidManifest.xml里预置了READ_SMSSEND_SMSRECEIVE_SMS三个权限,并在主Activity的onCreate()里做了Build.VERSION.SDK_INT >= 23的判断分支,仅对高版本设备动态申请。这种“一刀切”的兼容写法,在AS的现代模板里会被警告为“冗余”,但在真实产线环境中,它确保了同一份APK能在4.4平板和6.0车载屏上都稳定工作——你不需要为每个设备单独编译。

  • Eclipse ADT的“确定性”优势:ADT的Project Properties → Android → “Is Library”勾选、project.propertiestarget=android-19的硬编码、ant debug命令的输出日志格式……这些看似笨拙的设定,恰恰提供了100%可复现的构建环境。我在客户现场用一台Windows 7笔记本(连Java 8都装不上),装好ADT 23.0.7,导入工程,ant debug,生成的APK和我本地Mac上生成的SHA256完全一致。而AS项目,光是Gradle Wrapper版本、JDK路径、NDK配置就足以让两次build产出不同的APK签名。对于需要向客户交付“可验证二进制”的场景,这种确定性比任何自动化都重要。

2.2 为何只做“读、发、收”三大功能?——聚焦核心,拒绝干扰

这个包里没有联系人选择器、没有短信模板、没有长短信自动拆分、没有MMS支持。它只做三件事:

  1. 读取收件箱(Inbox):调用ContentResolver.query()查询content://sms/inbox,提取addressbodydate字段,按时间倒序展示在ListView中;
  2. 手动发送短信:用户输入号码和内容,点击按钮后调用SmsManager.getDefault().sendTextMessage(),并监听PendingIntent获取发送状态;
  3. 接收新消息:注册<receiver android:name=".SMSReceiver">,在onReceive()中解析intent.getExtras().get("pdus"),提取发件人和内容并Toast提示。

为什么砍掉所有“增值功能”?因为每一个附加功能都是潜在的故障点。比如加入联系人选择,就需要READ_CONTACTS权限,而该权限在6.0+设备上与短信权限是独立申请的,一旦客户忘记授权,整个界面就卡死在空列表;比如加入长短信拆分,SmsManager.divideMessage()在某些国产ROM(如早期华为EMUI 3.x)上存在兼容性Bug,会导致发送失败但无异常抛出,排查起来极其隐蔽。我们刻意把功能压到最小原子单元,就是为了确保:当你说“收件箱读不出来”,那问题100%出在content://sms/inbox的URI访问、READ_SMS权限或ContentProvider是否被厂商禁用上,而不是被某个无关的UI组件拖了后腿。

这背后是一种工程哲学:在不确定的环境中,降低系统的熵值,是提升诊断效率最有效的手段。就像汽车维修手册的第一步永远是“检查保险丝”,而不是直接拆发动机。

2.3 目录结构精简逻辑:每一处都服务于“零配置导入”

你看到的目录树里没有gen/、没有bin/、没有.gradle/,只有src/res/AndroidManifest.xml等标准ADT结构。这种“裸奔式”组织,是经过无数次客户现场踩坑后沉淀下来的:

  • proguard.cfg文件存在,但内容极简:-keep class com.ouling.** { *; }。它不混淆任何东西,只为满足project.propertiesproguard.config=proguard.cfg的强制要求。很多客户在导入时会因ProGuard配置缺失报错,加这一行,省去所有解释成本。
  • resources.ap_classes.dex这两个编译产物被明确放入包中,不是为了让你直接安装(ex_SMS.apk才是安装包),而是为了让你在导入Eclipse后,能立刻右键项目 →Run As → Android Application,无需等待ADT漫长的自动编译——因为ant debug早已把它们生成好了。这对在客户会议室里演示,节省了关键的3分钟。
  • drawable-ldpi/mdpi/hdpi下的ic_launcher.png全部提供,且尺寸严格遵循规范(48x48, 72x72, 96x96)。我们测试过,某些Android 4.2的山寨平板,如果缺少ldpi图标,启动器图标会显示为白方块,进而让用户误以为App崩溃。这不是美观问题,是信任问题。

这个结构,本质上是一个“防呆设计”(Poka-Yoke):它假设使用者可能不熟悉ADT,可能网络受限无法下载SDK,可能连Java环境都没配好。所以,所有依赖都被显式固化,所有路径都符合ADT默认约定,所有配置都做到“打开即用”。

3. 核心细节解析与实操要点:从源码到Manifest的逐行深挖

3.1 Java源码:三个类的职责边界与关键陷阱

整个src/com/ouling/目录下只有三个Java文件:MainActivity.javaSMSReceiver.javaSMSHelper.java。它们的分工极其清晰,且每一行都藏着针对老系统的适配细节。

MainActivity.java—— 界面与主逻辑中枢

这是唯一继承Activity的类,负责加载main.xml布局、初始化控件、处理按钮点击。关键点在于权限申请和短信读取:

// 权限动态申请(仅对API 23+) if (Build.VERSION.SDK_INT >= 23) { String[] permissions = {Manifest.permission.READ_SMS, Manifest.permission.SEND_SMS}; if (checkSelfPermission(permissions[0]) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(permissions[1]) != PackageManager.PERMISSION_GRANTED) { requestPermissions(permissions, REQUEST_CODE_ASK_PERMISSIONS); return; } } // 注意:这里没有else分支!因为API < 23时,权限已在安装时授予,无需额外操作

这段代码的精妙之处在于“无感兼容”。它不试图用ContextCompat.checkSelfPermission()这种新API去覆盖旧系统(那需要support-v4库,增加包体积),而是用原生Activity.checkSelfPermission(),并严格限定在>=23分支内。更重要的是,它没有在onRequestPermissionsResult()里做复杂的权限状态同步,而是采用最朴素的方式:申请回调后,直接finish()当前Activity,然后startActivity(getIntent())重启自己。这样做的好处是,重启后的onCreate()会重新执行权限检查,如果用户授予权限,流程自然继续;如果拒绝,checkSelfPermission()返回PERMISSION_DENIED,Activity会Toast提示并停留在界面——用户能清晰看到“权限被拒绝”,而不是App静默失败。

短信读取部分的坑更深

Cursor cursor = getContentResolver().query( Uri.parse("content://sms/inbox"), new String[]{"_id", "address", "body", "date"}, null, null, "date DESC LIMIT 50" );

这里用了硬编码URI"content://sms/inbox",而非Telephony.Sms.Inbox.CONTENT_URI。原因?后者在Android 4.0(API 14)才引入,而我们的最低支持是4.x(即API 14+),看似没问题。但某些深度定制ROM(如联想K900的VIBE UI 1.5)会重写Telephony.Sms类,导致静态常量指向错误URI。硬编码URI虽然不优雅,但绕过了所有类加载风险,是经过20+款老机型实测的“土办法”。

SMSReceiver.java—— 广播接收器的生命周期守门人

这是一个继承BroadcastReceiver的类,核心逻辑在onReceive()中。最关键的两行是:

// 必须在onReceive()内完成所有操作,不能startService或发通知(Android 4.0+限制) // 所以这里只做最轻量的事:解析PDU,Toast提示,存入本地DB(可选) Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); if (pdus != null) { for (Object pdu : pdus) { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); String sender = sms.getOriginatingAddress(); String body = sms.getMessageBody(); Toast.makeText(context, "新短信来自:" + sender + ":" + body, Toast.LENGTH_LONG).show(); // 此处可添加insert into local SQLite DB逻辑 } } }

这里埋着一个老系统经典陷阱:SmsMessage.createFromPdu()在Android 4.4(API 19)之前,对某些CDMA制式手机(如早期中国电信定制机)解析pdus数组时会抛NullPointerException。解决方案不是try-catch(那会掩盖真正问题),而是在调用前加双重判空:

if (pdus != null && pdus.length > 0) { for (Object pdu : pdus) { if (pdu != null) { // 关键!防止pdu本身为null try { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); // ... 解析逻辑 } catch (Exception e) { Log.e("SMSReceiver", "PDU parse failed", e); } } } }

这个if (pdu != null)判断,是我们在一台Android 4.2的酷派8720L上抓Logcat抓了三天才定位到的——它的基带固件在弱信号下会发出空PDU帧。

SMSHelper.java—— 工具类里的“安全网”

这个类封装了发送短信的通用方法,核心是sendSMS()

public static void sendSMS(Context context, String phoneNumber, String message, PendingIntent sentIntent, PendingIntent deliveryIntent) { SmsManager smsManager = SmsManager.getDefault(); // 针对长短信的自动拆分(最大70字符/条,UCS2编码) if (message.length() > 70) { ArrayList<String> parts = smsManager.divideMessage(message); ArrayList<PendingIntent> sentIntents = new ArrayList<>(); ArrayList<PendingIntent> deliveryIntents = new ArrayList<>(); for (int i = 0; i < parts.size(); i++) { sentIntents.add(sentIntent); deliveryIntents.add(deliveryIntent); } smsManager.sendMultipartTextMessage(phoneNumber, null, parts, sentIntents, deliveryIntents); } else { smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, deliveryIntent); } }

这里的关键是divideMessage()的使用时机。很多开发者以为“只要消息长就拆分”,但sendMultipartTextMessage()在Android 4.0–4.3的部分机型(如三星Galaxy S3 I9300)上存在内存泄漏,连续发送3次以上会导致App ANR。因此,我们在实际调用中加了长度阈值控制:

// 实际调用处(MainActivity) if (message.length() > 140) { // 提高阈值,避免频繁触发multipart SMSHelper.sendSMS(this, number, message, sentPI, deliveryPI); } else { SmsManager.getDefault().sendTextMessage(...); // 直接走单条 }

这个140的阈值,是平衡了Unicode字符支持(70 UCS2字符 ≈ 140 ASCII字符)和老系统稳定性后得出的经验值。

3.2 布局与资源:main.xmlstrings.xml的务实主义

res/layout/main.xml是一个极简的LinearLayout,垂直排列:一个EditText(输入号码)、一个EditText(输入内容)、两个Button(发送、刷新收件箱)、一个ListView(显示短信)。没有Fragment,没有RecyclerView(4.x不原生支持),没有ConstraintLayout(那是2016年的事)。它的价值在于“所见即所得”——你改一行XML,保存,ant debug,新APK里立刻生效,没有任何中间缓存或编译优化干扰。

res/values/strings.xml里定义了所有文本,包括一个容易被忽略的关键项:

<string name="app_name">ex_SMS</string> <string name="hint_number">请输入手机号</string> <string name="hint_message">请输入短信内容</string> <string name="btn_send">发送</string> <string name="btn_refresh">刷新收件箱</string> <string name="toast_sms_sent">短信已发送</string> <string name="toast_sms_failed">短信发送失败</string> <string name="permission_rationale">需要短信权限才能收发信息</string> <!-- 这个字符串至关重要 --> <string name="no_sms_permission">未授予短信权限,请在设置中开启</string>

no_sms_permission这个字符串,是给用户看的最终提示。我们测试发现,在Android 5.1的小米红米Note 3上,如果用户拒绝权限,系统Toast只会显示“App needs permission”,用户根本不知道要开哪个权限。而我们在这里明确写出“请在设置中开启”,并在MainActivity的权限拒绝分支里直接调用:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent);

这行代码会直接跳转到本App的权限管理页面,用户只需点一下开关即可。这是老系统时代最有效的“权限引导”方案,比任何文字说明都管用。

3.3AndroidManifest.xml:权限与组件声明的教科书级范本

这个文件是整个工程的“宪法”,每一行都经过千锤百炼:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ouling.exsms" android:versionCode="1" android:versionName="1.0" > <!-- 最低支持Android 4.0 (API 14) --> <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" /> <!-- 三大核心权限,缺一不可 --> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <!-- 可选:如果你需要监听短信送达状态 --> <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.Light" > <!-- 主Activity,必须声明LAUNCHER --> <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 短信接收器,exported必须为true(Android 4.x要求) --> <receiver android:name=".SMSReceiver" android:enabled="true" android:exported="true" > <intent-filter android:priority="1000" > <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> </application> </manifest>

重点解析几个易错点:

  • android:exported="true":这是Android 4.x的硬性要求。从Android 5.0开始,exported默认为false,但4.x不识别该属性,必须显式设为true,否则广播接收器根本收不到SMS_RECEIVED事件。我们曾在一个Android 4.4的飞利浦电视上,因为漏了这行,折腾了两天。

  • <intent-filter>里的android:priority="1000":数值越大优先级越高。系统短信App通常设为1000,我们设成1000是为了确保我们的Receiver能第一时间收到广播,避免被其他App拦截。注意:这个值在Android 8.0+被废弃,但对4.x–6.x是救命稻草。

  • android:targetSdkVersion="23":明确指向Android 6.0,这是触发运行时权限模型的开关。如果设成22,即使在6.0设备上,App也会以“旧模式”运行,权限在安装时授予,无法测试动态申请逻辑。

  • android:theme="@android:style/Theme.Light":不用AppCompat主题,是因为4.x原生就不支持。用系统原生Light主题,确保在所有设备上UI渲染一致,避免因主题兼容性导致的ClassNotFoundException

4. 实操过程与核心环节实现:从导入到真机验证的全流程

4.1 Eclipse环境准备:ADT 23.0.7是最稳组合

别试图用最新版ADT或Eclipse。经过实测,Eclipse Kepler SR2(4.3.2) + ADT 23.0.7 + JDK 7u80是兼容性最好的黄金组合。原因如下:

  • ADT 23.0.7是最后一个全面支持Android 6.0 SDK的ADT版本,后续版本(23.0.8+)开始逐步移除对旧构建工具的支持;
  • Eclipse Kepler SR2的Workspace对project.properties文件的解析最稳定,不会像Oxygen版本那样,因UTF-8 BOM问题导致target=android-19读取失败;
  • JDK 7u80是Oracle官方为Android构建链最后认证的JDK 7版本,JDK 8在dx工具中会出现Unsupported major.minor version 52.0错误。

安装步骤(Windows为例):

  1. 下载Eclipse Kepler SR2 Classic版(eclipse-kepler-SR2-win32.zip),解压到C:\eclipse\
  2. 下载ADT 23.0.7插件(adt-bundle-windows-x86_64-201407022030.zip),解压后得到sdk/eclipse/文件夹;
  3. adt-bundle-windows-x86_64-201407022030\eclipse\plugins\下的所有jar包,复制到C:\eclipse\plugins\
  4. 启动C:\eclipse\eclipse.exe,首次启动会提示选择Workspace,选一个空文件夹(如C:\workspace_sms);
  5. Window → Preferences → Android,点击Browse...,指向adt-bundle-windows-x86_64-201407022030\sdk\
  6. Android → DDMS → ADB connection timeout,改为10000(毫秒),适应老设备连接慢的问题。

提示:如果启动Eclipse时报Failed to load the JNI shared library jvm.dll,说明JDK路径不对。务必在C:\eclipse\eclipse.ini文件开头添加:
-vm C:\Program Files\Java\jdk1.7.0_80\bin\javaw.exe

4.2 工程导入与零配置调试

将下载的资源包解压到C:\workspace_sms\ex_sms_project\(路径不能含中文或空格!)。然后:

  1. File → Import → General → Existing Projects into Workspace
  2. Select root directory,浏览到C:\workspace_sms\ex_sms_project\
  3. 勾选ex_sms_project,取消勾选Copy projects into workspace(我们就是要原地调试);
  4. 点击Finish

此时,Eclipse会自动识别这是一个ADT项目,project.properties里的target=android-19会被读取,并在Package Explorer中显示黄色感叹号——这是正常的,因为SDK 19(Android 4.4)可能未安装。

解决方法:

  1. Window → Android SDK Manager
  2. 展开Android 4.4.2 (API 19),勾选SDK PlatformIntel x86 Atom System Image(如果要用模拟器);
  3. 点击Install packages,等待安装完成;
  4. 关闭SDK Manager,右键项目 →Properties → Android,确认Project Build Target已选中Android 4.4.2
  5. 右键项目 →Android Tools → Fix Project Properties(自动修复lib引用和R.java生成)。

现在,你可以直接右键项目 →Run As → Android Application。Eclipse会自动执行ant debug,生成bin/ex_SMS-debug.apk,并尝试安装到已连接的设备或模拟器上。

注意:如果设备是Android 6.0+,首次安装后会弹出权限申请对话框,务必点击“允许”。如果没弹出,说明targetSdkVersion没生效,检查AndroidManifest.xml

4.3 真机验证四步法:快速定位问题根源

安装ex_SMS.apk后,不要急着点“发送”。按以下顺序验证,每一步都是关键诊断节点:

第一步:检查收件箱(Read)
- 打开App,点击“刷新收件箱”;
- 观察ListView是否显示历史短信;
- 如果为空,立即打开adb shell,执行:
bash adb shell content query --uri content://sms/inbox --projection address:body:date
如果返回数据,说明ContentProvider正常,问题在App的Cursor查询逻辑;如果返回SecurityException,说明READ_SMS权限未授予;如果返回空,说明系统短信数据库损坏(罕见,需恢复出厂设置)。

第二步:发送测试(Send)
- 在输入框填入一个已知能收短信的号码(如自己的另一台手机),内容写“test_442”;
- 点击“发送”,观察Toast提示;
- 如果提示“短信已发送”,但对方没收到,用adb logcat | grep SmsManager查看日志,重点找SmsManager: sendTextMessageSmsSender: sent result
- 如果提示“短信发送失败”,检查adb shell dumpsys telephony.registry,看mServiceState是否为IN_SERVICE,如果不是,说明基带未注册网络。

第三步:接收验证(Receive)
- 用另一台手机给测试机发一条短信;
- 观察App是否弹出Toast;
- 如果没弹出,检查adb logcat | grep SMSReceiver,看是否有onReceive called日志;
- 如果有日志但没Toast,说明Toast.makeText()执行了但被系统拦截(某些ROM会屏蔽非系统App的Toast),此时应检查SMSReceiver.java里是否误加了abortBroadcast()

第四步:权限快检(Permission)
- 进入手机设置 → 应用 → ex_SMS → 权限
- 确认短信权限为“允许”;
- 如果是Android 6.0+,还要检查电话权限(某些ROM将短信权限归类到电话下);
- 如果权限开关是灰色不可点,说明该权限被设备管理员策略禁用(常见于企业MDM管控设备),需联系IT部门。

这套四步法,能在5分钟内区分问题是出在App代码、系统权限、基带通信还是ROM定制上,是现场支持的黄金准则。

4.4 模拟器专项适配:AVD配置要点

Android 4.x–6.x的模拟器(AVD)对短信功能支持不一,必须按以下配置:

  1. AVD Manager → Create Virtual Device,选择Nexus 4(屏幕适配好);
  2. System Image,选择Android 4.4.2 (API 19)Android 6.0 (API 23)x86镜像(不要选ARM,太慢);
  3. Next → Next → Finish
  4. 启动AVD后,在Eclipse中Run As → Android Application,选择该AVD;
  5. 关键一步:AVD启动后,在电脑端命令行执行:
    bash telnet localhost 5554 # 连上后输入: sms send 13800138000 hello from emulator
    这会向模拟器发送一条短信,触发SMSReceiver

注意:模拟器的短信发送功能在Android 5.0+的x86镜像中有时失效。如果telnet命令无响应,换用Android 4.4.2 x86镜像,或改用Android 6.0 ARM镜像(牺牲速度保功能)。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 典型问题速查表

问题现象可能原因排查命令/步骤解决方案
安装失败:“INSTALL_FAILED_DEXOPT”设备存储空间不足,或Dalvik缓存损坏adb shell df -h查看/data空间;adb shell rm -rf /data/dalvik-cache/*清理存储,或重启设备
点击“发送”无反应,Logcat无日志SEND_SMS权限未授予,且App未做权限检查adb shell pm list permissions -g \| grep smsadb shell pm grant com.ouling.exsms android.permission.SEND_SMS手动授予权限,或检查MainActivity权限申请逻辑
收件箱始终为空,但adb content query有数据CursorAdapter未正确绑定,或ListView未调用setAdapter()检查MainActivity.java第87行listView.setAdapter(adapter)是否被注释取消注释,确保Adapter初始化后立即绑定
短信能发,但收不到送达回执(deliveryIntent不触发)运营商不支持SMSC回执,或目标手机关闭了送达报告adb logcat \| grep "SMSDispatcher"查看底层发送日志改用sendTextMessage()sentIntent监听发送成功,放弃deliveryIntent
Android 6.0设备上,权限申请对话框不弹出targetSdkVersion未设为23,或AndroidManifest.xml<uses-sdk>标签缺失aapt dump badging ex_SMS.apk \| grep sdk确保AndroidManifest.xml中有<uses-sdk android:targetSdkVersion="23" />
图标在Android 4.2设备上显示为白方块缺少drawable-ldpi/ic_launcher.png,或图片尺寸不对adb shell ls /data/data/com.ouling.exsms/files/检查资源包中drawable-ldpi/是否存在,且图片为48x48 PNG

5.2 独家避坑技巧:来自二十次现场支持的血泪总结

技巧一:adb shell input keyevent代替手动点击
在客户设备上,如果触摸屏失灵或需要批量测试,可以用ADB命令模拟操作:

# 模拟点击“刷新收件箱”按钮(假设它是界面上第二个Button) adb shell input tap 500 800 # 或更精准:先用uiautomatorviewer获取坐标 adb shell uiautomator dump /sdcard/window.xml adb pull /sdcard/window.xml

这比让客户一次次点屏幕高效十倍,且可写成批处理脚本。

技巧二:adb backup导出短信数据库做离线分析
当客户说“收件箱数据丢了”,不要只信他。用以下命令导出系统短信DB:

adb backup -f sms_backup.ab -noapk com.android.providers.telephony # 将backup.ab转为tar(需dd命令) dd if=sms_backup.ab bs=24 skip=1 \| python -c "import sys; sys.stdout.write(sys.stdin.read()[20:])" > sms.tar tar -xf sms.tar # 得到db文件:apps/com.android.providers.telephony/db/mmssms.db

用SQLite Browser打开mmssms.db,直接查messages表,真相立现。我们曾用这招发现,某款POS机的ROM会在每天凌晨3点自动清空inbox表,根本不是App的问题。

技巧三:proguard.cfg里加-dontobfuscate保命
虽然工程里proguard.cfg只有一行-keep,但如果你要二次开发并启用混淆,务必加上:

-dontobfuscate -dontoptimize -keepattributes Signature

原因:老系统dx工具对混淆后的字节码兼容性差,-dontobfuscate能保证类名方法名不变,避免ClassNotFoundException。这是用无数个VerifyError换来的教训。

技巧四:AndroidManifest.xml里加android:debuggable="true"
<application>标签里加上:

android:debuggable="true"

这能让adb logcat捕获到更多详细日志,尤其在onReceive()里加Log.d()时。虽然发布版要删掉,但调试阶段,它比断点还管用。

技巧五:用adb shell getprop查ROM指纹
当一切正常但功能异常时,终极排查是查ROM:

adb shell getprop ro.build.fingerprint # 输出类似:samsung/jfltexx/jflte:4.4.2/KOT49H/I9505XXUHNK1:user/release-keys

把指纹发给ROM厂商,他们能立刻告诉你,这个版本是否禁用了content://smsURI。我们曾发现,某款华为B199定制ROM,把TelephonyProviderreadPermission设为null,导致所有App都无法读取短信——这是厂商的硬编码限制,非App之过。

6. 二次开发与扩展建议:如何安全地在此基础上迭代

这个包不是终点,而是起点。如果你需要基于它做二次开发,请牢记三条铁律:

铁律一:绝不修改AndroidManifest.xml的权限声明顺序
READ_SMS必须在SEND_SMS之前,RECEIVE_SMS必须在最后。某些ROM(如vivo Funtouch OS 2.5)的权限管理器会按声明顺序校验,顺序错一位,RECEIVE_SMS就会被静默拒绝。我们曾为此重构过三次权限模块。

铁律二:新增功能必须通过Intent解耦,而非直接调用
比如你要加“群发短信”功能,不要在MainActivity里写循环发送逻辑。而是:
1. 新建GroupSendActivity
2. 在AndroidManifest.xml中声明;
3. 从MainActivitystartActivity(new Intent(this, GroupSendActivity.class))跳转;
4.GroupSendActivity里独立处理群发逻辑。

这样做的好处是,如果群发功能出Bug,不影响主流程;而且可以单独测试、单独发布补丁APK。

铁律三:所有网络请求必须用HttpURLConnection,禁用OkHttp/retrofit
Android 4.x的HttpsURLConnection底层是OpenSSL,而OkHttp 3.x+默认用Conscrypt,在4.4设备上会NoClassDefFoundError。如果必须联网(如短信网关对接),用原生HttpURLConnection,并手动处理SSL证书:

URL url = new URL("https://api.example.com/sms"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(getCustomSSLSocketFactory()); // 自定义工厂

最后分享一个小技巧:这个包的ex_SMS.apk签名密钥是debug.keystore(密码android),如果你要发布正式版,务必用keytool生成新密钥,并在ant.properties里指定:

key.store=release.keystore key.alias=my_alias key.store.password=your_password key.alias.password=your_password

否则,用户升级时会因签名不一致而安装失败——这是所有二次开发者必踩的第一个坑。

我在实际使用中发现,最高效的二次开发方式,是把它当作一个“短信能力探针”。先用它验证设备基础功能,再把它的SMSHelper.javaSMSReceiver.java复制到你的主项目中,替换掉你原来写的、可能有兼容性问题的短信模块。这样,你继承的不是代码,而是经过20+款老机型锤炼的、关于“如何在碎片化Android世界里让短信可靠工作”的全部经验。

本文还有配套的精品资源,点击获取

简介:直接安装就能测试短信收发的Android工程,内置ex_SMS.apk,支持读取收件箱、手动发送短信、接收新消息三大基础功能。源码结构清晰,Java代码放在src/com/ouling下,main.xml定义界面布局,strings.xml管理文本内容,AndroidManifest.xml已预配READ_SMS、SEND_SMS、RECEIVE_SMS等必要权限和BroadcastReceiver声明。编译产物齐全:classes.dex、resources.ap_、proguard.cfg混淆配置,以及ldpi/mdpi/hdpi三套ic_launcher.png图标。所有资源按标准Eclipse ADT项目组织,无需额外配置即可导入调试,适合在Android 4.0到6.0真机或模拟器上快速验证短信API调用逻辑、排查权限问题或作为二次开发起点。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/1149413/

相关文章:

  • wvp-GB28181-pro架构深度解析:基于Java的国标视频监控平台设计与高并发实现
  • SharpKeys:彻底改变你的Windows键盘体验——免费开源键盘重映射终极指南
  • LV3296与MK24FN1M0VDC12在工业数据采集中的应用
  • 龙虾与马:Qwen+Marvis+Kimi WebBridge语义自动化实战
  • DVWA靶场——Insecure CAPTCHA(不安全的验证码)
  • Openclaw Windows 部署指南:解决 PowerShell 策略与 Node.js ESM 兼容性问题
  • 若依系统 Druid 弱口令检测:FOFA/Hunter 资产收集与 5 类路径探测实战
  • 如何轻松突破网盘限速:八大平台直链下载的完整解决方案
  • Anaconda虚拟环境实战:可复现依赖管理四阶段流水线
  • Hermes Agent三大核心配置:SOUL/USER/AGENTS详解
  • Zotero 9架构升级与安装避坑指南:Electron+Rust、双因素认证与系统兼容性
  • 单VLA模型驱动异构机器人协同:从语义鸿沟到行为契约的工业落地
  • Okbiye 一站式毕业论文 AI 写作工具,彻底告别熬夜改稿难题
  • Cursor Plan Mode误删生产库的9秒灾难与七层防护实战
  • VS Code AI Agent 协同架构:ACP 协议实战落地
  • Sunshine游戏串流完整指南:5步打造高效家庭游戏共享平台
  • Windsurf与Cursor深度对比:MCP协议与Tab预测技术解析
  • Qwen Code+Obsidian构建工程师知识操作系统
  • Codex API编排中枢:契约驱动的第三方API接入指南
  • C语言实现的MPEG-2编解码三件套:编码器、解码器、播放器全齐,带测试样例和跨平台编译支持
  • Matlab版双向LSTM多变量时序预测工具包(含实测数据与评估结果图)
  • 设备树 Overlay 机制实战详解:运行时动态加载硬件配置而不重新编译内核的方案
  • OLMo 3源码深度解析:从安装、准备到可调试训练架构
  • CodeBERT vs. GPT-4:3 种代码智能任务实战对比与微调策略选择
  • 学生可用的Python车牌识别实战项目:检测定位+字符识别全流程可运行代码包
  • OpenClaw龙虾:面向本地化部署的AI Agent运行时框架
  • VISTA框架:面向腕式可穿戴的物理可行VLA系统
  • FModel终极指南:如何安全高效地探索Unreal Engine游戏资源
  • 2026年房地产宣传片行业观察
  • Codex++上下文自动压缩:基于AST的轻量级代码蒸馏方案