AsyncTask 和 AsyncTaskLoader 的使用

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

为什么要使用多线程?

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

《AsyncTask 和 AsyncTaskLoader 的使用》 主线程

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

《AsyncTask 和 AsyncTaskLoader 的使用》 结束未响应程序|center

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

《AsyncTask 和 AsyncTaskLoader 的使用》 主线程

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

AsyncTask 处理多线程

Android为了降低开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,就是异步任务。
先来介绍一下 AyncTask 的基本用法。AyncTask 是一个抽象类,要使用这个抽象类需要建立一个子类去继承它,在继承这个类的同时需要规定三个泛型参数,我们为了便于理解,我们把着三个泛型参数放到方法之后讲解,先来讲解 AyncTask 的四个方法:

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

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

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

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

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

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

练习操作

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

《AsyncTask 和 AsyncTaskLoader 的使用》 效果图

先顶一个


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 内部类,调用我们这个网络访问类。

 /*创建一个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<E> 接口,这样 LoaderManager 就可以通知我们创建 加载器 Loader 。
并且实现三个方法

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

不多说直接贴代码

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 上的东西,跟本篇讲的可能关系不大,主要是处理应用的交互性,比如:判断是否联网,当没有联网的情况下提示文本,还有在进行网络访问的时候,添加一个进度条提示,应用正在处理。希望能给大家带来帮助!

    原文作者:有情绪的仙人掌
    原文地址: https://www.jianshu.com/p/a354d8d67a29
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