杨子刚的博客


Android线程

2013-11-20

在Android中,UI线程是程序内处理UI事件的主线程,应该避免在UI线程中进行耗时比较长的操作,比如通过网络接口存取数据。 如果在UI线程内执行耗时操作,则程序会被阻塞且界面无法响应用户操作。 对于耗时较长的操作,比如从网络上下载数据,可以新开一个线程,在这个线程里面下载数据。

使用Thread

我们先介绍使用Thread来实现多线程。 对于最简单的Thread使用,主要涉及到两个函数

构造函数需要传递一个实现了Runnable接口的对象,Runnable接口非常简单,只有一个void run()函数,当启动线程的时候,线程就会执行这个run()函数。

HelloThreadThread项目中,我们在run函数中,只是简单的创建了一个局部变量i,并且创建了一个无限循环体,每隔3秒钟将i加一,并输出log,在这个无限循环体中如果发现用户想终止该线程,则break跳出这个无限循环体,run()函数执行完毕,从而终止这个线程。

HelloThread.java:

28-50行定义了一个实现了Runnable接口的对象,该对象只有一个函数run()。

47行会检测是否需要终止当前线程,如果需要中止,则通过50行的break来跳出无限循环。

68-69行创建了一个新线程、将其启动。

17 @Override
18  protected void onCreate(Bundle savedInstanceState) {
19      super.onCreate(savedInstanceState);
20      setContentView(R.layout.activity_hello_thread);
21      
22      Button startThreadButton = (Button)findViewById(R.id.startThread);
23      startThreadButton.setOnClickListener(startThread);
24      
25      Button stopThreadButton = (Button)findViewById(R.id.stopThread);
26      stopThreadButton.setOnClickListener(stopThread);
27      
28      runnable = new Runnable(){
29 
30          @Override
31          public void run() {
32              int i = 0;
33              while(true) {
34                  i++;
35                  Log.i("HelloThread", "i is " + i);
36                  try {
37                      Thread.sleep(3000);
38                  } catch (InterruptedException e) {
39                      e.printStackTrace();
40                  }
41                  if (shouldCancel) {
42                      Log.i("HelloThread", "thread is canceled");
43                      thread = null;
44                      break;
45                  }
46              }
47              
48          }
49          
50      };
51  }
52 
53  @Override
54  public boolean onCreateOptionsMenu(Menu menu) {
55      // Inflate the menu; this adds items to the action bar if it is present.
56      getMenuInflater().inflate(R.menu.hello_thread, menu);
57      return true;
58  }
59 
60  private OnClickListener startThread = new OnClickListener() {
61 
62      @Override
63      public synchronized void onClick(View arg0) {
64          if (thread != null) {
65              return;
66          }
67          shouldCancel = false;
68          thread = new Thread(runnable);
69          thread.start();
70      }
71      
72  };
73  
74  private OnClickListener stopThread = new OnClickListener() {
75 
76      @Override
77      public synchronized void onClick(View v) {
78          shouldCancel = true;
79      }
80      
81  };

在线程中刷新UI

场景:程序需要从网络下载一个比较大的文件,在下载的过程中把下载进度显示在界面上,这样子用户就可以实时查看当前下载的状态。

这个要求貌似简单,但是具体实施起来要比较小心。也许你会觉得对于上一个例子,我们只需在33-46行这个while循环中,根据下载的具体进度,将UI的某个控件(比如说TextView)的显示文字设置为``当前已经下载了xx%''。非常遗憾的是,这样做是行不通的,因为UI线程跟下载线程是两个不同的线程,对于该做什么、什么时候做什么,都是由UI线程自己控制的,我们不能在其他线程中直接指手画脚、粗暴干涉,如果我们在非UI线程中,调用UI控件的函数,直接后果就是使程序挂掉。

