Android Full App,第4部分:与主要活动异步执行API请求

这是“ Android完整应用程序教程”系列的第四部分。 完整的应用程序旨在提供一种通过互联网搜索表演电影/演员的简便方法。 在本系列的第一部分( “主要活动UI” )中,我们创建了Eclipse项目并为应用程序的主要活动设置了基本界面。 在第二部分( “使用HTTP API” )中,我们使用Apache HTTP客户端库来使用外部HTTP API并将API的搜索功能集成到我们的应用程序中。 在第三部分( “解析XML响应” )中,我们看到了如何使用Android内置的XML解析功能来解析XML响应。 在这一部分中,我们将把HTTP检索器和XML解析器服务结合在一起,以便从应用程序的主要活动中执行API搜索请求。 该请求将在后台线程中异步执行,以避免阻塞主UI线程。

在移动应用程序开发中,应用程序行为的一个非常重要的方面是顺利执行。 该应用程序对用户输入的响应应该Swift,并且整个体验应该流畅而敏捷。 应用程序的响应能力非常重要,尤其是对于Android平台而言,Google已为此发布了一些设计准则 。 这就是我们让应用程序在后台执行搜索操作的原因,这意味着这些操作将在除主UI线程之外的其他线程中执行。

尽管在过去的几年中移动设备上的Internet连接速率已经得到了极大的提高,但仍然存在一个事实,即从Internet下载数据可能是一项耗时的操作。 因此,我们不想在HTTP客户端等待数据下载时暂停主线程。 另请注意,将主线程UI停滞超过五秒钟将导致“应用程序无响应”(ANR)对话框出现,并且用户将有机会杀死您的应用程序。 那绝对不是一个功能。

为此,我们将利用Android API并使用名为AsyncTask的内置类。 我已经在我的上一篇文章( “使用Yahoo API进行Android反向地理编码– PlaceFinder” )中解释了它的用法,但总而言之,该类使我们能够正确,轻松地使用UI线程。 从官方文档页面 :“ AsyncTask支持正确轻松地使用UI线程。 该类允许执行后台操作并在UI线程上发布结果,而无需操纵线程和/或处理程序。

在开始编写异步代码之前,我们将首先介绍一些服务类,这些服务类负责执行HTTP请求,解析XML响应,创建相应的模型对象并将它们返回给调用方Activity。 这些类将使用以下源代码扩展名为GenericSeeker的抽象基类:

package com.javacodegeeks.android.apps.moviesearchapp.services;

import java.net.URLEncoder;
import java.util.ArrayList;

public abstract class GenericSeeker<E> {
    
    protected static final String BASE_URL = "http://api.themoviedb.org/2.1/";    
    protected static final String LANGUAGE_PATH = "en/";
    protected static final String XML_FORMAT = "xml/";
    protected static final String API_KEY = "<YOUR_API_KEY_HERE>";
    protected static final String SLASH = "/";
    
    protected HttpRetriever httpRetriever = new HttpRetriever();
    protected XmlParser xmlParser = new XmlParser();
    
    public abstract ArrayList<E> find(String query);
    public abstract ArrayList<E> find(String query, int maxResults);

    public abstract String retrieveSearchMethodPath();
    
    protected String constructSearchUrl(String query) {
        StringBuffer sb = new StringBuffer();
        sb.append(BASE_URL);
        sb.append(retrieveSearchMethodPath());
        sb.append(LANGUAGE_PATH);
        sb.append(XML_FORMAT);
        sb.append(API_KEY);
        sb.append(SLASH);
        sb.append(URLEncoder.encode(query));
        return sb.toString();
    }
    
    public ArrayList<E> retrieveFirstResults(ArrayList<E> list, int maxResults) {
        ArrayList<E> newList = new ArrayList<E>();
        int count = Math.min(list.size(), maxResults);
        for (int i=0; i<count; i++) {
            newList.add(list.get(i));
        }
        return newList;
    }

}

GenericSeeker类表示它能够找到特定类的结果,而扩展类将必须为适当的类提供具体的实现。 将使用我们之前教程中的HttpRetriever和XmlParser对象。 请记住, TMDb API使用相似的URL进行电影和人物搜索:

因此,我们使用一个通用的基本URL,扩展类必须通过实现“ retrieveSearchMethodPath”方法来提供附加路径。 还必须实现两个方法find(String)和find(String,int),这两个方法均返回具有适当类的对象的ArrayList 。 可以使用第二个来缩小结果总数。 这可能会有帮助,因为API通常返回与搜索查询不太相关的结果,可以将其丢弃以提高性能。 最后,不要忘记用TMDb站点中的有效密钥替换API_KEY变量的值。

