android 中的 ViewModel 和可变实时数据无法正常工作
ViewModel and Mutable Live Data in android not working properly
我只是想获取地震列表并从中制作一个数组列表。为了避免在屏幕旋转时多次 API 调用,我使用 ViewModel 实现了可变实时数据 class,但是通过登录我发出网络请求的 QueryUtils class,我发现即使我旋转设备,网络请求仍在进行中。我不明白为什么。这是我的相关 classes:
public class EarthquakeActivity extends AppCompatActivity {
private static final String LOG_TAG = "MainActivity";
private MyModel mMyModel;
private ViewModelProvider mViewModelProvider;
private RecyclerView mRecyclerView;
private TextView mEmptyView;
private EarthquakeAdapter mEarthquakeAdapter;
private ProgressBar mProgressBar;
private ConnectivityManager mConnectivityManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.earthquake_activity);
mEmptyView = findViewById(R.id.empty_view);
// get the progress bar to indicate loading
mProgressBar = findViewById(R.id.loading_bar);
// set a view model provider for the current activity
mViewModelProvider = new ViewModelProvider(this);
// get the view model for the class
mMyModel = mViewModelProvider.get(MyModel.class);
// find a reference to the {@link RecyclerView} in the layout
mRecyclerView = findViewById(R.id.earthquakes);
mConnectivityManager = getSystemService(ConnectivityManager.class);
mEmptyView.setText("No internet available");
mEmptyView.setVisibility(View.VISIBLE);
Handler handler = new Handler(Looper.getMainLooper());
mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
@Override
public void onAvailable(Network network) {
handler.post(new Runnable() {
@Override
public void run() {
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
fetchData();
}
});
}
});
Log.e(LOG_TAG,"Visibility changed");
}
private void fetchData(){
// fetch the list of earthquakes
mMyModel.getEarthquakes().observe(EarthquakeActivity.this, new Observer<ArrayList<Earthquake>>() {
@Override
public void onChanged(ArrayList<Earthquake> earthquakes) {
Log.v(LOG_TAG,"fetching the data");
// set up recycler view with this data, this will work even if you rotate the device
setUpRecyclerView(earthquakes);
}
});
}
private void setUpRecyclerView(ArrayList<Earthquake> earthquakes) {
if(earthquakes == null || earthquakes.size() == 0) {
mRecyclerView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
mEmptyView.setText(R.string.no_data_available);
mEmptyView.setVisibility(View.VISIBLE);
}
else {
mRecyclerView.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
// create adapter passing in the earthquake data
mEarthquakeAdapter = new EarthquakeAdapter(EarthquakeActivity.this, earthquakes);
// attach the adapter to the recyclerView to populate the items
mRecyclerView.setAdapter(mEarthquakeAdapter);
// set the layout manager to position the items
mRecyclerView.setLayoutManager(new LinearLayoutManager(EarthquakeActivity.this));
Log.e(LOG_TAG,"Recycler view about to be setup");
// click listener for when an item is clicked
mEarthquakeAdapter.setClickListener((view, position) -> searchWeb(earthquakes.get(position).getUrl()));
}
}
}
我的视图模型class
public class MyModel extends ViewModel {
private static final String LOG_TAG = "MyModel";
private MutableLiveData<ArrayList<Earthquake>> mMutableLiveData;
public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
mMutableLiveData = new MutableLiveData<>();
// call the API
init();
return mMutableLiveData;
}
public void init() {
// perform the network request on separate thread
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
// create array list of earthquakes
mMutableLiveData.postValue(QueryUtils.fetchEarthquakeData("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2021-06-01&limit=300"));
}
});
executorService.shutdown();
}
}
和我的 Query Utils class 相关方法只是因为 class 很大
private static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
// if url null, return
if(url == null) {
return jsonResponse;
}
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection= (HttpURLConnection)url.openConnection();
urlConnection.setReadTimeout(10000/*milliseconds*/);
urlConnection.setConnectTimeout(15000/*milliseconds*/);
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// this is being logged again even after screen rotation
Log.v(LOG_TAG,"Network request made");
// if the request was successful(response code 200)
//then read the input stream and parse the output
if(urlConnection.getResponseCode() == 200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
}
else{
Log.e(LOG_TAG,"Error Response code: " + urlConnection.getResponseCode());
}
}
catch (IOException e) {
Log.e(LOG_TAG,"Problem retrieving the earthquake JSON results",e);
}
finally {
if(urlConnection != null) {
urlConnection.disconnect();
}
if(inputStream != null) {
inputStream.close();
}
}
return jsonResponse;
请帮我看看哪里出错了。这让我现在很困惑。
看看这里,也许能帮到你
Activity restart on rotation Android
可能在屏幕旋转时您再次调用 onCreate,ViewModel 也是如此
编辑:尝试以这种方式调用 API
public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
mMutableLiveData = new MutableLiveData<>();
//call the API
mMutableLiveData.init();
配置更改时,您的 activity 会重新创建并调用 fetchData() 调用 getEarthquakes() 和 init()。
步骤应该是:
- 在 viewModel 中创建一个私有的 MutableLiveData
- 为该 MutableLiveData
创建一个 getter 方法
- 在 onCreate() 方法中,您应该在不进行任何调用的情况下注册观察者
- 然后您应该调用 public 方法来获取数据并将值发布到 MutableLiveData。
- 这样,您在 UI 中的观察器应该被触发。
您应该通过调用 getEarthquakes() 而不是调用 init() 来注册观察者。如果数据最初为空,则可以在观察者方法中进行空检查。
创建 activity 之后,您应该调用在 viewModel 中创建的 public 方法来获取数据,在您的例子中是 init()。
我只是想获取地震列表并从中制作一个数组列表。为了避免在屏幕旋转时多次 API 调用,我使用 ViewModel 实现了可变实时数据 class,但是通过登录我发出网络请求的 QueryUtils class,我发现即使我旋转设备,网络请求仍在进行中。我不明白为什么。这是我的相关 classes:
public class EarthquakeActivity extends AppCompatActivity {
private static final String LOG_TAG = "MainActivity";
private MyModel mMyModel;
private ViewModelProvider mViewModelProvider;
private RecyclerView mRecyclerView;
private TextView mEmptyView;
private EarthquakeAdapter mEarthquakeAdapter;
private ProgressBar mProgressBar;
private ConnectivityManager mConnectivityManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.earthquake_activity);
mEmptyView = findViewById(R.id.empty_view);
// get the progress bar to indicate loading
mProgressBar = findViewById(R.id.loading_bar);
// set a view model provider for the current activity
mViewModelProvider = new ViewModelProvider(this);
// get the view model for the class
mMyModel = mViewModelProvider.get(MyModel.class);
// find a reference to the {@link RecyclerView} in the layout
mRecyclerView = findViewById(R.id.earthquakes);
mConnectivityManager = getSystemService(ConnectivityManager.class);
mEmptyView.setText("No internet available");
mEmptyView.setVisibility(View.VISIBLE);
Handler handler = new Handler(Looper.getMainLooper());
mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
@Override
public void onAvailable(Network network) {
handler.post(new Runnable() {
@Override
public void run() {
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.VISIBLE);
fetchData();
}
});
}
});
Log.e(LOG_TAG,"Visibility changed");
}
private void fetchData(){
// fetch the list of earthquakes
mMyModel.getEarthquakes().observe(EarthquakeActivity.this, new Observer<ArrayList<Earthquake>>() {
@Override
public void onChanged(ArrayList<Earthquake> earthquakes) {
Log.v(LOG_TAG,"fetching the data");
// set up recycler view with this data, this will work even if you rotate the device
setUpRecyclerView(earthquakes);
}
});
}
private void setUpRecyclerView(ArrayList<Earthquake> earthquakes) {
if(earthquakes == null || earthquakes.size() == 0) {
mRecyclerView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
mEmptyView.setText(R.string.no_data_available);
mEmptyView.setVisibility(View.VISIBLE);
}
else {
mRecyclerView.setVisibility(View.VISIBLE);
mEmptyView.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
// create adapter passing in the earthquake data
mEarthquakeAdapter = new EarthquakeAdapter(EarthquakeActivity.this, earthquakes);
// attach the adapter to the recyclerView to populate the items
mRecyclerView.setAdapter(mEarthquakeAdapter);
// set the layout manager to position the items
mRecyclerView.setLayoutManager(new LinearLayoutManager(EarthquakeActivity.this));
Log.e(LOG_TAG,"Recycler view about to be setup");
// click listener for when an item is clicked
mEarthquakeAdapter.setClickListener((view, position) -> searchWeb(earthquakes.get(position).getUrl()));
}
}
}
我的视图模型class
public class MyModel extends ViewModel {
private static final String LOG_TAG = "MyModel";
private MutableLiveData<ArrayList<Earthquake>> mMutableLiveData;
public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
mMutableLiveData = new MutableLiveData<>();
// call the API
init();
return mMutableLiveData;
}
public void init() {
// perform the network request on separate thread
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
// create array list of earthquakes
mMutableLiveData.postValue(QueryUtils.fetchEarthquakeData("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2021-06-01&limit=300"));
}
});
executorService.shutdown();
}
}
和我的 Query Utils class 相关方法只是因为 class 很大
private static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
// if url null, return
if(url == null) {
return jsonResponse;
}
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection= (HttpURLConnection)url.openConnection();
urlConnection.setReadTimeout(10000/*milliseconds*/);
urlConnection.setConnectTimeout(15000/*milliseconds*/);
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// this is being logged again even after screen rotation
Log.v(LOG_TAG,"Network request made");
// if the request was successful(response code 200)
//then read the input stream and parse the output
if(urlConnection.getResponseCode() == 200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
}
else{
Log.e(LOG_TAG,"Error Response code: " + urlConnection.getResponseCode());
}
}
catch (IOException e) {
Log.e(LOG_TAG,"Problem retrieving the earthquake JSON results",e);
}
finally {
if(urlConnection != null) {
urlConnection.disconnect();
}
if(inputStream != null) {
inputStream.close();
}
}
return jsonResponse;
请帮我看看哪里出错了。这让我现在很困惑。
看看这里,也许能帮到你 Activity restart on rotation Android
可能在屏幕旋转时您再次调用 onCreate,ViewModel 也是如此
编辑:尝试以这种方式调用 API
public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
mMutableLiveData = new MutableLiveData<>();
//call the API
mMutableLiveData.init();
配置更改时,您的 activity 会重新创建并调用 fetchData() 调用 getEarthquakes() 和 init()。
步骤应该是:
- 在 viewModel 中创建一个私有的 MutableLiveData
- 为该 MutableLiveData 创建一个 getter 方法
- 在 onCreate() 方法中,您应该在不进行任何调用的情况下注册观察者
- 然后您应该调用 public 方法来获取数据并将值发布到 MutableLiveData。
- 这样,您在 UI 中的观察器应该被触发。
您应该通过调用 getEarthquakes() 而不是调用 init() 来注册观察者。如果数据最初为空,则可以在观察者方法中进行空检查。
创建 activity 之后,您应该调用在 viewModel 中创建的 public 方法来获取数据,在您的例子中是 init()。