虽然不能直接在非UI线程中对UI进行操作,但是Android提供了一个办法,使得我们可以把想要做的事情发给UI线程,由UI线程择机处理这些事情。 Android给我们提供了一个类Handler,我们可以把想要做的事情放在一个Runnable对象中(在run函数中写下要做的事情),然后调用Handler的public final boolean post (Runnable r)函数,Handler就会将这个Runnable发送至某个线程。 请注意:是某个线程,那具体是哪个线程呢?答案是创建Handler的线程!也就是说Handler someHandler = new Handler()这行代码在哪个线程中执行的,那这个Handler就属于这个线程,post函数所提交的Runnable最后都交由这个线程来处理。 具体到我们这个例子,我们需要在UI线程中创建一个Handler,创建一个Runnable,在Runnable的run函数中,对界面的控件进行设置。见项目HelloThreadThreadUI

下面是HelloThread.java文件:

35行我们创建了一个Handler;

37-42行我们创建了一个Runnable,在其中我们设置TextView显示的文字;

51行,需要更新UI时,我们调用Handler的post方法,参数为37-42行创建的Runnable;

读者可以试试注释掉第51行,改用第50行代码,看看会发生什么情况。

13 public class HelloThread extends Activity {
14 
15  private Runnable runnable;
16  private Thread thread;
17  private boolean shouldCancel;
18  private Handler handler;
19  private Runnable updateUIRunnable;
20  private int i;
21 
22  @Override
23  protected void onCreate(Bundle savedInstanceState) {
24      super.onCreate(savedInstanceState);
25      setContentView(R.layout.activity_hello_thread);
26 
27      Button startThreadButton = (Button) findViewById(R.id.startThread);
28      startThreadButton.setOnClickListener(startThread);
29 
30      Button stopThreadButton = (Button) findViewById(R.id.stopThread);
31      stopThreadButton.setOnClickListener(stopThread);
32 
33      final TextView valueOfI = (TextView) findViewById(R.id.valueOfI);
34 
35      handler = new Handler();
36 
37      updateUIRunnable = new Runnable() {
38          @Override
39          public void run() {
40              valueOfI.setText("value of i is " + Integer.toString(i));
41          }
42      };
43 
44      runnable = new Runnable() {
45          @Override
46          public void run() {
47              while (true) {
48                  i++;
49                  Log.i("HelloThread", "i is " + i);
50                  // valueOfI.setText("value of i is " + Integer.toString(i));
51                  handler.post(updateUIRunnable);
52                  try {
53                      Thread.sleep(3000);
54                  } catch (InterruptedException e) {
55                      e.printStackTrace();
56                  }
57                  if (shouldCancel) {
58                      Log.i("HelloThread", "thread is canceled");
59                      thread = null;
60                      break;
61                  }
62              }
63          }
64      };
65  }
66 
67  @Override
68  public boolean onCreateOptionsMenu(Menu menu) {
69      // Inflate the menu; this adds items to the action bar if it is present.
70      getMenuInflater().inflate(R.menu.hello_thread, menu);
71      return true;
72  }
73 
74  private OnClickListener startThread = new OnClickListener() {
75 
76      @Override
77      public synchronized void onClick(View arg0) {
78          if (thread != null) {
79              return;
80          }
81          shouldCancel = false;
82          thread = new Thread(runnable);
83          thread.start();
84      }
85 
86  };
87 
88  private OnClickListener stopThread = new OnClickListener() {
89 
90      @Override
91      public synchronized void onClick(View v) {
92          shouldCancel = true;
93      }
94 
95  };
96 }

使用AsyncTask

AsyncTask也同样可以实现多线程, 并且AsyncTask内建了对UI操作的机制。

在使用AsyncTask时,需要创建一个继承自AsyncTask的类,形如:

private class MyAsyncTask extends AsyncTask<参数, 进度, 执行结果> {

        @Override
        protected 执行结果 doInBackground(参数... arg0) {
            return 执行结果;
        }

        protected void onProgressUpdate(进度... progress) {
        }

