Android WindowLeaked错误求助:已调用dismiss仍崩溃
看起来你遇到了典型的WindowLeaked错误,这个问题的核心不是你没调用dismiss(),而是你的AsyncTask和Activity的生命周期没有同步——当后台任务还在运行时,你的DetailsActivity可能已经被销毁了(比如用户按了返回键),这时候再去操作依附在Activity上的Dialog,就会触发这个泄漏错误并导致应用崩溃。
一、先搞懂WindowLeaked错误的原因
android.view.WindowLeaked: Activity com.mqa.android.peminjamankelas.DetailsActivity has leaked window...
这个日志明确告诉你:你的DetailsActivity已经被销毁,但之前显示的Dialog还没有被正确移除,它还持有Activity的引用,导致内存泄漏,系统因此抛出错误并强制关闭应用。
你代码里虽然在onPostExecute()调用了pDialog.dismiss(),但如果在后台任务运行期间,Activity已经被finish(比如用户退出页面),那么onPostExecute()执行时,Activity已经不存在了,这时候调用dismiss()就会触发崩溃。
二、针对你的代码的修复步骤
1. 先检查Activity状态再操作Dialog
修改onPostExecute()方法,先判断Activity是否还存活,再执行Dialog和Intent操作:
@Override protected void onPostExecute(String message) { // 先判断Activity是否还处于活跃状态 if (DetailsActivity.this.isFinishing() || DetailsActivity.this.isDestroyed()) { return; } // 确保Dialog存在且显示时才关闭 if (pDialog != null && pDialog.isShowing()) { pDialog.dismiss(); } // 根据接口返回的message显示提示,而不是固定显示成功 if (message != null) { Toast.makeText(DetailsActivity.this, message, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(DetailsActivity.this, "请求失败,请重试", Toast.LENGTH_SHORT).show(); } // 只有请求成功时才跳转页面 if (message != null && message.contains("Berhasil")) { Intent i = new Intent(DetailsActivity.this, MainActivity.class); startActivity(i); // 跳转后finish当前Activity,避免用户返回后看到无效页面 DetailsActivity.this.finish(); } }
2. 把AsyncTask改成静态内部类,避免内存泄漏
你的BuatPeminjaman是普通内部类,会持有外部DetailsActivity的强引用——即使Activity销毁了,只要AsyncTask还在运行,Activity就无法被回收,这会加重内存泄漏问题。改成静态内部类并使用弱引用:
// 静态内部类+弱引用,避免持有Activity强引用 private static class BuatPeminjaman extends AsyncTask<String, String, String> { private WeakReference<DetailsActivity> activityRef; private ProgressDialog pDialog; private String gedung, lantai, nim, nama, peserta, keperluan, ruang; private JsonParser jsonParser; private static final String LOGIN_URL = "你的请求URL"; private static final String TAG_SUCCESS = "success"; private static final String TAG_MESSAGE = "message"; // 构造方法传入Activity和请求参数 public BuatPeminjaman(DetailsActivity activity, String gedung, String lantai, String nim, String nama, String peserta, String keperluan, String ruang) { this.activityRef = new WeakReference<>(activity); this.gedung = gedung; this.lantai = lantai; this.nim = nim; this.nama = nama; this.peserta = peserta; this.keperluan = keperluan; this.ruang = ruang; this.jsonParser = new JsonParser(); } @Override protected void onPreExecute() { super.onPreExecute(); DetailsActivity activity = activityRef.get(); // 如果Activity已销毁,直接取消任务 if (activity == null || activity.isFinishing()) { cancel(true); return; } pDialog = new ProgressDialog(activity); pDialog.setMessage("Booking..."); pDialog.setIndeterminate(false); pDialog.setCancelable(true); pDialog.show(); } @Override protected String doInBackground(String... arg) { // 如果任务已取消,直接返回 if (isCancelled()) { return null; } int success; try { List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("gedung", gedung)); params.add(new BasicNameValuePair("lantai", lantai)); params.add(new BasicNameValuePair("nim", nim)); params.add(new BasicNameValuePair("nama", nama)); params.add(new BasicNameValuePair("jumlah peserta", peserta)); params.add(new BasicNameValuePair("keperluan", keperluan)); params.add(new BasicNameValuePair("ruang", ruang)); Log.d("request!", "starting"); JSONObject json = jsonParser.makeHttpRequest(LOGIN_URL, "GET", params); Log.d("proses membuat", json.toString()); success = json.getInt(TAG_SUCCESS); if (success == 1) { Log.d("Berhasil membuat !", json.toString()); return json.getString(TAG_MESSAGE); } else { return json.getString(TAG_MESSAGE); } } catch (JSONException e) { e.printStackTrace(); return "解析数据出错,请检查接口返回格式"; } catch (Exception e) { e.printStackTrace(); return "网络请求失败,请检查网络连接"; } } @Override protected void onPostExecute(String message) { DetailsActivity activity = activityRef.get(); if (activity == null || activity.isFinishing() || activity.isDestroyed()) { return; } if (pDialog != null && pDialog.isShowing()) { pDialog.dismiss(); } if (message != null) { Toast.makeText(activity, message, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, "请求失败,请重试", Toast.LENGTH_SHORT).show(); } // 只有成功时跳转页面 if (message != null && message.contains("Berhasil")) { Intent i = new Intent(activity, MainActivity.class); activity.startActivity(i); activity.finish(); } } }
调用时改成:
// 在DetailsActivity的点击事件中调用 new BuatPeminjaman(this, gedung, lantai, nim, nama, peserta, keperluan, ruang).execute();
3. 排查JSON请求的潜在问题
从你的代码看,JSON请求如果失败(比如网络错误、服务器返回非JSON格式数据),会进入JSONException,但之前只打印了异常没有返回明确错误信息,这会导致onPostExecute()收到null,进而显示模糊的错误提示。上面的修复已经在catch块里添加了明确的错误返回,方便你调试。
另外,注意你用的是GET方法传递参数,如果参数包含中文或特殊字符,可能会导致解析错误,建议换成POST方法,或者对参数进行URL编码。
三、更现代的替代方案(推荐)
AsyncTask已经被Android官方废弃,更推荐使用ViewModel + Coroutines或者Retrofit来处理异步网络请求,这些方案能更好地管理生命周期,从根源避免内存泄漏:
比如用Coroutines的示例(Kotlin):
// 在DetailsActivity中 private fun submitBooking() { val progressDialog = ProgressDialog(this).apply { setMessage("Booking...") setIndeterminate(false) setCancelable(true) show() } lifecycleScope.launch { try { // 这里用Retrofit调用接口 val response = apiService.submitBooking(gedung, lantai, nim, nama, peserta, keperluan, ruang) progressDialog.dismiss() if (response.success == 1) { Toast.makeText(this@DetailsActivity, response.message, Toast.LENGTH_SHORT).show() startActivity(Intent(this@DetailsActivity, MainActivity::class.java)) finish() } else { Toast.makeText(this@DetailsActivity, response.message, Toast.LENGTH_SHORT).show() } } catch (e: Exception) { progressDialog.dismiss() Toast.makeText(this@DetailsActivity, "请求失败:${e.message}", Toast.LENGTH_SHORT).show() e.printStackTrace() } } }
这种方式会自动和Activity的生命周期绑定,当Activity销毁时,协程会自动取消,不会出现WindowLeaked的问题。
内容的提问来源于stack exchange,提问作者Qube

