线程概览

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

应用主线程

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

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

线程Handler

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

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

一个简单的Thread例子

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

具体的布局文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <TextView
  8. android:id="@+id/text_view"
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. android:text="Hello World, MyActivity"
  12. />
  13.  
  14. <Button android:id="@+id/thread_button"
  15. android:layout_width="match_parent"
  16. android:layout_height="wrap_content"
  17. android:text="Press me"/>
  18.  
  19. </LinearLayout>

看起来是这样的:

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

代码如下:

  1. package com.example.myapp;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.TextView;
  8.  
  9. public class ThreadExampleActivity extends Activity implements View.OnClickListener{
  10. /**
  11. * Called when the activity is first created.
  12. */
  13. @Override
  14. public void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.activity_thread_example);
  17.  
  18. Button threadButton = (Button)findViewById(R.id.thread_button);
  19. threadButton.setOnClickListener(this);
  20. }
  21.  
  22. @Override
  23. public void onClick(View v) {
  24. long endTime = System.currentTimeMillis() + 20 * 1000;
  25.  
  26. // waiting...
  27. while (System.currentTimeMillis() < endTime) {
  28. synchronized (this) {
  29. try{
  30. wait(endTime - System.currentTimeMillis());
  31. }
  32. catch (Exception e) {
  33.  
  34. }
  35. }
  36. }
  37.  
  38. // update `TextView`, after 20 seconds
  39. TextView textView = (TextView)findViewById(R.id.text_view);
  40. textView.setText("Button Pressed");
  41. }
  42. }

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

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

创建一个新的线程

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

修改后的代码如下:

  1. @Override
  2. public void onClick(View v) {
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. long endTime = System.currentTimeMillis() + 20 * 1000;
  7.  
  8. // waiting...
  9. while (System.currentTimeMillis() < endTime) {
  10. synchronized (this) {
  11. try{
  12. wait(endTime - System.currentTimeMillis());
  13. }
  14. catch (Exception e) {
  15.  
  16. }
  17. }
  18. }
  19. }
  20. };
  21.  
  22. Thread thread = new Thread(runnable);
  23. thread.start();
  24. }

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

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

实现一个Thread Handler

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

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

修改后的代码如下:

  1. package com.example.myapp;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.os.Handler;
  6. import android.os.Message;
  7. import android.view.View;
  8. import android.widget.Button;
  9. import android.widget.TextView;
  10.  
  11. public class ThreadExampleActivity extends Activity implements View.OnClickListener {
  12.  
  13. Handler handler = new Handler() {
  14. @Override
  15. public void handleMessage(Message message) {
  16. TextView textView = (TextView)findViewById(R.id.text_view);
  17. textView.setText("Button Pressed!");
  18. }
  19. };
  20. //...
  21.  
  22. }

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

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

修改后的代码如下:

  1. @Override
  2. public void onClick(View v) {
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. long endTime = System.currentTimeMillis() + 20 * 1000;
  7.  
  8. // waiting...
  9. while (System.currentTimeMillis() < endTime) {
  10. synchronized (this) {
  11. try{
  12. wait(endTime - System.currentTimeMillis());
  13. }
  14. catch (Exception e) {
  15.  
  16. }
  17. }
  18. }
  19.  
  20. // waiting is over
  21. handler.sendEmptyMessage(0);
  22. }
  23. };
  24.  
  25. Thread thread = new Thread(runnable);
  26. thread.start();
  27. }

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

给Handler传递消息

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

  1. @Override
  2. public void onClick(View v) {
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. Message message = handler.obtainMessage();
  7. Bundle bundle = new Bundle();
  8. SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss MM/dd/yyy", Locale.US);
  9. String dateString = dateFormat.format(new Date());
  10. bundle.putString("thread_date", dateString); // key is `thread_date`

  11. message.setData(bundle);
  12. handler.sendMessage(message);
  13. }
  14. };
  15.  
  16. Thread thread = new Thread(runnable);
  17. thread.start();
  18. }

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

  1. Handler handler = new Handler() {
  2. @Override
  3. public void handleMessage(Message message) {
  4. Bundle bundle = message.getData();
  5. String dateString = bundle.getString("thread_date");
  6.  
  7. TextView textView = (TextView)findViewById(R.id.text_view);
  8. textView.setText(dateString);
  9. }
  10. };

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

总结

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

