这是“ 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进行电影和人物搜索:
- Movie.search ( http://api.themoviedb.org/2.1/methods/Movie.search )提供了搜索电影的最简单,最快的方法。
- Person.search ( http://api.themoviedb.org/2.1/methods/Person.search )用于搜索演员,女演员或制作成员。
因此,我们使用一个通用的基本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项目。
- “ Android完整应用程序教程”系列
- Android文字转语音应用
- 带有Yahoo API的Android反向地理编码– PlaceFinder
- Android基于位置的服务应用程序– GPS位置
- 使用VirtualBox在PC上安装Android OS
- 拥抱Android的强大功能:快速概述
翻译自: https://www.javacodegeeks.com/2010/10/android-full-app-part-4-asynchronous.html