后台网络连接检测、Service中网络检测及短信监听离线同步问题咨询
Hey Kriss,针对你提出的两个Android开发场景问题,我整理了实操性很强的解决方案,都是项目里常用的思路,一起来看看:
一、Service里怎么像Activity一样检测网络连接
其实Service作为Context的子类,获取网络状态的逻辑和Activity本质一致,只是要注意权限适配和高版本系统的限制:
1. 基础网络状态判断
首先得在AndroidManifest.xml里加必要权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" />
然后可以写个工具方法(放在Service里或者单独的工具类都可以),兼顾新旧版本系统:
private boolean isNetworkAvailable(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; // Android 7.0+推荐用NetworkCapabilities(更准确) Network activeNetwork = cm.getActiveNetwork(); if (activeNetwork != null) { NetworkCapabilities capabilities = cm.getNetworkCapabilities(activeNetwork); return capabilities != null && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)); } // 兼容旧版本的NetworkInfo方式(虽然已标记deprecated,但适配低版本还是能用) NetworkInfo legacyNetworkInfo = cm.getActiveNetworkInfo(); return legacyNetworkInfo != null && legacyNetworkInfo.isConnected(); }
在Service里直接调用这个方法,就能快速判断当前是否有可用网络啦。
2. 实时监听网络状态变化
如果需要实时感知网络从断开到恢复的变化(这对你后面同步短信数据很重要),推荐用ConnectivityManager.NetworkCallback——替代旧的广播监听,高版本系统下更可靠:
private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { super.onAvailable(network); // 网络恢复啦!这里直接触发同步短信到服务器的逻辑 syncUnsyncedSmsToServer(); } @Override public void onLost(Network network) { super.onLost(network); // 网络断开了,你可以做一些本地状态标记,比如暂停同步任务 } }; // 在Service的onCreate里注册监听 @Override public void onCreate() { super.onCreate(); ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { NetworkRequest networkRequest = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build(); cm.registerNetworkCallback(networkRequest, networkCallback); } } // 别忘了解绑!在Service销毁时执行 @Override public void onDestroy() { super.onDestroy(); ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null) { cm.unregisterNetworkCallback(networkCallback); } }
二、短信监听+离线缓存+网络恢复同步
你的需求核心是“特定短信存本地→离线时暂存→有网时自动同步”,这个场景的关键是本地持久化+网络触发+重试兜底:
1. 监听特定短信并存入本地DB
首先要申请权限:Android 10及以上需要READ_SMS和RECEIVE_SMS危险权限,记得要动态申请(用户同意后才能用)。然后用BroadcastReceiver监听短信:
public class TargetSmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) { Bundle bundle = intent.getExtras(); if (bundle == null) return; Object[] pdus = (Object[]) bundle.get("pdus"); if (pdus == null) return; for (Object pdu : pdus) { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); String smsContent = sms.getMessageBody(); String senderNumber = sms.getOriginatingAddress(); // 筛选包含特定关键词的短信 if (smsContent.contains("你的目标关键词")) { // 存入本地DB,推荐用Room,给每条数据加个`isSynced`字段(默认false,表示未同步) SmsRecord smsRecord = new SmsRecord(); smsRecord.setContent(smsContent); smsRecord.setSender(senderNumber); smsRecord.setIsSynced(false); smsRecord.setCreateTime(System.currentTimeMillis()); // 调用Room的DAO插入数据 AppDatabase.getInstance(context).smsRecordDao().insert(smsRecord); } } } } }
注意:Android 8.0+静态注册广播受限,建议在Service或者Application里动态注册Receiver:
// 在Service的onCreate里注册Receiver TargetSmsReceiver smsReceiver = new TargetSmsReceiver(); IntentFilter filter = new IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION); registerReceiver(smsReceiver, filter); // 同样在onDestroy里解绑 unregisterReceiver(smsReceiver);
2. 离线缓存与网络恢复同步
没网的时候,所有符合条件的短信都存在本地DB,标记为未同步。当网络恢复时(就是前面NetworkCallback的onAvailable方法里),执行同步逻辑:
private void syncUnsyncedSmsToServer() { // 从DB查询所有未同步的短信 List<SmsRecord> unsyncedRecords = AppDatabase.getInstance(this).smsRecordDao().getAllUnsyncedRecords(); if (unsyncedRecords.isEmpty()) return; // 用Retrofit/OkHttp上传到服务器,这里是示例代码 ApiService apiService = RetrofitClient.getInstance().getApiService(); Call<Void> uploadCall = apiService.uploadSmsRecords(unsyncedRecords); uploadCall.enqueue(new Callback<Void>() { @Override public void onResponse(Call<Void> call, Response<Void> response) { if (response.isSuccessful()) { // 上传成功,把这些数据标记为已同步 AppDatabase.getInstance(MyService.this).smsRecordDao().markRecordsAsSynced(unsyncedRecords); } else { // 上传失败,安排重试 scheduleSyncRetry(); } } @Override public void onFailure(Call<Void> call, Throwable t) { // 请求失败,同样触发重试 scheduleSyncRetry(); } }); }
3. 重试兜底(避免同步失败丢数据)
如果上传过程中网络又断了,或者服务器返回错误,推荐用WorkManager做后台重试——它会自动处理系统休眠、重启等情况,非常省心:
private void scheduleSyncRetry() { OneTimeWorkRequest syncWork = new OneTimeWorkRequest.Builder(SmsSyncWorker.class) // 指数退避重试:第一次等10秒,第二次20秒,以此类推,避免频繁请求服务器 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build(); WorkManager.getInstance(this).enqueue(syncWork); } // 对应的Worker类,后台执行同步任务 public class SmsSyncWorker extends Worker { public SmsSyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { List<SmsRecord> unsyncedRecords = AppDatabase.getInstance(getApplicationContext()).smsRecordDao().getAllUnsyncedRecords(); if (unsyncedRecords.isEmpty()) return Result.success(); ApiService apiService = RetrofitClient.getInstance().getApiService(); try { Response<Void> response = apiService.uploadSmsRecords(unsyncedRecords).execute(); if (response.isSuccessful()) { AppDatabase.getInstance(getApplicationContext()).smsRecordDao().markRecordsAsSynced(unsyncedRecords); return Result.success(); } else { // 上传失败,告诉WorkManager稍后重试 return Result.retry(); } } catch (IOException e) { e.printStackTrace(); return Result.retry(); } } }
备注:内容来源于stack exchange,提问作者Kriss

