如何应用从doInBackground()获取的数据以应用于我的RecyclerView?

时间:2017-02-25 04:41:06

标签: java android android-studio android-asynctask java-threads

我已经查看了超过50个页面,为我非常简单的应用程序找到解决方案,但似乎没有一个对我有用。请帮忙。

问题:我在MainActivity的菜单中有一个刷新按钮。按下此按钮后,我想执行我的AsyncTask,然后更新我的RecyclerView中的所有项目。

状况。

  1. 我的应用程序通过OpenWeatherMap.org从API获取数据,然后显示数据。

  2. 我有一个MainActivity类(我的recyclelerView驻留在这里。)

  3. 对于recyclerView,我使用RecyclerView.Adapter和GridViewManager。
  4. 我有一个单独的AsyncTask类。
  5. 所以,我尝试过但没有工作:

    方法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.");
                    }
                });
            }
    
        }
    }
    

    提前谢谢!

1 个答案:

答案 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

如果仍然无法解决您的问题,请分享您的更新代码,以便我们了解您的列表未更新的原因:)