安卓学习日志 Day13 — 线程与并行

本文详细介绍了Android应用中线程与并行处理的重要性,通过一个名为'Did You Feel It?'的示例应用展示了主线程与后台进程的区别。文章解释了主线程执行网络操作会导致应用崩溃的原因,并引入了AsyncTask类来处理异步任务。此外,还讨论了Loader的使用,以避免资源浪费和多线程问题,提高应用的稳定性和用户体验。最后,提供了在网络连接不稳定或缺失时处理空数据和显示适当信息的建议。
摘要由CSDN通过智能技术生成

简介

现在已经在 Soonami 应用 中 成功使用网络来请求数据,那是否可以考虑 将网络应用到 QuakeReport 应用中呢?

答案是 No。我们在处理网络调用时需要许多操作及时发生,设备需要与无线信号连接 并发送请求,然后服务器必须创建响应并将其送回。这不知道要花费多少时间,而我们希望应用能够同时执行其他任务,例如 响应用户输入。

同时处理多个不同的任务,被称为多任务处理,每个任务可以看作是 Java 中的一个线程。

本文将通过一个简单的示例应用(叫做 Did You Feel It?),来了解这些线程与并行的基本概念,以及如何在 Android 应用中使用它们。最后会将这些都添加到 Quake Report 应用当中,以便从服务器中提取地震数据同时可以响应用户输入。

你感觉到了吗?

我们将简要探讨一个名为 “Did You Feel It?” 的新示例应用,该应用会查看特定地震 并告诉用户 人们感受到的地震强度。这些数据来自 USGS 网站,在 USGS 网站中有一个 Did You Feel It? 页面,从感觉到地震的人们那里收集信息,并绘制地图,显示人们的经历和破坏程度。

Did You Feel It? 应用的代码可以从 GitHub 仓库获得,使用 Git 命令克隆到初始代码:

git clone -b starting-point https://github.com/HEY-BLOOD/DidYouFeelIt.git

之后导入 到 Android Studio 中运行,程序会直接崩溃,且报错信息如下:

在这里插入图片描述

应用无法运行且提示存在一个叫做 android.os.NetworkOnMainThreadException 异常,从字面意思来看,好像是安卓系统中网络在主线程而产生的异常,事实也确实如此,这也是 Did You Feel It? 应用存在的意义。

主线程与后台进程

为什么 Did You Feel It? 应用崩溃了,NetworkOnMainThreadException 异常又到底意味着什么?

当出现 NetworkOnMainThreadException 异常时,在主线程中执行网络操作是不允许的,因为这会造成应用无响应或延迟。线程是保存指令序列的容器,目前为止 我们所写的所有代码都是 在主线程中执行的。主线程也称为 UI 线程,主线程可处理绘图操作,以响应用户输入(点击,滚动等),但主线程一次只能处理一个事件。所以 如果在同一时间发生多起事件,这些事件就会依次排在后面,等待前面的事件完成后再执行。

Android 系统能够同时运行多个线程, 因此可以相互独立的处理 两组或多组任务。如果有多个线程需要运行, Android 还会就何时运行哪些线程进行优先级排序, 并确定各线程的运行时长。

线程也知道如何保存其位置。它不仅会记录所有变量的值, 还会记住完成当前正在执行的指令 会调用的函数系列: 即能够重新完成工作所需的一切内容。 旨在利用多线程, 因此,在 Android 平台上构建应用并没有什么不同!

AsyncTask

在本文刚开始 测试运行 Did You Feel It? 应用时,出现了 NetworkOnMainThreadException异常,因为 Android 不允许开发人员在主线程进行网络请求,以至于造成应用无响应或延迟。

通常抛出一个异常使应用崩溃可以强制开发人员使用最优的办法,并且在后台线程运行网络操作,然后将结果返回给 UI 线程。就 Did You Feel It? 应用来说,并不需要线程的全部功能,只需要在一个单独的线程上,运行一个 HTTP 请求的任务即可,该线程不能是用于处理 UI 线程事件的线程。Android 框架工程师预料到了这会变成一个普通需求,并创建了专门的 Java 类使这一模式变得非常简单,这个类叫做 AsyncTask

AsyncTask 回调方法

AsyncTask 不同与之前所学的其他类型,如 Activity、View,它们都运行在主线程当中。而 AsyncTask 类的某些部分运行在主线程中,其他的部分运行在单独的后台线程中。

