Android定时HTTP请求Service运行1小时崩溃问题修复建议
你的Service初期运行正常但1小时后崩溃,是多个潜在问题叠加导致的,我来逐一拆解并给出可落地的修复方案:
1. 最致命问题:主线程被阻塞引发ANR/崩溃
你在Runnable的run()方法里调用了new RequestTask().execute(url).get(),get()是同步等待AsyncTask执行结果的方法,会直接卡死Handler所在的主线程。每次网络请求不管快慢,主线程都会被强制等待,多次累积后必然触发ANR(应用无响应),甚至直接崩溃。
修复方案:
彻底移除get(),改用AsyncTask的onPostExecute回调异步处理结果:
首先给RequestTask添加回调接口,让请求完成后主动通知结果:
// 定义回调接口 interface OnRequestCompleteListener { void onSuccess(String response); void onFailure(); } class RequestTask extends AsyncTask<String, String, String> { private OnRequestCompleteListener listener; public RequestTask(OnRequestCompleteListener listener) { this.listener = listener; } @Override protected String doInBackground(String... uri) { HttpClient httpclient = new DefaultHttpClient(); String responseString = null; try { // 原网络请求逻辑保留,注意添加资源关闭逻辑 HttpResponse response = httpclient.execute(new HttpGet(uri[0])); StatusLine statusLine = response.getStatusLine(); if(statusLine.getStatusCode() == HttpStatus.SC_OK){ ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); responseString = out.toString(); out.close(); } else{ response.getEntity().getContent().close(); throw new IOException(statusLine.getReasonPhrase()); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭HttpClient连接池,避免资源泄漏 httpclient.getConnectionManager().shutdown(); } return responseString; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); if (result != null && !result.isEmpty()) { listener.onSuccess(result); } else { listener.onFailure(); } } }
然后在Runnable里异步调用任务,不在主线程等待:
new RequestTask(new OnRequestCompleteListener() { @Override public void onSuccess(String response) { // 这里处理原有的字符串解析逻辑 String withoutitlne = response.replaceAll("\\r|\\n", ""); String input = withoutitlne.replace(" ", ""); if (input != null && !input.isEmpty()) { String[] separated = response.split("\\*"); // 先判断数组长度,避免越界崩溃 if (separated.length >= 3) { String nb = separated[1]; String am = separated[2]; String oper = separated[0]; String nnb = nb.replace(" ", ""); if (oper.indexOf("GP") >= 0) { // 注意:原代码中`rb`变量未定义,请替换为实际存在的变量 Intent newintent = new Intent(getApplicationContext(), Recharge.class); newintent.putExtra("rcnumber", nnb); newintent.putExtra("rcamount", am); // newintent.putExtra("gpin", rb); // 修正此处未定义的变量 startService(newintent); } } } // 请求完成后再安排下一次任务 handler.postDelayed(runnable, runTime); } @Override public void onFailure() { // 请求失败也继续循环任务 handler.postDelayed(runnable, runTime); } }).execute(url);
2. Handler内存泄漏导致内存耗尽
你使用的匿名内部类Runnable会持有Server Service的强引用,加上handler.postDelayed的循环机制,即使Service被销毁,这个引用也会让Service无法被GC回收,长期运行会导致内存泄漏累积,最终触发OOM崩溃。
修复方案:
- 在
onDestroy中移除Handler的所有回调,切断引用链:
@Override public void onDestroy() { super.onDestroy(); if (handler != null) { // 移除所有待执行的回调和消息 handler.removeCallbacksAndMessages(null); } // 原AlarmManager重启代码保留 // ... }
- 改用静态内部类+弱引用定义Runnable,避免持有Service的强引用:
private static class TaskRunnable implements Runnable { private WeakReference<Server> serviceRef; private Handler handler; private final int runTime = 5000; public TaskRunnable(Server service) { this.serviceRef = new WeakReference<>(service); this.handler = new Handler(); } @Override public void run() { Server service = serviceRef.get(); // Service已被回收则终止任务 if (service == null) return; if (!service.isOnline(service)) { handler.postDelayed(this, runTime); return; } String url = "http://localhost:8080/feed/appfeed.php"; new RequestTask(new OnRequestCompleteListener() { @Override public void onSuccess(String response) { // 处理结果逻辑... handler.postDelayed(TaskRunnable.this, runTime); } @Override public void onFailure() { handler.postDelayed(TaskRunnable.this, runTime); } }).execute(url); } }
然后在onCreate中初始化:
@Override public void onCreate() { super.onCreate(); handler = new Handler(); runnable = new TaskRunnable(this); handler.post(runnable); }
3. 未处理的空指针与数组越界异常
原代码存在多个潜在崩溃点:
- 网络请求失败时
output为null,直接调用output.replaceAll会抛出NPE split("\\*")后的数组长度可能不足3,直接访问separated[1]/separated[2]会触发数组越界- 代码中
rb变量未定义,编译都无法通过(你说初期正常,大概率是笔误)
修复方案:
- 对所有可能为null的变量做非空判断
- 拆分字符串后先检查数组长度
- 修正未定义的
rb变量问题,替换为实际存在的参数
4. HttpClient过时与资源泄漏
Android 6.0(API 23)之后HttpClient被官方移除,且原代码未正确关闭连接池,长期运行会导致连接资源耗尽。
修复方案:
改用官方推荐的HttpURLConnection,示例如下:
@Override protected String doInBackground(String... uri) { HttpURLConnection urlConnection = null; BufferedReader reader = null; String responseString = null; try { URL url = new URL(uri[0]); urlConnection = (HttpURLConnection) url.openConnection(); // 设置User-Agent String userAgent = "Mozilla/5.0 (Linux; Android "+getAndroidVersion()+"; "+getDeviceName()+" Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36"; urlConnection.setRequestProperty("User-Agent", userAgent); urlConnection.setRequestMethod("GET"); urlConnection.connect(); if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream inputStream = urlConnection.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder buffer = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { buffer.append(line); } responseString = buffer.toString(); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭所有资源 if (urlConnection != null) urlConnection.disconnect(); if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return responseString; }
5. 定时任务稳定性优化
用Handler.postDelayed做长期定时任务不稳定,一旦Service被系统回收(后台内存紧张时很常见),任务就会中断。你已经用AlarmManager做重启,但原代码的AlarmManager调用存在适配问题。
修复方案:
适配不同Android版本的AlarmManager API,使用正确的PendingIntent flag:
@Override public void onDestroy() { super.onDestroy(); Toast.makeText(getApplicationContext(), "Service Task destroyed", Toast.LENGTH_LONG).show(); Intent myIntent = new Intent(getApplicationContext(), Server.class); // 根据Android版本选择合适的flag int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, myIntent, flags); AlarmManager alarmManager1 = (AlarmManager) getSystemService(ALARM_SERVICE); long triggerTime = System.currentTimeMillis() + 10 * 1000; // 适配不同版本的精确触发API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager1.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager1.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else { alarmManager1.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } }
onTaskRemoved中的AlarmManager代码做同样修改即可。
修复后关键总结
- 绝对不要在主线程调用
AsyncTask.get(),改用异步回调 - 用静态内部类+弱引用避免Handler内存泄漏
- 对所有对象做非空/边界检查,杜绝NPE和数组越界
- 替换过时的HttpClient,用HttpURLConnection并正确关闭资源
- 适配AlarmManager API,保证Service在后台能稳定重启
内容的提问来源于stack exchange,提问作者Osman Gani

