第二十三个项目——使用AsyncTask进行网络异步请求

实验目的

  • 掌握AsyncTask进行异步任务处理的基本流程;

  • 能使用AsyncTaskOkhttp网络库实现网络数据异步请求处理;

实验要求

  • 自定义AsyncTask子类NewsListAsyncTask,重载doInBackground()onPostExecute()方法;

实验内容

第十四个项目中,使用okhttp网络请求库调用天行数据API新闻接口,并在ListView中显示获取的新闻列表数据。在MainActivity中通过定义okhttp3.Callback回调接口,并使用Call.enqueue(okhttp3.Callback)异步形式进行网络请求。

但由于Android不允许在主线程中进行网络请求操作,因此实际的网络请求必须在子线程中完成。在子线程中获取天行数据API新闻接口返回数据后,再通过runOnUiThread()方法将获取的数据更新。这些步骤看起来十分复杂繁琐,且大量代码堆叠在MainActivity活动中,如果一个活动需要请求多个网络接口的数据,那么会造成该活动的代码十分臃肿也不利于维护。

为了解决上述问题,Android框架提供了AsyncTask异步任务类,AsyncTask封装了子线程创建, 子线程与UI主线程的通信等操作,对开发人员暴露以下方法,以方便使用:

  • void onPreExecute(),进行异步任务操作前的回调接口,用户可在该接口中进行任务初始化的必要操作,该方法将在UI主线程中执行;

  • Result doInBackground(Params... params)AsyncTaskdoInBackground()方法中的代码放入子线程中执行,Params为子类化AsyncTask时指定的第一个模板参数类型,用于在UI主线程传递参数给doInBackground()方法。Result为子类化AsyncTask时指定的第三个模板参数类型,表示子线程返回给主线程的数据类型;

  • void onProgressUpdate(Progress ),可以doInBackground()方法内通过publishProgress()更新异步任务进度,onProgressUpdate()方法将在主线程中被执行。通常Progress类型选择为Integer类型;

  • void onPostExecute(Result result),当子线程返回后,该方法将在UI主线程中被执行,其参数ResultdoInBackground()方法的返回值。

通过上述几个方法,AsyncTask封装了子线程启动、通过Handler进行主线程与子线程通信等操作,暴露给调用者的仅仅是AsyncTask的对象构造以及execute(Params... params)方法。从而使得AsyncTask对象的调用者代码更加简洁,也使得代码更便于维护。

步骤一,打开第十四个项目

打开第十四个项目,在该项目的基础上完成本次实验。

步骤二,定义NewsListAsyncTask类

定义NewsListAsyncTask类继承至AsyncTask类,并重写doInBackground()方法及onPostExecute()方法,具体代码如下所示。

doInBackground()方法中的代码是原项目中放在子线程执行网络请求的代码,其参数为Integer类型的变长参数,其实参为实例化NewsListAsyncTask对象后调用execute()方法所传递进来的参数。在此我们将请求天行数据API所需的频道、每页新闻数量、当前页码三个参数传递给doInBackground()方法。

doInBackground()方法内,还需构造Request请求对象、OkHttpClient请求客户端对象等,最后通过Call.execute()方法执行同步网络请求。 这一点与第十四个项目中使用OkHttpClientCall.enqueue(Callback)进行异步网络请求略微不同。Call.execute()方法会阻塞当前线程直到Request对应的Response可用为止,因此可以直接在doInBackground()方法中获取Response对象,判断其返回状态再获取其ResponseBody响应体。并将ResponseBody响应体转换为String类型作为doInBackground()方法的返回值。

在介绍AsyncTask异步任务类的几个方法时,我们说过doInBackground()方法的代码将在子线程中执行,而当doInBackground()方法返回后,onPostExecute()方法将被在主线程中执行,而该方法的参数实际对应了doInBackground()方法的返回值。也就是说,doInBackground()方法将子线程处理后的结果交由onPostExecute()方法在主线程中进行进一步处理。其实现机制是Handler异步消息通信机制。

onPostExecute()方法中,需要将ResponseBody响应体的数据进行解析,并转换成对应的News新闻对象,并添加至ListView绑定的NewsAdapter适配器中。该方法的代码与第十四个项目okhttp3.Callback接口中的onResponse()方法的代码作用相同。只不过由于onPostExecute()方法本身在UI主线程中执行,因此不在需要runOnUiThread()方法将其切入UI主线程中。

最后NewsListAsyncTask的构造函数接收了两个参数,分别是Context上下文以及NewsAdapter适配器类,主要为了便于在onPostExecute()等方法中更新适配器的数据。

public class NewsListAsyncTask 
        extends AsyncTask<Integer, Void, String> {

    private NewsAdapter adapter;
    private Context context;

    public NewsListAsyncTask(Context context, NewsAdapter adapter) {
        this.context = context;
        this.adapter = adapter;
    }

    @Override
    protected String doInBackground(Integer... integers) {

        Integer col = integers[0];
        Integer newsNum = integers[1];
        Integer page = integers[2];

        NewsRequest requestObj = new NewsRequest();

        requestObj.setCol(col);
        requestObj.setNum(newsNum);
        requestObj.setPage(page);
        String urlParams = requestObj.toString();

        Request request = new Request.Builder()
                .url(Constants.GENERAL_NEWS_URL + urlParams)
                .get().build();
        try {
            OkHttpClient client = new OkHttpClient();
            Response response = client.newCall(request).execute();

            if (response.isSuccessful()) {
                String body = response.body().string();
                return body;
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String body) {
        Gson gson = new Gson();
        Type type = new TypeToken<BaseResponse<List<News>>>() {}.getType();

        BaseResponse<List<News>> newsListResponse = gson.fromJson(body, type);
        for (News news:newsListResponse.getData()) {
            adapter.add(news);
        }

        adapter.notifyDataSetChanged();
        super.onPostExecute(body);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
}

步骤三,修改MainActivity初始化数据代码

完成上述步骤后,最后需要修改MainActivity活动类中进行数据刷新的refreshData()方法的代码,具体代码如下所示。 在该方法中仅需要实例化NewsListAsyncTask对象并调用其execute()方法传递相应的天行数据API新闻频道、每页新闻数量、当前新闻页码参数即可。

而原来在MainActivity中定义的okhttp3.Callback接口可直接删除。如果仔细对比当前MainActivity的代码与第十四个项目的代码,你会发现使用AsyncTask异步任务类封装原来的网络请求代码后,MainActivity的代码更加简洁清晰,也更利于代码维护。如果需要多个接口请求,则再子类化AsyncTask实现特定的业务即可。

public class MainActivity extends AppCompatActivity {

    ...
    private void refreshData(final int page) {
        new NewsListAsyncTask(MainActivity.this, adapter)
                .execute(new Integer[]{
                        mCols[mCurrentColIndex],
                        Constants.NEWS_NUM,
                        page});
    }

    ...
}

步骤四,编译并运行项目

编译并运行项目,其执行效果第十四个项目相同。

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 掌握子类化AsyncTask异步任务进行异步操作的基本流程;

  • 能使用AsyncTask实现异步任务处理;

Last updated

Was this helpful?