接下来,我们有两个子类的代码,MovieSeeker和PersonSeeker。 这些类非常相似,因此为了简洁起见,我在这里仅介绍一个类:

package com.javacodegeeks.android.apps.moviesearchapp.services;

import java.util.ArrayList;

import android.util.Log;

import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;

public class MovieSeeker extends GenericSeeker<Movie> {
        
    private static final String MOVIE_SEARCH_PATH = "Movie.search/";
    
    public ArrayList<Movie> find(String query) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return moviesList;
    }
    
    public ArrayList<Movie> find(String query, int maxResults) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return retrieveFirstResults(moviesList, maxResults);
    }
    
    private ArrayList<Movie> retrieveMoviesList(String query) {
        String url = constructSearchUrl(query);
        String response = httpRetriever.retrieve(url);
        Log.d(getClass().getSimpleName(), response);
        return xmlParser.parseMoviesResponse(response);
    }

    @Override
    public String retrieveSearchMethodPath() {
        return MOVIE_SEARCH_PATH;
    }

}

私有方法“ retrieveMoviesList”是该类的核心。 它首先构造API调用的URL,然后使用HttpRetriever类实例执行HTTP请求。 如果请求成功,则XML响应将馈送到XmlParser服务,该服务负责将响应映射到Movie模型对象。 find(String)和find(String,int)方法实际上是私有方法的包装。

现在,我们准备使用主要活动中的搜索服务。 首先,我们创建这些服务的实例,如下所示:

...
private GenericSeeker<Movie> movieSeeker = new MovieSeeker();
private  GenericSeeker<Person> personSeeker = new PersonSeeker();
...

如本文开头所述,对这些类的find方法的调用应在UI线程之外的其他线程中执行。 现在是时候创建我们的AsyncTask实现,以下是用于电影搜索的内容:

...
private class PerformMovieSearchTask extends AsyncTask<String, Void, List<Movie>> {

   @Override
   protected List<Movie> doInBackground(String... params) {
      String query = params[0];
      return movieSeeker.find(query);
   }
   
   @Override
   protected void onPostExecute(final List<Movie> result) {         
      runOnUiThread(new Runnable() {
      @Override
      public void run() {
         if (progressDialog!=null) {
            progressDialog.dismiss();
            progressDialog = null;
         }
         if (result!=null) {
               for (Movie movie : result) {
                  longToast(movie.name + " - " + movie.rating);
               }
            }
      }
       });
   }
      
}
...

首先,我们声明我们的实现扩展了AsyncTask>类。 此签名意味着执行后发送到任务的参数类型为String类型,在后台计算过程中不会发布任何进度单元(由Void表示),并且后台计算的结果为List类型,其保持电影对象。 在doInBackground方法中,我们执行实际的HTTP数据检索,然后在onPostExecute方法中,以Toast通知的形式呈现结果。 请注意,还将创建另一个名为“ PerformPersonSearchTask”的任务类,并将其用于执行人员搜索。

请注意,使用ProgressDialog小部件是为了让用户知道发生了数据检索并且他应该耐心等待。 进度对话框在其工厂方法中被声明为可取消,因此用户可以根据要求取消任务。 相关代码如下:

...
private void performSearch(String query) {
        
        progressDialog = ProgressDialog.show(MovieSearchAppActivity.this,
                "Please wait...", "Retrieving data...", true, true);
        
        if (moviesSearchRadioButton.isChecked()) {
            PerformMovieSearchTask task = new PerformMovieSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
        else if (peopleSearchRadioButton.isChecked()) {
            PerformPersonSearchTask task = new PerformPersonSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
        
    }
...

我们还为进度对话框的OnCancelListener提供了一个实现,其唯一目的是取消相关任务。 代码如下:

...
private class CancelTaskOnCancelListener implements OnCancelListener {
        private AsyncTask<?, ?, ?> task;
        public CancelTaskOnCancelListener(AsyncTask<?, ?, ?> task) {
            this.task = task;
        }
        @Override
        public void onCancel(DialogInterface dialog) {
            if (task!=null) {
                task.cancel(true);
            }
        }
    }
...

让我们看看到目前为止的代码结果。 使用Eclipse运行项目的配置并启动应用程序。 在编辑文本中提供查询字符串,然后单击按钮以执行搜索。 出现进度对话框,通知请求操作,如下图所示:

用户可以随时单击“返回”按钮来取消任务。 如果操作成功,则将触发回调方法,并向用户呈现每个结果的祝酒词,如下图所示:

至此,本系列教程的第四部分结束。 您可以在此处下载到目前为止创建的Eclipse项目。

相关文章 :

翻译自: https://www.javacodegeeks.com/2010/10/android-full-app-part-4-asynchronous.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值