Android 9.0 Activity窗口泄漏求助:添加usesCleartextTraffic后ProgressDialog异常
我之前碰到过几乎一模一样的场景!当时也是为了让Volley支持明文请求加了android:usesCleartextTraffic="true",结果在Android 9.0上直接炸出了窗口泄漏的报错,其他版本都好好的。后来排查发现,这个问题其实和usesCleartextTraffic本身无关——它只是让你的Volley请求能正常跑起来,把之前隐藏的生命周期不一致问题给暴露出来了。
问题根源
Android 9.0(API 28)开始强化了窗口管理的检测机制,同时对非前台Activity显示窗口的限制更严格。如果你的Volley请求回调在Activity已经销毁(比如用户按了返回键)之后才执行,这时候去显示或操作ProgressDialog,就会导致Dialog附着在一个已经被销毁的Activity实例上,触发Activity has leaked window的报错。之前的Android版本可能没有明确抛出这个异常,但泄漏其实已经存在了。
具体解决步骤
1. 主动管理ProgressDialog的生命周期
在你的Activity的onDestroy()方法中,强制关闭并销毁Dialog,避免它持有Activity的引用:
private ProgressDialog mProgressDialog; @Override protected void onDestroy() { super.onDestroy(); // 清理Dialog if (mProgressDialog != null) { if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } mProgressDialog = null; // 置空彻底释放引用 } }
2. 绑定Volley请求与Activity生命周期
在Activity销毁时,取消所有正在执行的Volley请求,避免回调在Activity销毁后触发:
private RequestQueue mVolleyQueue; private Request<?> mCurrentRequest; // 保存当前正在执行的请求 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mVolleyQueue = Volley.newRequestQueue(this); } // 发起请求时保存请求实例 private void sendVolleyRequest() { StringRequest request = new StringRequest(Request.Method.GET, "你的接口地址", response -> { // 请求成功,关闭Dialog if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } // 处理响应数据 }, error -> { // 请求失败,关闭Dialog if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } // 处理错误 }); mCurrentRequest = request; mVolleyQueue.add(request); } @Override protected void onDestroy() { super.onDestroy(); // 取消当前请求 if (mCurrentRequest != null) { mCurrentRequest.cancel(); } // 或者取消所有和当前Activity相关的请求(如果用了tag) mVolleyQueue.cancelAll(this); // 别忘了清理Dialog if (mProgressDialog != null) { if (mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } mProgressDialog = null; } }
3. 进阶优化:用ViewModel解耦请求与UI
如果你的项目用了Jetpack,推荐用ViewModel来管理Volley请求,通过LiveData通知Activity更新UI状态。这样请求的生命周期和Activity分离,从根本上避免生命周期不一致的问题:
// 自定义ViewModel public class MyViewModel extends ViewModel { private MutableLiveData<Boolean> mIsLoading = new MutableLiveData<>(); private RequestQueue mVolleyQueue; public MyViewModel() { mVolleyQueue = Volley.newRequestQueue(MyApplication.getInstance()); } public LiveData<Boolean> getIsLoading() { return mIsLoading; } public void sendRequest() { mIsLoading.postValue(true); StringRequest request = new StringRequest(Request.Method.GET, "你的接口地址", response -> { mIsLoading.postValue(false); // 处理响应 }, error -> { mIsLoading.postValue(false); // 处理错误 }); mVolleyQueue.add(request); } @Override protected void onCleared() { super.onCleared(); // ViewModel销毁时取消所有请求 mVolleyQueue.cancelAll(this); } } // 在Activity中观察状态 public class MyActivity extends AppCompatActivity { private ProgressDialog mProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.getIsLoading().observe(this, isLoading -> { if (isLoading) { showProgressDialog(); } else { hideProgressDialog(); } }); // 发起请求 viewModel.sendRequest(); } private void showProgressDialog() { if (mProgressDialog == null) { mProgressDialog = ProgressDialog.show(this, "加载中", "请稍后..."); } else if (!mProgressDialog.isShowing()) { mProgressDialog.show(); } } private void hideProgressDialog() { if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); } } @Override protected void onDestroy() { super.onDestroy(); if (mProgressDialog != null) { mProgressDialog.dismiss(); mProgressDialog = null; } } }
总结
这个问题的本质是Volley请求回调时机和Activity生命周期不同步,Android 9.0只是把之前隐藏的泄漏问题明确报错了。按照上面的方法处理后,就能彻底解决这个窗口泄漏的问题。
内容的提问来源于stack exchange,提问作者Sufferer

