Android:如何防止屏幕旋转时Loader在计算过程中重启?
嘿,这个问题我之前踩过一模一样的坑!本质上是因为在屏幕旋转(配置变更)的过程中,你的Loader没有正确复用正在进行的加载任务——虽然LoaderManager本身会在配置变更后保留Loader实例,但如果你的Loader实现没处理好「正在加载中」的状态,就会触发重复计算。
问题根源拆解
当你在计算过程中旋转屏幕,系统会销毁旧Activity并创建新实例。如果你的Loader没有在onStartLoading()里检查是否已有正在执行的任务,新关联的Loader(其实是同一个实例,LoaderManager会帮你保留)就会傻乎乎地从头开始计算;另外如果初始化Loader时用了动态ID,LoaderManager会误以为是新任务,直接创建新Loader,自然会重新计算。
针对性解决方案
1. 用固定ID初始化Loader
在Activity的onCreate()里,一定要用固定不变的ID来初始化Loader,绝对不能用动态生成的ID(比如随机数或者随时间变化的值)。示例代码:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recipe_list); // 用固定ID,比如1,确保配置变更后复用同一个Loader getSupportLoaderManager().initLoader(1, null, this); }
initLoader()的作用是:如果ID对应的Loader已经存在,直接复用;不存在才创建新的。用固定ID是复用的前提。
2. 在Loader内部维护加载状态,避免重复触发
你的Loader子类需要保留当前计算任务的引用,在onStartLoading()里判断:如果任务正在执行,就不要重新启动,等待它完成即可。示例实现:
public class RecipeLoader extends AsyncTaskLoader<List<Recipe>> { private List<Recipe> mCachedRecipes; private AsyncTask<Void, Void, List<Recipe>> mCurrentCalculationTask; public RecipeLoader(Context context) { super(context); // 确保Loader在后台也能保持引用 onContentChanged(); } @Override protected void onStartLoading() { // 有缓存直接返回,这就是加载完成后旋转正常的原因 if (mCachedRecipes != null) { deliverResult(mCachedRecipes); } // 关键判断:没有正在执行的任务,或者数据确实需要更新时才启动新任务 if (mCurrentCalculationTask == null || takeContentChanged()) { // 如果之前有未完成的任务,先取消(可选,根据你的业务决定是否中断) if (mCurrentCalculationTask != null) { mCurrentCalculationTask.cancel(false); } // 启动新的计算任务 mCurrentCalculationTask = new AsyncTask<Void, Void, List<Recipe>>() { @Override protected List<Recipe> doInBackground(Void... voids) { // 这里是你的高成本计算逻辑 return calculateRecipes(); } @Override protected void onPostExecute(List<Recipe> recipes) { mCachedRecipes = recipes; mCurrentCalculationTask = null; // 把结果传递给LoaderManager deliverResult(recipes); } }.execute(); } } @Override public void deliverResult(List<Recipe> data) { mCachedRecipes = data; super.deliverResult(data); } @Override protected void onStopLoading() { // 停止当前任务(如果用户离开页面,可选操作) if (mCurrentCalculationTask != null) { mCurrentCalculationTask.cancel(true); } } @Override protected void onReset() { super.onReset(); onStopLoading(); mCachedRecipes = null; mCurrentCalculationTask = null; } }
这里的核心是mCurrentCalculationTask的维护:配置变更后,Loader实例被保留,这个任务引用还在,所以onStartLoading()里不会启动新任务,而是等待原有任务完成后返回结果。
3. 别乱用restartLoader()
除非你确实需要强制重新加载数据,否则不要在onResume()或者其他生命周期方法里调用restartLoader()——这个方法会强制销毁旧Loader并创建新的,直接导致重复计算。
这样调整后,不管你在计算过程中怎么旋转屏幕,Loader都会复用正在进行的任务,不会从头开始计算啦!
内容的提问来源于stack exchange,提问作者CesarPim

