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

📅 2026/7/8 18:40:12
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.0API 23是运行时权限的分水岭但它的引入是渐进式的。这个包明确限定到6.x意味着它必须同时兼容两种模式对于targetSdkVersion ≤ 22的App所有权限在安装时授予对于targetSdkVersion 23且运行在6.0设备上的App则需手动请求。工程中AndroidManifest.xml里预置了READ_SMS、SEND_SMS、RECEIVE_SMS三个权限并在主Activity的onCreate()里做了Build.VERSION.SDK_INT 23的判断分支仅对高版本设备动态申请。这种“一刀切”的兼容写法在AS的现代模板里会被警告为“冗余”但在真实产线环境中它确保了同一份APK能在4.4平板和6.0车载屏上都稳定工作——你不需要为每个设备单独编译。Eclipse ADT的“确定性”优势ADT的Project Properties → Android → “Is Library”勾选、project.properties里targetandroid-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支持。它只做三件事读取收件箱Inbox调用ContentResolver.query()查询content://sms/inbox提取address、body、date字段按时间倒序展示在ListView中手动发送短信用户输入号码和内容点击按钮后调用SmsManager.getDefault().sendTextMessage()并监听PendingIntent获取发送状态接收新消息注册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.properties里proguard.configproguard.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.java、SMSReceiver.java、SMSHelper.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_DENIEDActivity会Toast提示并停留在界面——用户能清晰看到“权限被拒绝”而不是App静默失败。短信读取部分的坑更深Cursor cursor getContentResolver().query( Uri.parse(content://sms/inbox), new String[]{_id, address, body, date}, null, null, date DESC LIMIT 50 );这里用了硬编码URIcontent://sms/inbox而非Telephony.Sms.Inbox.CONTENT_URI。原因后者在Android 4.0API 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限制 // 所以这里只做最轻量的事解析PDUToast提示存入本地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.4API 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) { ArrayListString parts smsManager.divideMessage(message); ArrayListPendingIntent sentIntents new ArrayList(); ArrayListPendingIntent 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.xml与strings.xml的务实主义res/layout/main.xml是一个极简的LinearLayout垂直排列一个EditText输入号码、一个EditText输入内容、两个Button发送、刷新收件箱、一个ListView显示短信。没有Fragment没有RecyclerView4.x不原生支持没有ConstraintLayout那是2016年的事。它的价值在于“所见即所得”——你改一行XML保存ant debug新APK里立刻生效没有任何中间缓存或编译优化干扰。res/values/strings.xml里定义了所有文本包括一个容易被忽略的关键项string nameapp_nameex_SMS/string string namehint_number请输入手机号/string string namehint_message请输入短信内容/string string namebtn_send发送/string string namebtn_refresh刷新收件箱/string string nametoast_sms_sent短信已发送/string string nametoast_sms_failed短信发送失败/string string namepermission_rationale需要短信权限才能收发信息/string !-- 这个字符串至关重要 -- string nameno_sms_permission未授予短信权限请在设置中开启/stringno_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 version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.ouling.exsms android:versionCode1 android:versionName1.0 !-- 最低支持Android 4.0 (API 14) -- uses-sdk android:minSdkVersion14 android:targetSdkVersion23 / !-- 三大核心权限缺一不可 -- uses-permission android:nameandroid.permission.READ_SMS / uses-permission android:nameandroid.permission.SEND_SMS / uses-permission android:nameandroid.permission.RECEIVE_SMS / !-- 可选如果你需要监听短信送达状态 -- uses-permission android:nameandroid.permission.RECEIVE_WAP_PUSH / application android:allowBackuptrue android:icondrawable/ic_launcher android:labelstring/app_name android:themeandroid:style/Theme.Light !-- 主Activity必须声明LAUNCHER -- activity android:name.MainActivity android:labelstring/app_name intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity !-- 短信接收器exported必须为trueAndroid 4.x要求 -- receiver android:name.SMSReceiver android:enabledtrue android:exportedtrue intent-filter android:priority1000 action android:nameandroid.provider.Telephony.SMS_RECEIVED / /intent-filter /receiver /application /manifest重点解析几个易错点android:exportedtrue这是Android 4.x的硬性要求。从Android 5.0开始exported默认为false但4.x不识别该属性必须显式设为true否则广播接收器根本收不到SMS_RECEIVED事件。我们曾在一个Android 4.4的飞利浦电视上因为漏了这行折腾了两天。intent-filter里的android:priority1000数值越大优先级越高。系统短信App通常设为1000我们设成1000是为了确保我们的Receiver能第一时间收到广播避免被其他App拦截。注意这个值在Android 8.0被废弃但对4.x–6.x是救命稻草。android:targetSdkVersion23明确指向Android 6.0这是触发运行时权限模型的开关。如果设成22即使在6.0设备上App也会以“旧模式”运行权限在安装时授予无法测试动态申请逻辑。android:themeandroid:style/Theme.Light不用AppCompat主题是因为4.x原生就不支持。用系统原生Light主题确保在所有设备上UI渲染一致避免因主题兼容性导致的ClassNotFoundException。4. 实操过程与核心环节实现从导入到真机验证的全流程4.1 Eclipse环境准备ADT 23.0.7是最稳组合别试图用最新版ADT或Eclipse。经过实测Eclipse Kepler SR24.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问题导致targetandroid-19读取失败JDK 7u80是Oracle官方为Android构建链最后认证的JDK 7版本JDK 8在dx工具中会出现Unsupported major.minor version 52.0错误。安装步骤Windows为例下载Eclipse Kepler SR2 Classic版eclipse-kepler-SR2-win32.zip解压到C:\eclipse\下载ADT 23.0.7插件adt-bundle-windows-x86_64-201407022030.zip解压后得到sdk/和eclipse/文件夹将adt-bundle-windows-x86_64-201407022030\eclipse\plugins\下的所有jar包复制到C:\eclipse\plugins\启动C:\eclipse\eclipse.exe首次启动会提示选择Workspace选一个空文件夹如C:\workspace_smsWindow → Preferences → Android点击Browse...指向adt-bundle-windows-x86_64-201407022030\sdk\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.exe4.2 工程导入与零配置调试将下载的资源包解压到C:\workspace_sms\ex_sms_project\路径不能含中文或空格。然后File → Import → General → Existing Projects into WorkspaceSelect root directory浏览到C:\workspace_sms\ex_sms_project\勾选ex_sms_project取消勾选Copy projects into workspace我们就是要原地调试点击Finish。此时Eclipse会自动识别这是一个ADT项目project.properties里的targetandroid-19会被读取并在Package Explorer中显示黄色感叹号——这是正常的因为SDK 19Android 4.4可能未安装。解决方法Window → Android SDK Manager展开Android 4.4.2 (API 19)勾选SDK Platform和Intel x86 Atom System Image如果要用模拟器点击Install packages等待安装完成关闭SDK Manager右键项目 →Properties → Android确认Project Build Target已选中Android 4.4.2右键项目 →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: sendTextMessage和SmsSender: 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对短信功能支持不一必须按以下配置AVD Manager → Create Virtual Device选择Nexus 4屏幕适配好System Image选择Android 4.4.2 (API 19)或Android 6.0 (API 23)的x86镜像不要选ARM太慢Next → Next → Finish启动AVD后在Eclipse中Run As → Android Application选择该AVD关键一步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监听发送成功放弃deliveryIntentAndroid 6.0设备上权限申请对话框不弹出targetSdkVersion未设为23或AndroidManifest.xml中uses-sdk标签缺失aapt dump badging ex_SMS.apk \| grep sdk确保AndroidManifest.xml中有uses-sdk android:targetSdkVersion23 /图标在Android 4.2设备上显示为白方块缺少drawable-ldpi/ic_launcher.png或图片尺寸不对adb shell ls /data/data/com.ouling.exsms/files/检查资源包中drawable-ldpi/是否存在且图片为48x48 PNG5.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导出短信数据库做离线分析当客户说“收件箱数据丢了”不要只信他。用以下命令导出系统短信DBadb backup -f sms_backup.ab -noapk com.android.providers.telephony # 将backup.ab转为tar需dd命令 dd ifsms_backup.ab bs24 skip1 \| 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:debuggabletrue在application标签里加上android:debuggabletrue这能让adb logcat捕获到更多详细日志尤其在onReceive()里加Log.d()时。虽然发布版要删掉但调试阶段它比断点还管用。技巧五用adb shell getprop查ROM指纹当一切正常但功能异常时终极排查是查ROMadb shell getprop ro.build.fingerprint # 输出类似samsung/jfltexx/jflte:4.4.2/KOT49H/I9505XXUHNK1:user/release-keys把指纹发给ROM厂商他们能立刻告诉你这个版本是否禁用了content://smsURI。我们曾发现某款华为B199定制ROM把TelephonyProvider的readPermission设为null导致所有App都无法读取短信——这是厂商的硬编码限制非App之过。6. 二次开发与扩展建议如何安全地在此基础上迭代这个包不是终点而是起点。如果你需要基于它做二次开发请牢记三条铁律铁律一绝不修改AndroidManifest.xml的权限声明顺序READ_SMS必须在SEND_SMS之前RECEIVE_SMS必须在最后。某些ROM如vivo Funtouch OS 2.5的权限管理器会按声明顺序校验顺序错一位RECEIVE_SMS就会被静默拒绝。我们曾为此重构过三次权限模块。铁律二新增功能必须通过Intent解耦而非直接调用比如你要加“群发短信”功能不要在MainActivity里写循环发送逻辑。而是1. 新建GroupSendActivity2. 在AndroidManifest.xml中声明3. 从MainActivity用startActivity(new Intent(this, GroupSendActivity.class))跳转4.GroupSendActivity里独立处理群发逻辑。这样做的好处是如果群发功能出Bug不影响主流程而且可以单独测试、单独发布补丁APK。铁律三所有网络请求必须用HttpURLConnection禁用OkHttp/retrofitAndroid 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.storerelease.keystore key.aliasmy_alias key.store.passwordyour_password key.alias.passwordyour_password否则用户升级时会因签名不一致而安装失败——这是所有二次开发者必踩的第一个坑。我在实际使用中发现最高效的二次开发方式是把它当作一个“短信能力探针”。先用它验证设备基础功能再把它的SMSHelper.java和SMSReceiver.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调用逻辑、排查权限问题或作为二次开发起点。本文还有配套的精品资源点击获取