AsyncTask必须子类化才能使用。这个子类将覆盖至少一个方法 doInBackground (Params…),通常会覆盖第二个方法 onPostExecute (Result)

图片来自 https://www.runoob.com/w3cnote/android-tutorial-ansynctask.html

设计提示:理论上,应用中的内容是即时加载的。如果 未加载,请尝试允许用户与应用的其他部分进行交互, 以便用户不会坐在那里无所事事,或是观看进度条。

AsyncTask 泛型参数

AsyncTask 的泛型参数有三个,如:<Params, Progress, Result>。Params 作为输入,Progress 表示进度,Result 为运行结束后的返回结果。

官方示例如下:

Here is an example of subclassing:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
   
     protected Long doInBackground(URL... urls) {
   
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
   
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
   
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
   
         showDialog("Downloaded " + result + " bytes");
     }
 }
 

Once created, a task is executed very simply:

 new DownloadFilesTask().execute(url1, url2, url3);

修复 Did You Feel It?

  1. 创建 AsyncTask 的子类作为 MainActivity 类中的 私有内部类。 实现 doInBackground() 方法来获取地震数据并 返回结果。 实现 onPostExecute() 方法以根据我们的结果 更新 UI。

    私有内部类EarthquakeAsyncTask 定义如下:

    /**
     * Displays the perceived strength of a single earthquake event based on responses from people who
     * felt the earthquake.
     */
    public class MainActivity extends AppCompatActivity {
         
    
        …………
    
        private class EventAsyncTask extends AsyncTask<String, Void, Event> {
         
            @Override
            protected Event doInBackground(String... urls) {
         
                // Perform the HTTP request for earthquake data and process the response.
                Event earthquake = Utils.fetchEarthquakeData(USGS_REQUEST_URL);
                return earthquake;
            }
    
            @Override
            protected void onPostExecute(Event result) {
         
                // Update the information displayed to the user.
                updateUi(result);
            }
        }
    
    }
    
  2. 在 MainActivity 的 onCreate() 方法中创建内部类的实例, 并执行。

    public class MainActivity extends AppCompatActivity {
         
    
    	…………
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
         
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            EarthquakeAsyncTask task = new EarthquakeAsyncTask();
            task.execute(USGS_REQUEST_URL);
    
        }
        …………
    }
    
  3. 运行应用 效果如下(更改前后代码对比参考 此链接):

处理空或 null 情况

目前,所有的代码是针对最佳 情况编写的。假设 EarthquakeAsyncTask 通过预期的输入调用, 且在执行网络请求时未出现任何 错误。

如果…

  • 未来使用此应用的队友更改了代码,并试图以不输入任何参数的方式来执行 EarthquakeAsyncTask,这将如何?应用可能会崩溃,因为它假设至少为任务输入 1 个字符串。
  • USGS 的计算机存在内部服务器错误,无法将响应解析为 Event 对象,这又将如何?应用将尝试使用无效或空 Event 对象更新 UI。

试图进一步验证应用,以便接触代码的任何其他开发者 不会不小心在应用中导致 bug 或崩溃。 为此,需要将当前类以外的代码编写假设、 以及超出控制的假设 最小化。

如果可以接受任何输入(零输入、1 个 输入、2 个输入等)或处理任何意外行为(服务器 做出有效或无效响应),并做出得体的处理而不使应用崩溃, 那么就说明代码变得更加稳健。

为了这个目的,对 “你感觉到了吗?” 应用进行下列修改, 以处理 EarthquakeAsyncTask 中空或 Null 情况。

在 doInBackground 方法中,检查 url 数组是否至少具有 1 个 条目且第一个条目不为空。如果数组长度为 0 或第一个条目为空,则通过返回 null 提早离开 此方法。我们需要返回 null,因为需要有对象 作为返回值。如果有 1 个有效字符串 URL,则继续 获取数据。

  protected Event doInBackground(String... urls) {
   
      // 如果不存在任何 URL 或第一个 URL 为空,切勿执行请求。
      if (urls.length < 1 || urls[0] == null) {
   
         return null;
      }

      Event result = Utils.fetchEarthquakeData(urls[0]);
      return result;
  }

在 onPostExecute 方法中,如果不存在地震结果,则提早 返回。

  protected void onPostExecute(Event result) {
   
     // 如果不存在任何结果,则不执行任何操作。
     if (result == null) {
   
         return;
     }

     updateUi(result);
  }

