AsyncTask 和 AsyncTaskLoader 的使用

上一篇 JSON学习 中介绍了 JSON 对象的结构,如何解析 JSON 数据,但是我们的 JSON 数据是本地已经转换的 String 类型,现在我们处理网上的 JSON 数据,用到了网上的操作,必然要进行网络的连接,讲到网络部分,有必然使用到多线程,所以本篇侧重于介绍多线程部分。

为什么要使用多线程?

我们最初的 Android 学习中,默认都是在主线程( MainThread )进行操作的,主线程也叫做 UI 线程,一般的添加控件之类的操作都是主线程运行的,下面的图可以很好的说明,我们一般的绘图操作,点击按钮的操作,网络请求…都是在主线程上运行,但是这些操作的会被放到队列中,顺序执行,每次执行一个事件。

主线程

然而我们为什么要使用多线程,我们可能遇到过这样的情况,当我们访问一些 APP 的时候,在做一些网络请求的时候,界面会不动,当你点击界面上的按钮时,会出现是否停止响应的按钮。

结束未响应程序|center

为什么会出现这种情况?那是因为我们的网络请求很可能写到主线程上,当你进行网络请求时,应用可以需要一段时间去连接,获取,解析数据,但是同时你又多次按下了按钮,这些操作会出现到你的主线程队列上,但是应用并没有执行你的操作,以至于导致线程阻塞,一段时间后 Android 系统会显示上图的提示。

主线程

所以 Android 有个重要原则:不能把网络请求放到主线程 ,也就是不能阻塞 UI 线程,所以这个问题的解决方案就是使用多线程,让各自的操作到各自的线程中进行,网络访问一个线程,数据处理一个线程….这里我们只需要一个后台线程,用来处理网络请求。关于进程和线程可以访问官方链接

AsyncTask 处理多线程

Android为了降低开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,就是异步任务。它是在 Android 上线程之间进行线程和消息传递的抽象类,并不适用与任何情况,在使用的情况下,考虑当前的使用背景。
先来介绍一下 AyncTask 的基本用法。AyncTask 是一个抽象类,要使用这个抽象类需要建立一个子类去继承它,在继承这个类的同时需要规定三个泛型参数,我们为了便于理解,我们把着三个泛型参数放到方法之后讲解,先来讲解 AyncTask 的四个方法:

  1. onPreExcute():
    UI 线程中调用 该方法在后台任务执行之前调用,一般用于界面的初始化操作,如:显示一个进度条。

  2. doInBackground(Params…):
    后台线程中调用 ,该方法用于处理耗时操作,操作一旦完成既可以通过 return 来返回执行结果。用过要在该方法中更新 UI 可以手动调用 publishProgres(Progress…) 方法来完成。

  3. onProgressUpdate(Progress…):
    UI 线程中调用 利用该方法可以对 UI 进行相应的更新。该方法的使用必须在 doInBackground() 方法使用 publishProgres() 方法

  4. onPostExcute(Result):
    UI 线程中调用 在doInBackground(Params…)中返回的数据会作为参数传递到该方法中。 可以利用后台线程返回的参数进行 UI 操作。

现在我们在讲一讲 AyncTask 中的三个泛型参数,配合上述的方法,你会很快找到对应的规律和使用方法。

  • Params:在执行 AyncTask 需要传入的参数,用于后台任务使用。对应 doInBackground()
  • Progress:后台任务执行时,如果需要在界面上显示当前的进度。对应onProgressUpdate()
  • Result:当任务完毕的情况下,如果需要对结果进行返回,则使用该返回值类型,对应方法onPostExcute()

## 练习操作
>这个项目是获取网上地震数据并进行显示的应用。效果图如下:

效果图