        protected void onPostExecute(执行结果 result) {
        }
    };

正如代码展示的,创建继承自AsyncTask的类的时候,需要指定三个泛型(分别是参数、进度、执行结果对应的类型)。

这三个函数不会由用户自己调用,当需要启动AsyncTask,使其需要在新线程中执行函数donInBackground内的代码时,用户需要调用public final AsyncTask<Params, Progress, Result> execute (Params... params)函数(其中参数Params... var的意思是:这个函数接受零个或多个Params类型的变量作为参数,而在在这个函数体内,var为一个包含了这若干个变量的数组),系统再以execute的参数为参数调用函数doInBackground。

当用户需要在doInBackground函数内刷新UI时,用户可以调用函数protected final void publishProgress (Progress... values),之后系统会在UI线程中调用onProgressUpdate函数。

当doInBackground执行完成后,系统会在UI线程中调用onPostExecute函数,如果AsyncTask被cancel掉,则系统不会调用这个函数。

参考项目HelloThreadAsyncTask,HelloThread.java:

59-95行定义了一个AsyncTask。

70行使用publishProgress()来刷新界面。

66-68行为当i大于10时,正常的结束AsyncTask,这时候界面的TextView就会显示async task finished-1;如果在i小于10的时候,按下了Stop Thread按钮,则onPostExecute不会被执行,TextView显示value of i is

13 public class HelloThread extends Activity {
14 
15  private boolean runningAsyncTask;
16 
17  @Override
18  protected void onCreate(Bundle savedInstanceState) {
19      super.onCreate(savedInstanceState);
20      setContentView(R.layout.activity_hello_thread);
21      Button startThreadButton = (Button) findViewById(R.id.startThread);
22      startThreadButton.setOnClickListener(startThread);
23 
24      Button stopThreadButton = (Button) findViewById(R.id.stopThread);
25      stopThreadButton.setOnClickListener(stopThread);
26  }
27 
28  @Override
29  public boolean onCreateOptionsMenu(Menu menu) {
30      // Inflate the menu; this adds items to the action bar if it is present.
31      getMenuInflater().inflate(R.menu.hello_thread, menu);
32      return true;
33  }
34 
35  private OnClickListener startThread = new OnClickListener() {
36 
37      @Override
38      public synchronized void onClick(View arg0) {
39          if (runningAsyncTask) {
40              return;
41          }
42          runningAsyncTask = true;
43          asyncTask.execute((Object) null);
44      }
45 
46  };
47 
48  private OnClickListener stopThread = new OnClickListener() {
49 
50      @Override
51      public synchronized void onClick(View v) {
52          asyncTask.cancel(false);
53      }
54 
55  };
56 
57  private MyAsyncTask asyncTask = new MyAsyncTask();
58 
59  private class MyAsyncTask extends AsyncTask<Object, Integer, Long> {
60 
61      @Override
62      protected Long doInBackground(Object... arg0) {
63          int i = 0;
64          while (true) {
65              i++;
66              if (i > 10) {
67                  break;
68              }
69              Log.i("HelloThread", "i is " + i);
70              publishProgress(i);
71              try {
72                  Thread.sleep(3000);
73              } catch (InterruptedException e) {
74                  e.printStackTrace();
75              }
76              if (isCancelled()) {
77                  Log.i("HelloThread", "thread is canceled");
78                  break;
79              }
80          }
81          return (long) -1;
82      }
83 
84      protected void onProgressUpdate(Integer... progress) {
85          TextView valueOfI = (TextView) findViewById(R.id.valueOfI);
86          if (progress.length > 0) {
87              valueOfI.setText("value of i is " + progress[0]);
88          }
89      }
90 
91      protected void onPostExecute(Long result) {
92          TextView valueOfI = (TextView) findViewById(R.id.valueOfI);
93          valueOfI.setText("async task finished" + result);
94      }
95  };
96 }