安卓Java开发:如何避免屏幕旋转时ExecutorService后台任务重复加载数据
Android屏幕旋转导致地震数据重复加载的Java解决方案
根本原因
Android发生配置变更(如屏幕旋转、系统语言切换、分辨率调整)时,默认会销毁并重建当前Activity,你的网络请求逻辑直接写在onCreate生命周期方法中,Activity重建时onCreate会重新执行,就会重复发起后台请求加载数据。同时未做管控的后台线程还会持有已销毁Activity的引用,存在内存泄漏风险。
可行解决方案(Java环境)
下面给出3种常用实现方案,可根据你的项目需求选择:
方案1:配置configChanges手动处理旋转事件(适合横竖屏布局一致的场景,实现最简单)
直接在AndroidManifest.xml中为你的MainActivity添加配置变更声明,旋转时不会触发Activity重建:
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:exported="true"> <!-- 其他原有配置 --> </activity>
如果不需要在旋转时做特殊逻辑,不需要额外修改Activity代码,即可解决重复请求问题。如果横竖屏布局不同,需要重写onConfigurationChanged方法手动替换布局和更新视图。
方案2:通过onSaveInstanceState缓存已加载数据(适合数据量小的场景)
步骤如下:
- 让你的
EarthquakeModel实体类实现Parcelable接口,支持序列化存储。 - 重写Activity的
onSaveInstanceState方法,把已经加载好的地震列表缓存进去:
@Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList("earthquake_data", earthquake); outState.putBoolean("is_connected", isConnected); }
- 修改
onCreate逻辑,先判断是否有缓存数据,有缓存直接用,没有再发起请求:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recycler_view); nointernetLinearLayout = findViewById(R.id.no_internet); mEmptyView = findViewById(R.id.empty_view); earthquake = new ArrayList<>(); // 先取缓存数据 if (savedInstanceState != null) { earthquake = savedInstanceState.getParcelableArrayList("earthquake_data"); isConnected = savedInstanceState.getBoolean("is_connected"); // 直接渲染UI,不需要发起请求 updateUI(); return; } // 没有缓存才发起网络请求 ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { // 原有请求逻辑不变,请求成功后调用updateUI()渲染视图 }); } // 把UI渲染逻辑抽成公共方法,避免重复代码 private void updateUI() { View loadingIndicator = findViewById(R.id.loading_indicator); loadingIndicator.setVisibility(View.GONE); if (isConnected&&!earthquake.isEmpty()){ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext()); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); EarthquakeAdapter earthquakeAdapter = new EarthquakeAdapter(earthquake,MainActivity.this); recyclerView.setAdapter(earthquakeAdapter); }if(isConnected&&earthquake.isEmpty()){ recyclerView.setVisibility(View.GONE); mEmptyView.setVisibility(View.VISIBLE); } if (!isConnected){ recyclerView.setVisibility(View.GONE); nointernetLinearLayout.setVisibility(View.VISIBLE); } }
方案3:使用ViewModel托管数据和请求逻辑(官方推荐,适配性最强)
ViewModel是Android Jetpack提供的生命周期感知组件,配置变更时ViewModel不会销毁,托管的数据和正在执行的请求都不会中断,不会重复执行。
- 先引入ViewModel依赖(如果没引入的话,build.gradle(module)中添加):
dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.2" implementation "androidx.lifecycle:lifecycle-livedata:2.6.2" }
- 新建
EarthquakeViewModel类,继承AndroidViewModel:
public class EarthquakeViewModel extends AndroidViewModel { private MutableLiveData<ArrayList<EarthquakeModel>> earthquakeLiveData; private MutableLiveData<Boolean> isConnectedLiveData; private static final String url = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2021-09-10&endtime=2021-09-11"; public EarthquakeViewModel(@NonNull Application application) { super(application); } public LiveData<ArrayList<EarthquakeModel>> getEarthquakeData() { if (earthquakeLiveData == null) { earthquakeLiveData = new MutableLiveData<>(); isConnectedLiveData = new MutableLiveData<>(); loadEarthquakeData(); } return earthquakeLiveData; } public LiveData<Boolean> getIsConnected() { return isConnectedLiveData; } private void loadEarthquakeData() { ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(() -> { QueryUtils queryUtils = new QueryUtils(); String json = queryUtils.call(url); ArrayList<EarthquakeModel> tempList = new ArrayList<>(); boolean connected = false; try { ConnectivityManager cm = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); connected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); if (connected){ Log.i("Background task","only run once"); JSONObject jsonObject = new JSONObject(json); JSONArray jsonArray = jsonObject.getJSONArray("features"); for (int i=0; i<jsonArray.length(); i++){ JSONObject c = jsonArray.getJSONObject(i); JSONObject properties = c.getJSONObject("properties"); double magnitude = properties.getDouble("mag"); String location = properties.getString("place"); long time = properties.getLong("time"); String url = properties.getString("url"); EarthquakeModel earthquakeModel = new EarthquakeModel(location, magnitude,time,url); tempList.add(earthquakeModel); } } }catch (JSONException e){ Log.e("Error","is"+e.getMessage()); } // 把结果 post 到LiveData earthquakeLiveData.postValue(tempList); isConnectedLiveData.postValue(connected); }); } }
- 修改MainActivity的
onCreate逻辑,只做观察者绑定:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recycler_view); nointernetLinearLayout = findViewById(R.id.no_internet); mEmptyView = findViewById(R.id.empty_view); View loadingIndicator = findViewById(R.id.loading_indicator); // 获取ViewModel实例 EarthquakeViewModel viewModel = new ViewModelProvider(this).get(EarthquakeViewModel.class); // 观察数据变化 viewModel.getEarthquakeData().observe(this, earthquakeList -> { loadingIndicator.setVisibility(View.GONE); boolean isConnected = Boolean.TRUE.equals(viewModel.getIsConnected().getValue()); if (isConnected&&!earthquakeList.isEmpty()){ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext()); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(linearLayoutManager); EarthquakeAdapter earthquakeAdapter = new EarthquakeAdapter(earthquakeList,MainActivity.this); recyclerView.setAdapter(earthquakeAdapter); }if(isConnected&&earthquakeList.isEmpty()){ recyclerView.setVisibility(View.GONE); mEmptyView.setVisibility(View.VISIBLE); } if (!isConnected){ recyclerView.setVisibility(View.GONE); nointernetLinearLayout.setVisibility(View.VISIBLE); } }); }
这个方案不仅解决了旋转重复请求的问题,还避免了内存泄漏,是长期维护项目的最优选择。
内容的提问来源于stack exchange,提问作者Amit Kumar
相关产品推荐
相关产品推荐