先顶一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public final class QueryUtils {
private QueryUtils() {
}
//该方法进行网络连接,获取数据,解析数据
public static List<Info> fetchEarthquakeDatas(String requestUrl) throws MalformedURLException {
/*调用方法处理正确的url*/
URL url = createUrl(requestUrl);
/*定义一个json响应的变量*/
String jsonResponse = null;
try {
/*调用一个方法获取jsonResponse*/
jsonResponse = makeHttpRequest(url);
} catch (IOException e) {
e.printStackTrace();
}
/*extractFeatureFromJson一个提取json数据的方法*/
List<Info> listItem = extractFeatureFromJson(jsonResponse);
return listItem;
}
/*创建一个方法,处理传入的Url*/
private static URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return url;
}
/*创建一个方法,连接网络获取jsonResponse*/
private static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
if (url == null) return jsonResponse;
/*初始化网络连接*/
HttpURLConnection urlConnection = null;
/*初始化输入流,因为返回的是一个字符串类型,所以要读取数据*/
InputStream inputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(10000);
urlConnection.setConnectTimeout(15000);
urlConnection.setRequestMethod("GET");
urlConnection.connect();
if (urlConnection.getResponseCode() == 200) {
Log.i(TAG_LOG, "启动网络服务成功");
Log.v("MainActivity", String.valueOf(urlConnection.getResponseCode()));
/*得到他的输入流,也就是读取*/
inputStream = urlConnection.getInputStream();
/*调用一个方法,读取数据*/
jsonResponse = readFromStream(inputStream);
} else {
Log.i(TAG_LOG, "启动网络服务失败");
Log.v("MainActivity", String.valueOf(urlConnection.getResponseCode()));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) urlConnection.disconnect();
if (inputStream != null) {
inputStream.close();
}
}
return jsonResponse;
}
/*创建一个方法读取输入流的数据*/
private static String readFromStream(InputStream inputStream) {
/*字符串的拼接*/
StringBuilder output = new StringBuilder();
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(inputStreamReader);
try {
String line = reader.readLine();
while (line != null) {
output.append(line);
line = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/*如果输入流不为空,进行数据读取转换为字符串类型*/
return output.toString(); /*返回字符串*/
}
/*创建一个提取json数据的方法*/
private static List<Info> extractFeatureFromJson(String earthquakeJSON) throws MalformedURLException {
if (TextUtils.isEmpty(earthquakeJSON)) return null;
/*因为返回类型是List<Info>,所以新建一个对象*/
List<Info> listItem = new ArrayList<Info>();
try {
JSONObject root = new JSONObject(earthquakeJSON);
JSONArray featureArray = root.getJSONArray("features");
for (int i = 0; i < featureArray.length(); i++) {
JSONObject feature = featureArray.getJSONObject(i);
JSONObject properties = feature.getJSONObject("properties");
Double mag = properties.getDouble("mag");
String place = properties.getString("place");
Long time = properties.getLong("time");
String urlString = properties.getString("url");
URL url = new URL(urlString);
listItem.add(new Info(mag, place, time, url));
}
} catch (JSONException e) {
e.printStackTrace();
}
return listItem;
}
}

上面 QueryUtils 类 一个网络请求的类,我们需要使用 AsyncTask 内部类,调用我们这个网络访问类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*创建一个AsyncTask的内部类*/
private class EarthAsyncTask extends AsyncTask<String, Void, List<Info>> {
@Override
protected List<Info> doInBackground(String... urls) {
Log.i(TAG_LOG, "doInBackground()");
if (urls.length < 1 || urls[0] == null) return null;
List<Info> result = null;
try {
result = QueryUtils.fetchEarthquakeDatas(urls[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return result;
}
@Override
protected void onPostExecute(List<Info> result) {
/* 清除之前地震数据的适配器*/
adapter.clear();
/* add与addALL的区别在于add方法即便加入一个list也只是保存其中一条,addAll 保存全部*/
if (result != null && !result.isEmpty()) adapter.addAll(result);
}
}

为什么要使用 AsyncTaskLoader ?

当我们旋转屏幕的时候,会改变设备配置。设备配置是用来描述设备当前状态的一系列特征。当我们旋转屏幕时,我们重新创建了一个新的活动,相当于创建了两个 AsyncTask ,可能这个问题看起来没什么问题,我们都知道我们手机有内存限制,当我们频繁的旋转设备,就会造成大量的 AsyncTask 的资源占用,还有就是每次设备更改时,都会访问一次 Internet ,这不仅仅是造成不必要的流量丢失,还造成数据利用率极低。需要注意的,当 AsyncTask 数量增加时,Android 系统并不会自动释放不用的资源,因为 AsyncTask 是原始活动的的内部类,所以只有 AsyncTask 结束才会统一释放资源,如何处理上述所说的问题,这里就用到 Loader

关于 Loader 介绍,我们可以查看官方文档

在上述的项目中,我们如何加入 AsyncTaskLoader 来处理设备配置的问题?
首先,我们先创建一个子类继承 AsyncTaskLoader 并实现一个最重要的方法loadInBackground(),该方法和doInBackground()原理相同

其次,创建完这个 AsyncTaskLoader 的子类,我们需要用到一个 LoaderManager 顾名思义,它是管理我们 Loader 的类,要想让我们的类使用 Loader 就必须使用 LoaderManager.LoaderCallbacks 接口,这样 LoaderManager 就可以通知我们创建 加载器 Loader 。
并且实现三个方法

  1. onCreateLoader()
    当loadermanager调用initLoader()时, 首先检查指定的id是否存在,如果不存在才会触发该方法,通过该方法才能创建一个loader
  2. onLoadFinished()
    当一个加载器 完成了它的装载过程后被调用
  3. onLoaderReset()
    当一个加载器 被重置而什其数据无效时被调用

不多说直接贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class EarthquakeActivity extends AppCompatActivity implements android.app.LoaderManager.LoaderCallbacks<List<Info>> {
private TextView emptyTextView;
private InfoAdapter adapter;
private static final String REQUEST_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&orderby=time&minmag=5&limit=10";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG_LOG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.earthquake_activity);
emptyTextView = (TextView) findViewById(R.id.empty_tv);
/*获取listView*/
ListView earthquakeListView = (ListView) findViewById(R.id.list);
/*将空视图和list进行挂接,实现当加载不出来时,显示提示文本*/
earthquakeListView.setEmptyView(emptyTextView);
adapter = new InfoAdapter(this, new ArrayList<Info>());
/*绑定适配器*/
earthquakeListView.setAdapter(adapter);
/*判断是否联网*/
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetWork = cm.getActiveNetworkInfo();
if (activeNetWork != null && activeNetWork.isConnectedOrConnecting()) { /*初始化loader*/
getLoaderManager().initLoader(0, null, this); /*添加项目点击监听器*/
Log.i(TAG_LOG, "initLoader()");
} else {
View loading_progress_Bar = findViewById(R.id.loading_progressbar);
loading_progress_Bar.setVisibility(View.GONE);
emptyTextView.setText("no Internet");
}
earthquakeListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { /*查找单机的当前地震*/
Info currentInfo = (Info) adapter.getItem(i); /*获取uri对象???*/
Uri infoUri = Uri.parse(currentInfo.getURL().toString()); /*创建一个新的Intent*/
Intent intent = new Intent(Intent.ACTION_VIEW, infoUri);
startActivity(intent);
}
}); /* 实例化AsyncTask 并执行*/
EarthAsyncTask task = new EarthAsyncTask();
task.execute(REQUEST_URL);
}
@Override
public android.content.Loader<List<Info>> onCreateLoader(int i, Bundle bundle) {
Log.i(TAG_LOG, "onCreateLoader()");
return new EarthquakeLoader(this, REQUEST_URL);
}
@Override
public void onLoadFinished(android.content.Loader<List<Info>> loader, List<Info> infos) {
Log.i(TAG_LOG, "onLoadFinished()");
adapter.clear(); /* 如果存在 {@link Earthquake} 的有效列表,则将其添加到适配器的 数据集。这将触发 ListView 执行更新。 add与addALL的区别在于add方法即便加入一个list也只是保存其中一条,addAll 保存全部*/
if (infos != null && !infos.isEmpty()) adapter.addAll(infos);
View loading_progress_Bar = findViewById(R.id.loading_progressbar);
loading_progress_Bar.setVisibility(View.GONE);
emptyTextView.setText("no Text");
}
@Override
public void onLoaderReset(android.content.Loader<List<Info>> loader) {
Log.i(TAG_LOG, "onLoaderReset()");
adapter.clear();
} /*创建一个AsyncTask的内部类*/
private class EarthAsyncTask extends AsyncTask<String, Void, List<Info>> {
@Override
protected List<Info> doInBackground(String... urls) {
Log.i(TAG_LOG, "doInBackground()");
if (urls.length < 1 || urls[0] == null) return null;
List<Info> result = null;
try {
Log.i(TAG_LOG, "调用doInBackground+fetchEarthquakeDatas");
result = QueryUtils.fetchEarthquakeDatas(urls[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return result;
}
@Override
protected void onPostExecute(List<Info> result) { /* 清除之前地震数据的适配器*/
Log.i(TAG_LOG, "onPostExecute()");
adapter.clear(); /* 如果存在 {@link Earthquake} 的有效列表,则将其添加到适配器的 数据集。这将触发 ListView 执行更新。 add与addALL的区别在于add方法即便加入一个list也只是保存其中一条,addAll 保存全部*/
if (result != null && !result.isEmpty()) adapter.addAll(result);
}
}
}

总结:花了一下午时间去整理,后面的内容可能不是很详细,但是这种概念的东西,大家都可以的百度的到,还有有些代码都做了相关的注释,一些 UI 上的东西,跟本篇讲的可能关系不大,主要是处理应用的交互性,比如:判断是否联网,当没有联网的情况下提示文本,还有在进行网络访问的时候,添加一个进度条提示,应用正在处理。希望能给大家带来帮助!

(*^▽^*)