• Android线程和线程Handler基础一览


    线程概览

    线程是任何多任务系统的基石。可以被认为是一个主进程的多个子进程。这样做的目的就是了增加应用的性能。

    应用主线程

    当一个Android应用被打开的时候,系统会默认开辟一个线程。这个线程就被叫做是主线程。主线程的主要任务就是处理用户输入,即事件处理和view上的用户交互。任何应用里的其他组件,默认的,都是在主线程中运行的。

    一个应用的任何组件,如果在主线程上执行一个耗时的任务的话,都会使整个应用等待这个任务的完成。如果耗时过长的话就会触发系统的“Application is unresponsive”警告。显然,这个是任何应用都不愿意出现的状况。在这种情况下,只能开辟一个单独的线程来执行这个耗时的任务,这样才不会干扰主线程上的其他任务。

    线程Handler

    所以,应用开发中最关键的一条就是永远不要在主线程上执行耗时过长的任务。另外一个同样重要的规则是另外开辟的单独的线程任何情况下、绝对不可以直接更新用户界面。任何对用户界面的更新都要在主线程中进行。之所以这样的原因是Android的UI不是线程安全的。在多线程环境下调用非线程安全的代码会导致断断续续的问题以及不可预料的应用行为。

    要在子线程中更新用户界面就只能通过Handler来实现。

    一个简单的Thread例子

    这里会提供几个简单的例子来展示线程和Handler是如何使用的。第一步,展示一下耗时任务没有放在另外开辟的线程中,而放在主线程中出现的问题。首先创建一个Android项目叫做“ThreadExample”,包含一个单独的空白的activity:ThreadExampleActivity,layout叫做activity_thread_example。

    具体的布局文件如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
            >
        <TextView
                android:id="@+id/text_view"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="Hello World, MyActivity"
                />
    
        <Button android:id="@+id/thread_button"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Press me"/>
    
    </LinearLayout>

    看起来是这样的:

    保存。接下来,双击ThreadExampleActivity.java进入编辑模式。在这个activity文件中实现button的click方法。这个方法会在用户点击按钮之后被调用。这里主要是展示耗时任务的问题,所以会在主线程中发起一个20秒的延迟,之后更新TextView对象的文字。

    代码如下:

    package com.example.myapp;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class ThreadExampleActivity extends Activity implements View.OnClickListener{
        /**
         * Called when the activity is first created.
         */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_thread_example);
    
            Button threadButton = (Button)findViewById(R.id.thread_button);
            threadButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            long endTime = System.currentTimeMillis() + 20 * 1000;
    
            // waiting...
            while (System.currentTimeMillis() < endTime) {
                synchronized (this) {
                    try{
                        wait(endTime - System.currentTimeMillis());
                    }
                    catch (Exception e) {
    
                    }
                }
            }
    
            // update `TextView`, after 20 seconds
            TextView textView = (TextView)findViewById(R.id.text_view);
            textView.setText("Button Pressed");
        }
    }

    以上代价在运行之后,点击一下按钮,这个时候整个应用就在20秒的等待中。再次或者多次点击这个按钮不会立刻有反应。这时候系统就会弹出一个提醒:应用正在忙:

    因此,在按钮点击方法中,耗时的操作应该放置在另外一个单独的线程中。

    创建一个新的线程

    要创建一个新的线程,并让代码在这个线程中执行,需要把这些代码都放在Runnable接口的Run中。然后需要创建一个新的Thread对象。把Runnable接口的实例作为参数传给Thread的构造函数中。最后调用Thread实例的start方法来开辟线程并执行线程中的方法。

    修改后的代码如下:

    @Override
        public void onClick(View v) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    long endTime = System.currentTimeMillis() + 20 * 1000;
    
                    // waiting...
                    while (System.currentTimeMillis() < endTime) {
                        synchronized (this) {
                            try{
                                wait(endTime - System.currentTimeMillis());
                            }
                            catch (Exception e) {
    
                            }
                        }
                    }
                }
            };
    
            Thread thread = new Thread(runnable);
            thread.start();
        }

    当应用再次运行起来之后。点击按钮之后把造成延时的任务都放在了新的线程中运行,主线程可以及时响应用户的任何操作,包括无休止的按钮点击。事实上,每次的点击都会创建一个新的线程,这样任务就可以在多个线程中并发执行。

    两外一个需要注意的地方是,点击按钮之后更新TextView的文字的代码被去掉了。就像之前提到的,要更新界面上的内容只能在主线程中进行。要实现这个功能就需要给单独开辟的线程引入Handler实例。

    实现一个Thread Handler

    线程的Handler的实现是放在主线程中的,主要就是用来响应子线程的message并根据这个message来更新主线程的。

    Handler继承自Android的Handler类。用来表明线程的Runnable实例即将执行,或overridehandleMessage方法,这个方法接受和处理子线程发送的message。本例会用Handler来更新用户界面。

    修改后的代码如下:

    package com.example.myapp;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class ThreadExampleActivity extends Activity implements View.OnClickListener {
    
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                TextView textView = (TextView)findViewById(R.id.text_view);
                textView.setText("Button Pressed!");
            }
        };
    
        //...
    
    }

    上面的代码中声明了一个handler并实现了handleMessage回调方法。当子线程发出message的时候可以被这个方法处理。在这个实例中,只是简单地在代码中设置了TextView实例的文字。

    现在就剩下修改button点击事件中创建的线程了。我们需要在这个线程里发出一个消息告诉handler20秒的延时任务已经执行完成。

    修改后的代码如下:

     @Override
        public void onClick(View v) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    long endTime = System.currentTimeMillis() + 20 * 1000;
    
                    // waiting...
                    while (System.currentTimeMillis() < endTime) {
                        synchronized (this) {
                            try{
                                wait(endTime - System.currentTimeMillis());
                            }
                            catch (Exception e) {
    
                            }
                        }
                    }
    
                    // waiting is over
                    handler.sendEmptyMessage(0);
                }
            };
    
            Thread thread = new Thread(runnable);
            thread.start();
        }

    这段修改中唯一的修改就是增加了的就是handler调用sendEmptyMessage方法。由于handler实例不需要特别发送什么message所以这里只发送空消息。执行代码之后,点击按钮,等待20秒。TextView就会显示新的文本。

    给Handler传递消息

    之前的代码调用了handleMessage方法。但是这个方法并没有发挥出message可以发送数据给handler的优点。下面就会对现有的代码做出更多的修改来在子线程和handler实例之间传递数据。首先,在创建的子线程中会从系统获取到date和time,并转换成字符串。这些内容会保存在一个bundle实例中。然后调用handler的obtainMessage方法从message池中获取一个message实例。最后,这个保存了系统信息的bundle会被添加到message实例中并被sendMessage方法发送给handle实例。

    @Override
        public void onClick(View v) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Message message = handler.obtainMessage();
                    Bundle bundle = new Bundle();
                    SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss MM/dd/yyy", Locale.US);
                    String dateString = dateFormat.format(new Date());
                    bundle.putString("thread_date", dateString);    // key is `thread_date`
    
                    message.setData(bundle);
                    handler.sendMessage(message);
                }
            };
    
            Thread thread = new Thread(runnable);
            thread.start();
        }

    接下来更新handleMessage方法。用这个方法把接收到的时间显示在TextView实例中。

    Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                Bundle bundle = message.getData();
                String dateString = bundle.getString("thread_date");
    
                TextView textView = (TextView)findViewById(R.id.text_view);
                textView.setText(dateString);
            }
        };

    最后编译运行代码,点击按钮测试一下我们的修改是否成功。

    总结

    本教程就是提供一个对于Android应用实现多线程的概览。当一个app运行在一个进程中的时候,系统会给这个app穿件一个主线程。主线程的主要功能就是处理用户输入,所以任何执行时间过长的任务都会导致主线程无法及时响应用户后续的输入。所以,耗时的任务都应该放在另外开辟的子线程中执行。这些都是很基础的。因为Android用户界面的各种元素都是非线程安全的,所以对于界面的修改智能在主线程中进行。在主线程中可以使用Handler实例来接受子线程发出的消息来更新界面元素。

  • 相关阅读:
    平台
    重构之践
    Linux.NET
    系统分析员级下午试题II(论文)解答方法
    通用泛型存储接口的设计
    .NET平台4.0 发布网站流程及出错总结
    在IIS上发布基于Windows Azure Service Bus的WCF服务
    epoll + 多线程实现并发网络连接处理
    Linux进程地址空间之初探:一
    排序、搜索
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/4755152.html
Copyright © 2020-2023  润新知