我已经查看了超过50个页面,为我非常简单的应用程序找到解决方案,但似乎没有一个对我有用。请帮忙。
问题:我在MainActivity的菜单中有一个刷新按钮。按下此按钮后,我想执行我的AsyncTask,然后更新我的RecyclerView中的所有项目。
状况。
我的应用程序通过OpenWeatherMap.org从API获取数据,然后显示数据。
我有一个MainActivity类(我的recyclelerView驻留在这里。)
所以,我尝试过但没有工作:
方法1.正常方式。选择刷新按钮后,调用我的AsyncTask。在我的PostExecute()中,我在MainActivity中调用setter
public void setWeatherData(String[] weatherData) {this.weatherData = weatherData;}
从doInBackGround方法中分配结果数组。
然后在MainActivity中,
myAsyncTask.execute("43017,us");
recyclerView.recyclerView.getAdapter().notifyDataSetChanged();
但这导致notifyDataSetChanged();
被调用BEFORE MainActivity中的成员可修改数组从onPostExecute()
更新。
方法2.尝试完全根据onPostExecute
中的MyAsyncTask.class
方法更新用户界面。
我知道onPostExecute
即使在不同的类中编写,也会在UI线程上运行。所以在方法中,我做了类似
MainActivity mainActivity = new MainActivity();
RecyclerView recyclerView = mainActivity.findViewById(R.id.recyclerView);
RecyclerView.Adapter adapter = recyclerView.getAdapter();
adapter.notifyDataSetChanged();
在这种方法中,通过日志记录,我确认onPostExecute
成功更新了MainActivity中的成员变量,只是notifyDataSetChanged
被提前调用TOO,特别是之前onPostExecute
已在后台完成。
我希望得到一个答案,并对我的情况很清楚。我将在下面发布我的MainActivity,Adapter和AsyncTask代码。
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private String weatherData[] = {
"Today - Sunny",
"Tomorrow - Cloudy",
"Tuesday - Rainy",
"Wednesday - Sunny",
"Thursday - Sunny",
"Friday - Sunny",
"Saturday - Cloudy",
"Sunday - Rainy :/"
};
private RecyclerView recyclerView;
private mAdapter adapter;
private static final int SPAN_COUNT = 1;
private MyAsyncTask myAsyncTask = new MyAsyncTask();
private Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.recyclerview_weatherData);
setLayout(getApplicationContext());
adapter = new mAdapter(weatherData);
recyclerView.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id){
case R.id.action_refresh:
Log.v("Menu", "Refresh button selected.");
//for now, take some random ZIP code
myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
return true;
}
public void setLayout(Context context) {
int scrollPosition = 0;
//make a GridLayoutManager with 2 columns
LinearLayoutManager mLayoutManager = new LinearLayoutManager(context);
//set the mLayoutManager to the one that I just created
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.scrollToPosition(scrollPosition);
recyclerView.setLayoutManager(new GridLayoutManager(context, SPAN_COUNT));
//set the offset decoration definition to my layout
int middle_spacing = 30;
boolean includeEdge = true;
recyclerView.addItemDecoration(new ItemOffsetDecoration(SPAN_COUNT, middle_spacing, includeEdge));
}
public void setWeatherData(String[] weatherData) {
this.weatherData = weatherData;
}
public String[] getWeatherData() { return weatherData; }
}
MyAsyncTask.java:
public class MyAsyncTask extends AsyncTask<String,Void,String[]> {
public final static String OPEN_WEATHER_MAP_API_KEY = "bc607b72747aa672bf2ac9a5f3a5fc84";
String forecastJsonStr = null;
private String format = "json";
private String units = "metric";
private int numDays = 7;
private String data[] =null;
private RecyclerView recyclerView;
private MainActivity mainActivity;
private RecyclerView.Adapter adapter;
@Override
protected String[] doInBackground(String... params) {
if (params.length == 0) {
Log.v("AsyncTask", "No parameter is taken.");
return null;
}
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
try {
final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?";
final String QUERY_PARAM = "q";
final String FORMAT_PARAM = "mode";
final String UNITS_PARAM = "units";
final String DAYS_PARAM = "cnt";
final String APPID_PARAM = "APPID";
Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon()
.appendQueryParameter(QUERY_PARAM, params[0])
.appendQueryParameter(FORMAT_PARAM, format)
.appendQueryParameter(UNITS_PARAM, units)
.appendQueryParameter(DAYS_PARAM, Integer.toString(numDays))
.appendQueryParameter(APPID_PARAM, OPEN_WEATHER_MAP_API_KEY)
.build();
URL url = new URL(builtUri.toString());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null) {
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
return null;
}
//put the buffer in String var forecastJsonStr
forecastJsonStr = buffer.toString();
Log.v("AsyncTask", forecastJsonStr.toString());
} catch (IOException e) {
return null;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("Async", "Reader is null, something wrong.");
}
}
}
//Then put the string contents into an array
try {
ParseWeatherData parser = new ParseWeatherData();
data = parser.getWeatherDataFromJson(forecastJsonStr, numDays);
return data;
} catch (JSONException e) {
e.printStackTrace();
}
return data;
}
@Override
protected void onPostExecute(final String data[]) {
super.onPostExecute(data);
if (data != null) {
//this log works fine: the fetched data is successfully stored...
for (int i = 0; i<data.length; i++) {
Log.v("onPostExecute", data[i].toString());
}
//how do I pass this data to the main thread?
mainActivity = new MainActivity();
mainActivity.setWeatherData(data);
}
}
}
最后,mAdapter.java:
public class mAdapter extends RecyclerView.Adapter<mAdapter.ViewHolder> {
private String data[];
public mAdapter(String data[]) {
this.data = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View listView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single_list, parent, false);
return new ViewHolder(listView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.weather.setText(data[position]);
Log.v("BindView", "Item " + position + " set.");
}
@Override
public int getItemCount() {
if (data == null) {
Log.v("WeatherAdapter", "Oops, getting null in the adapter.");
return 0;
} else {
return data.length;
}
}
public void refreshContents(String data[]) {
this.data = null;
this.data = data;
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView weather, day;
//currently ViewHolder is set as the TextView for logging
public ViewHolder(View v) {
super(v);
weather = (TextView) v.findViewById(R.id.test_text);
// Define click listener for the ViewHolder's View.
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");
}
});
}
}
}
提前谢谢!
答案 0 :(得分:0)
首先,不要在MyAsyncTask中创建MainActivity的新对象。实际上发生了什么是AsyncTask在新线程上运行所以当你做
时myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
然后myAsyncTask
在另一个线程中运行,接下来的行开始在.execute
之前执行而不等待asynctask
完成,即使它等待asynctask
到完成,创建一个新对象来更新MainActivity
中的数据是行不通的。因此,从onPostExecute
方法更新列表的方法是将参数传递给myAsyncTask
。不要在开始时初始化您的myAsyncTask
,只需从= new MyAsyncTask()
中的private MyAsyncTask myAsyncTask = new MyAsyncTask();
移除MainActivity
,然后用以下内容替换MainActivity中的行:
而不是在您的MainActivity中
myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
写下
myAsyncTask = new MyAsyncTask(this);
myAsyncTask.execute("43017,us");
要刷新内容,您可以将adapter
公开,以便您可以从myAsyncTask本身调用notifydatasetchanged
,但如果您想跟随.execute
之后编写的代码,那么您可以移动这是一个类似下面的新方法
public void refreshList(){
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
现在剩下的就是在MainActivity
中获取MyAsyncTask
的上下文并在onPostExecute
中刷新您的列表。所以进行如下更改:
创建MyAsyncTask的构造函数
public class MyAsyncTask extends AsyncTask<String,Void,String[]> {
MainActivity mainActivity;
public MyAsyncTask(MainActivity mainActivity){
this.mainActivity = mainActivity;
}
在MyAsyncTask
中替换它mainActivity = new MainActivity();
mainActivity.setWeatherData(data);
有了这个
mainActivity.setWeatherData(data);
mainActivity.refreshList();
我自己测试过,但我认为这可以解决你的问题。您可以尝试调试它并查看它是如何工作的。如果您遇到任何问题,请在下面发表评论。
修改强>
根据 @Ganesh Patil 的评论,您也可以为此解决方案创建一个界面。有关在AsyncTask中使用接口的参考,您可以点击此链接: https://stackoverflow.com/a/28958913/7071039
但为了保持简单,我没有使用interface
,只是在MainActivity
MyAsyncTask
的上下文
根据评论进行编辑
亲爱的PC HUB,首先非常感谢您的详细解答。 你所有的解释都是有道理的,我应用了这些代码。我的应用 现在没有崩溃,所有数据都被正确传递/检索但是 不知何故mainActivity.refreshList()和 recyclerView.getAdapter()。notifyDataSetChanged()仍然无法正常工作。 因此,我的recyclerView仍然根本没有改变内容...我 我不知道该怎么做。 - Rikuto Echigoya
将您的refreshList更改为此
public void refreshList(){
/* Check your Weather Data size in this method to find out weather your data is changing or not */
Log.d("Tag","SIZE OF WEATHER DATA : "+weatherData.length);
/* You already have the adapter object so you don't need to
get it using recyclerview.getAdapter. Just do it directly like this */
adapter.notifyDataSetChanged();
// Not changing this as this will not stop your list from refreshing :P
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
另请确保您在mainActivity.setWeatherData(data);
mainActivity.refreshList();
之前致电MyAsyncTask
。
如果仍然无法解决您的问题,请分享您的更新代码,以便我们了解您的列表未更新的原因:)