`

[转载]Android UI 的更新及其线程模型

阅读更多

让我们通过一个交通状况查询Activity来讨论下Android 的UI 界面更新问题:
当用户输入区域名称,然后单击按钮进行查询后,程序会调用相应接口获得指定区域的交通状况摘要。当网络出现异常或者服务繁忙的时候都会使访问网络的动作很耗时,这时,Android会提示一个程序无法响应的异常,该对话框会询问用户是继续等待还是强行退出程序,这样就大大的降低用户体验。所以我们需要参试以别的方式来实现:

2.1 创建子线程更新UI
    显然如果你的程序需要执行耗时的操作的话,如果像上例一样由主线程来负责执行该操作是错误的。所以我们需要在onClick方法中创建一个新的子线程来负责调用相应借口来获得交通信息数据:
    public void onClick(View v) {
       //创建一个子线程执行从网络上获取交通信息的操作
       new Thread() {
           @Override
           public void run() {
              //获得用户输入的区域名称
              String zone = editText.getText().toString();
              //调用Google 交通API查询指定区域的交通情况
              String traffic = getTrafficByZone(zone);
              //把交通息显示在title上
              setTitle(traffic);
           }
       }.start();
    }

但是你会发现Android会提示程序由于异常而终止。为什么会出错呢?在LogCat中打印的日志信息就会发现这样的错误日志:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
错误信息不难看出Android禁止其他子线程来更新由UI线程创建的UI组件。本例中显示交通信息的title实际是就是一个由UI thread所创建的TextView,所以参试在一个子线程中去更改TextView的时候就出错了。这显示违背了单线程模型的原则:Android UI操作并不是线程安全的, 并且这些操作必须在UI线程中执行。啥意思,就是说如果由多个线程都对UI组件进行操作,无法保证其正确行为。

什么是线程安全?
  如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
  或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
  线程安全问题都是由全局变量及静态变量引起的。
  若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

2.2 Message Queue
在单线程模型下,为了解决类似的问题,Android设计了一个Message Queue(消息队列),线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:
l  Message Queue
Message Queue用来存放通过Handler发布的消息。消息队列通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列。Android在第一启动程序时会默认会为UI thread创建一个关联的消息队列,用来管理程序的一些上层组件,activities,broadcast receivers 等等。你可以在自己的子线程中创建Handler与UI thread通讯。

2  Handler
通过Handler你可以发布或者处理一个消息或者是一个Runnable的实例。没个Handler都会与唯一的一个线程以及该线程的消息队列管理。当你创建一个新的Handler时候,默认情况下,它将关联到创建它的这个线程和该线程的消息队列。也就是说,如果你通过Handler发布消息的话,消息将只会发送到与它关联的这个消息队列,因而也只能处理该消息队列中的消息。
主要的方法有:
a)   public final boolean sendMessage(Message msg)
把消息放入该Handler所关联的消息队列,放置在所有当前时间前未被处理的消息后。
b)   public void handleMessage(Message msg)
关联该消息队列的线程将通过调用Handler的handleMessage方法来接收和处理消息,通常需要子类化Handler来实现handleMessage。

3  Looper
Looper扮演着一个Handler和消息队列之间通讯桥梁的角色。程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler,Handler接受到消息后调用handleMessage进行处理。
a)   可以通过Looper类的静态方法Looper.myLooper得到当前线程的Looper实例,如果当前线程未关联一个Looper实例,该方法将返回空。
b)   可以通过静态方法Looper. getMainLooper方法得到主线程的Looper实例
线程,消息队列,Handler,Looper之间的关系可以通过一个图来展示:

现在将把交通信息的案例通过消息队列来重新实现:

private EditText editText;
    private Handler messageHandler;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        editText = (EditText) findViewById(R.id.weather_city_edit);
        Button button = (Button) findViewById(R.id.goQuery);
        button.setOnClickListener(this);
        //得到当前线程的Looper实例,由于当前线程是UI线程也可以通过Looper.getMainLooper()得到
        Looper looper = Looper.myLooper();
        //此处甚至可以不需要设置Looper,因为 Handler默认就使用当前线程的Looper
        messageHandler = new MessageHandler(looper);
    }

    @Override
    public void onClick(View v) {
        //创建一个子线程去做耗时的网络操作
        new Thread() {
            @Override
            public void run() {
                //活动用户输入的区域名称
                String zone = editText.getText().toString();
                //调用Google 交通API查询指定城市的交通情况
                String trafficInfo = getTrafficInfoByZone(zone);
                //创建一个Message对象,并把得到的交通信息赋值给Message对象
                Message message = Message.obtain();
                message.obj = trafficInfo;
                //通过Handler发送携带有交通情况的消息
                messageHandler.sendMessage(message);
            }
        }.start();
    }

    //子类化一个Handler
    class MessageHandler extends Handler {
        public MessageHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            //处理收到的消息,把交通信息显示在title上
            setTitle((String) msg.obj);
        }
    }

现在程序已经可以成功运行,因为Handler的handleMessage方法是由关联有该消息队列的UI 线程调用的,从而回避了线程安全问题。

 

 

原文转载: Android UI 的更新及其线程模型

分享到:
评论

相关推荐

    Android中子线程和UI线程通信详解

    2.首先在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。 3.Handler: (1).概念: Handler是沟通Activity 与Thread/runnable的桥梁。而Handler是...

    Android 单线程模型详解及实例

    Android 单线程模型详解及实例 我们今天将会在这篇文章中为大家详细介绍有关Android单线程模型的相关内容。希望初学者们可以通过本文介绍的内容对这一概念有一个充分的认识,并从中对这一系统有一个深刻的认识。 当...

    Android-UI刷新

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 Android程序中可以使用的界面刷新...

    Android 实现界面刷新的几种方法

     Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 Android程序中可以使用的界面...

    android 线程模型

    描述android 中的线程处理机制,防止UI阻塞。

    Android编程中关于单线程模型的理解与分析

    本文讲述了Android编程中关于单线程模型的理解与分析。...IMP,Android单线程模型的核心原则就是:只能在UI线程(Main Thread)中对UI进行处理。 为了提高Performance,Android对UI处理的相关method都不是synchroni

    详解Android进程和线程

    一个应用对应一个主线程,就是通常所说的UI线程,android遵守的就是单线程模型,所以说Ui操作不是线程安全的并且这些操作必须在UI线程中执行。 本文是对官方文档的翻译,原文链接:...

    Android应用程序消息处理机制

    Android应用程序主线程是一个特殊的线程,因为它同时也是UI线程以及触摸屏、键盘等输入事件处理线程。主线程对消息循环很敏感,一旦发生阻塞,就会影响UI的流畅度,甚至发生ANR问题。这个PPT讲Android应用程序线程...

    android-kotlin-golang-example-project:准备好构建的示例项目具有适当的基本回调和多线程UI模型

    准备好构建的示例项目具有基本的回调和多线程UI模型。 要构建模型,请进入gomobile /目录,然后键入“ gomobile bind”以生成.aar库。 每次对gomobile / gomobile.go文件进行修改时,您都需要这样做。 有关...

    Android AsyncTask实现异步处理任务的方法详解

    在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。 Android 单线程模型概念详解://www.jb51.net/article/112165.htm 在单线程模型中始终要记住两条...

    android中Invalidate和postInvalidate的更新view区别

    Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。 Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在...

    Android开发案例驱动教程 配套代码

    8.4.1 Android线程应用中的问题与分析 164 8.4.2 Message和MessageQueue 169 8.4.3 Handler 169 8.4.4 Looper和HandlerThread 172 本章小结 178 第9章 Activity和Intent 179 9.1 Activity 179 9.1.1 创建...

    Android 消息队列模型详解及实例

    Android中除了UI线程(主线程),创建的工作线程默认是没有消息循环和消息队列的。如果想让该线程具有消息队列和消息循环,并具有消息处理机制,就需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用...

    Android中刷新界面的二种方法

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 Android界面刷新方法有两种,分别...

    Android中invalidate()和postInvalidate() 的区别及使用方法

    Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。  Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在...

    asynctask的用法详解

    在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。在单线程模型中始终要记住两条法则: 1. 不要阻塞UI线程 2. 确保只在UI线程中访问Android UI工具包...

    Android 常用六大框架

    (2) 在UI线程外进行http请求 (3) 文件断点上传 (4) 智能重试 (5) 默认gzip压缩 (6) 支持解析成Json格式 (7) 可将Cookies持久化到SharedPreferences 3、Afinal框架 项目地址:...

    Android异步加载解决方案

    Android应用开发过程中必须遵循单线程模型(SingleThreadModel)的原则。因为Android的UI操作并不是线程安全的,所以涉及UI的操作必须在UI线程中完成。但是并非所有的操作都能在主线程中进行,Google工程师在设计上...

    Android ThinkAndroid开发框架.zip

    ioc模块:android中的ioc模块,完全注解方式就可以进行UI绑定、res中的资源的读取、以及对象的初始化。 数据库模块:android中的orm框架,使用了线程池对sqlite进行操作。 http模块:通过httpclient进行封装http...

Global site tag (gtag.js) - Google Analytics