Android线程和线程Handler基础一览的更多相关文章

  1. Android 线程通讯类Handler

    handler是线程通讯工具类.用于传递消息.它有两个队列: 1.消息队列 2.线程队列 消息队列使用sendMessage和HandleMessage的组合来发送和处理消息. 线程队列类似一段代码, ...

  2. (原)Android在子线程用handler发送的消息,主线程是怎么loop到的?

    来自知乎:https://www.zhihu.com/question/48130951?sort=created   大家都知道Android的Looper是ThreadLocal方式实现,每个线程 ...

  3. Android——线程通讯类Handler(转)

    原文地址:http://uule.iteye.com/blog/1705951 handler是线程通讯工具类.用于传递消息.它有两个队列:1.消息队列2.线程队列 消息队列使用sendMessage ...

  4. android线程 Handler Message Queue AsyncTask线程模型 线程交互 + 修改Button样式 示例 最终easy整合版

     首先原谅我把文章的标题写的这么长.其实我还嫌弃它短了因为 写不下去了所以我就不写了.因为我实在不知道该怎么定义这篇文章的标题或许应该叫 "乱谈"比较合适. 这样可能还体现了 ...

  5. Android中的线程池概述

    线程池 Android里面,耗时的网络操作,都会开子线程,在程序里面直接开过多的线程会消耗过多的资源,在众多的开源框架中也总能看到线程池的踪影,所以线程池是必须要会把握的一个知识点; 线程运行机制 开 ...

  6. android中的线程池学习笔记

    阅读书籍: Android开发艺术探索 Android开发进阶从小工到专家 对线程池原理的简单理解: 创建多个线程并且进行管理,提交的任务会被线程池指派给其中的线程进行执行,通过线程池的统一调度和管理 ...

  7. android开发之线程

              线程(android) 在java中我们学习了线程,线程,是进程的一个单位,在程序要运行时,会开启线程,运行程序,我们要创建线程就需要我们去继承接口Thread或者实现Runabl ...

  8. android中工作线程安全

    当应用程序启动,创建了一个叫“main”的线程,用于管理UI相关,又叫UI线程.其他线程叫工作线程(Work Thread). Single Thread Model 一个组件的创建并不会新建一个线程 ...

  9. Android ActivityThread(主线程或UI线程)简介

    1. ActivityThread功能 它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client ...

随机推荐

  1. 事件委托,js中的一种优化方法

    在前端编程中,我们常会遇到一种叫做事件委托的方法. 那么, 什么是事件委托呢? 简单来说,事件就是指onclick,onmouseover,onmouseout等大部分事件,为什么说是大部分后面会提到 ...

  2. 正则表达式(Swift)

    课题 使用正则表达式匹配字符串 使用正则表达式 "\d{3}-(\d{4})-\d{2}" 匹配字符串 "123-4567-89" 返回匹配结果:'" ...

  3. tflearn 实现DNN 全连接

    https://github.com/tflearn/tflearn/blob/master/examples/others/recommender_wide_and_deep.py import n ...

  4. zg 作业一

    作业一: 将xxjjT01增加窗体及按钮(增.修.删) 表单(form1.html) 设定按钮作业,授权 1.新增:XXJJT01+CREATE 2.修改:XXJJT01+UPDATE 3.删除:XX ...

  5. 登录时显示403 Access Denied

    用户名及密码设置如下: 在tomcat安装目录\conf\tomcat-users.xml中的<tomcat-users>标签内设置: <role rolename="ma ...

  6. tf.FIFOQueue()

    Tensorflow–tf.FIFOQueue详解描述tf.FIFOQueue根据先进先出(FIFO)的原则创建一个队列.队列是Tensorflow的一种数据结构,每个队列的元素都是包含一个或多个张量 ...

  7. LibreOJ 6282 数列分块入门 6(在线插入在线查询)

    题解:还是分块,将每个块存入vector,然后在插入的时候就是sqrt(n)级的重构,如果块太大了,暴力将这个块拆开. 代码如下: #include<cmath> #include< ...

  8. A计划(BFS)

    A计划 http://acm.hdu.edu.cn/showproblem.php?pid=2102 Time Limit: 3000/1000 MS (Java/Others)    Memory ...

  9. [leetcode]438. Find All Anagrams in a String找出所有变位词

    Given a string s and a non-empty string p, find all the start indices of p's anagrams in s. Strings ...

  10. ArrayList与LinkedList的基本添加删除方法 模拟栈 队列

    ArrayList  LinkedList ArrayList的add是在末尾添加 linkedlist也是 offer加在末尾 poll获取并移除此列表的头(第一个元素) peek 获取第一个但不移 ...