安卓中的线程

应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget)和 android.view 软件包的组件)进行交互的线程。因此,主线程有时也称为 UI 线程

系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。

例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身。

在应用执行繁重的任务以响应用户交互时,除非正确实现应用,否则这种单线程模式可能会导致性能低下。 具体地讲,如果 UI 线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户的角度来看,应用显示为挂起。 更糟糕的是,如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“应用无响应”(ANR) 对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。

此外,Android UI 工具包并非线程安全工具包。因此,你不应通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。因此,Android 的单线程模式必须遵守两条规则

  1. 不要阻塞 UI 线程
  2. 不要在 UI 线程之外访问 Android UI 工具包

工作线程

根据上述单线程模式,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。 如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。

例如,以下代码演示了一个点击侦听器从单独的线程下载图像并将其显示在 ImageView

public void onClick(View v) {
    new Thread(()->{
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
    }).start();
}

乍看起来,这段代码似乎运行良好,因为它创建了一个新线程来处理网络操作。 但是,它违反了单线程模式的第二条规则:不要在 UI 线程之外访问 Android UI 工具包 ——— 此示例从工作线程(而不是 UI 线程)修改了 ImageView。 这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。

为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程。 以下列出了几种有用的方法:

  • Activity.runOnUiThread(Runnable)

  • View.post(Runnable)

  • View.postDelayed(Runnable, long)

例如,你可以通过使用 View.post(Runnable)方法修复上述代码:

public void onClick(View v) {
    new Thread(()-> {
            final Bitmap bitmap =
                   loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(()-> {
                    mImageView.setImageBitmap(bitmap);
            });
    }).start();
}

现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 ImageView

但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask类,此类简化了与 UI 进行交互所需执行的工作线程任务。

使用 AsyncTask

AsyncTask允许对用户界面执行异步操作。 它会先阻塞工作线程中的操作,然后在 UI 线程中发布结果,而无需你亲自处理线程和/或处理程序。

要使用它,必须创建AsyncTask的子类并实现 doInBackground()回调方法,该方法将在后台线程池中运行。 要更新 UI,应该实现 onPostExecute()以传递 doInBackground()返回的结果并在 UI 线程中运行,以便安全地更新 UI。 稍后,你可以通过从 UI 线程调用 execute()来运行任务。

例如,你可以通过以下方式使用 AsyncTask
来实现上述示例:

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /**此系统调用在工作线程中执行,其参数来自AsyncTask.execute()的参数 */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

    /**此系统调用在UI线程中执行,其参数来自doInBackground()的返回值 */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

现在 UI 是安全的,代码也得到简化,因为任务分解成了两部分:一部分应在工作线程内完成,另一部分应在 UI 线程内完成。

下面简要概述了 AsyncTask 的工作方法,但要全面了解如何使用此类,你应阅读 AsyncTask 参考文档:

  • 可以使用泛型指定参数类型、进度值和任务最终值
  • 方法 doInBackground()会在工作线程上自动执行
  • onPreExecute()onPostExecute()onProgressUpdate() 均在 UI 线程中调用
  • doInBackground()返回的值将发送到 onPostExecute()
  • 可以随时在 doInBackground()中调用publishProgress(),以在 UI 线程中执行 onProgressUpdate()
  • 可以随时取消任何线程中的任务

注意:使用工作线程时可能会遇到另一个问题,即:运行时配置变更导致 Activity 意外重启,这可能会销毁工作线程。 要了解如何在这种重启情况下坚持执行任务,以及如何在 Activity 被销毁时正确地取消任务,请查阅相关文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android,UI线程(也称为主线程)是用于处理界面事件和更新界面的线程。在线程修改UI可能会导致ANR(应用程序无响应)错误,因为UI线程可能会被阻塞,导致应用程序无法响应用户的操作。因此,需要使用一些技术来在后台线程更新UI,以避免阻塞UI线程。 以下是一些在线程修改UI的方法: 1.使用Handler:Handler是Android的一个机制,可以用于在后台线程更新UI。可以在UI线程创建一个Handler对象,然后使用post()方法将一个Runnable对象发送到Handler对象,以便在UI线程执行该Runnable对象。示例代码如下: ```java // 创建一个Handler对象 Handler handler = new Handler(Looper.getMainLooper()); // 在后台线程发送一个Runnable对象到Handler对象 new Thread(new Runnable() { @Override public void run() { // 在后台线程更新UI handler.post(new Runnable() { @Override public void run() { // 在UI线程更新UI textView.setText("Hello World"); } }); } }).start(); ``` 2.使用runOnUiThread()方法:Activity类提供了一个runOnUiThread()方法,可以用于在后台线程更新UI。可以将一个Runnable对象传递给该方法,该方法将在UI线程执行该Runnable对象。示例代码如下: ```java new Thread(new Runnable() { @Override public void run() { // 在后台线程更新UI runOnUiThread(new Runnable() { @Override public void run() { // 在UI线程更新UI textView.setText("Hello World"); } }); } }).start(); ``` 3.使用AsyncTask:AsyncTask是Android的一个异步任务类,可以用于在后台线程执行耗时的操作,并在UI线程更新UI。可以在AsyncTask类实现doInBackground()方法来执行后台操作,在onPostExecute()方法更新UI。示例代码如下: ```java public class MyTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... voids) { // 在后台线程执行耗时操作 return "Hello World"; } @Override protected void onPostExecute(String s) { // 在UI线程更新UI textView.setText(s); } } // 在主线程执行异步任务 new MyTask().execute(); ``` 无论使用哪种方法,都需要注意在后台线程更新UI时,需要确保更新UI的操作不会阻塞UI线程,以避免ANR错误。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值