下面是完整的 EarthquakeAsyncTask 类声明。

  /**
  * {@link AsyncTask} 用于在后台线程上执行网络请求,然后
  * 使用响应中的第一个地震更新 UI。
  */
  private class EarthquakeAsyncTask extends AsyncTask<String, Void, Event> {
   

     /**
      * 此方法在后台线程上激活(调用),因此我们可以执行
      * 诸如做出网络请求等长时间运行操作。
      *
      * 因为不能从后台线程更新 UI,所以我们仅返回 
      * {@link Event} 对象作为结果。
      */
     protected Event doInBackground(String... urls) {
   
         // 如果不存在任何 URL 或第一个 URL 为空,切勿执行请求。
         if (urls.length < 1 || urls[0] == null) {
   
             return null;
         }

         Event result = Utils.fetchEarthquakeData(urls[0]);
         return result;
     }

     /**
      * 此方法是在完成后台工作后,在主 UI 线程上
      * 激活的。
      *
      * 可以在此方法内修改 UI。我们得到 {@link Event} 对象
      * (该对象从 doInBackground() 方法返回),并更新屏幕上的视图。
      */
     protected void onPostExecute(Event result) {
   
         // 如果不存在任何结果,则不执行任何操作。
         if (result == null) {
   
             return;
         }

         updateUi(result);
     }
  }

最后编译并运行应用时,应该看起来和之前没有任何区别,代码更改前后对比参考 此链接。

要在 GitHub 上浏览“你感觉到了吗?”应用的完整和最终状态, 请单击此处

更改地震报告应用

这两天已经了解了足够多的概念,HTTP、URL、HttpURLConnection、线程等,所以是时候来完成挑战了。

现在 回到 Quake Report 应用当中,使用 在 Soonami 应用中学到的 HTTP 网络 和 Did You Feel It? 应用中学到的异步任务,来改进 Quake Report 应用,以便能够从 网络请求数据。

访问 安卓学习日志 Day11 — JSON 解析 回顾一下 Quake Report 应用。

我列出了一个 更改 Quake Report 应用的清单:

  • 在 QueryUtils 中移除硬编码的 JSON 响应
  • 在 QueryUtils 类中添加赋值方法来创建 URL 对象、执行网络请求、输入流转化为字符串、解析 JSON(此查询 将提供全球最近发生的震级至少为 5 级 的 最多20个地震)
  • 修改 JSON 解析方法从 Web 服务器响应中提取地震列表
  • 在 MainActivity 中声明内部类 EarthquakeAsyncTask 作为异步任务
  • 在 MainActivity.onCreate() 方法中实例化 EarthquakeAysncTask 并执行任务
  • 最后,添加所需的 Internet 权限

这里涉及大量步骤。我已经在下面以文本形式写出了这些编码步骤。

移除硬编码响应

首先应该移除 QueryUtils 类中硬编码的 JSON 响应,因此 在 QueryUtils 类中找到如下代码并删除:

/**
 * Sample JSON response for a USGS query
 */
private static final String SAMPLE_JSON_RESPONSE = ………… ;

这时在已有的 extractEarthquakes() 方法中 会引发一个 错误提示 Cannot resolve symbol 'SAMPLE_JSON_RESPONSE' 表示无法解释 SAMPLE_JSON_RESPONSE 这个变量,因为被删除了。

然后将我们提供的字符串 URL 作为静态最终常量存储在 EarthquakeActivity 文件中。我们在变量上使用 “private” 访问修饰符,因为除了 EarthquakeActivity 外,没有其他类需要引用它。

在 EarthquakeActivity.java 中:

    /**
     * URL for earthquake data from the USGS dataset
     */
    private static final String USGS_REQUEST_URL =
            "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&orderby=time&minmag=5&limit=20";

网络权限

首先在 AndroidManifest.xml 文件中声明 Internet 权限,以便应用能够访问网络。

在 AndroidManifest.xml 中:

 <uses-permission android:name="android.permission.INTERNET"/>

基础轮廓

然后将我们提供的字符串 URL 作为静态最终常量存储在 EarthquakeActivity 文件中。我们在变量上使用 “private” 访问修饰符,因为除了 EarthquakeActivity 外,没有其他类需要引用它。

在 Earth

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值