一只羊的blog


Android线程

在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行创建了一个新线程、将其启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_hello_thread);
		
		Button startThreadButton = (Button)findViewById(R.id.startThread);
		startThreadButton.setOnClickListener(startThread);
		
		Button stopThreadButton = (Button)findViewById(R.id.stopThread);
		stopThreadButton.setOnClickListener(stopThread);
		
		runnable = new Runnable(){

			@Override
			public void run() {
				int i = 0;
				while(true) {
					i++;
					Log.i("HelloThread", "i is " + i);
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (shouldCancel) {
						Log.i("HelloThread", "thread is canceled");
						thread = null;
						break;
					}
				}
				
			}
			
		};
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.hello_thread, menu);
		return true;
	}

	private OnClickListener startThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View arg0) {
			if (thread != null) {
				return;
			}
			shouldCancel = false;
			thread = new Thread(runnable);
			thread.start();
		}
		
	};
	
	private OnClickListener stopThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View v) {
			shouldCancel = true;
		}
		
	};

在线程中刷新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行代码,看看会发生什么情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class HelloThread extends Activity {

	private Runnable runnable;
	private Thread thread;
	private boolean shouldCancel;
	private Handler handler;
	private Runnable updateUIRunnable;
	private int i;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_hello_thread);

		Button startThreadButton = (Button) findViewById(R.id.startThread);
		startThreadButton.setOnClickListener(startThread);

		Button stopThreadButton = (Button) findViewById(R.id.stopThread);
		stopThreadButton.setOnClickListener(stopThread);

		final TextView valueOfI = (TextView) findViewById(R.id.valueOfI);

		handler = new Handler();

		updateUIRunnable = new Runnable() {
			@Override
			public void run() {
				valueOfI.setText("value of i is " + Integer.toString(i));
			}
		};

		runnable = new Runnable() {
			@Override
			public void run() {
				while (true) {
					i++;
					Log.i("HelloThread", "i is " + i);
					// valueOfI.setText("value of i is " + Integer.toString(i));
					handler.post(updateUIRunnable);
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (shouldCancel) {
						Log.i("HelloThread", "thread is canceled");
						thread = null;
						break;
					}
				}
			}
		};
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.hello_thread, menu);
		return true;
	}

	private OnClickListener startThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View arg0) {
			if (thread != null) {
				return;
			}
			shouldCancel = false;
			thread = new Thread(runnable);
			thread.start();
		}

	};

	private OnClickListener stopThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View v) {
			shouldCancel = true;
		}

	};
}

使用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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class HelloThread extends Activity {

	private boolean runningAsyncTask;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_hello_thread);
		Button startThreadButton = (Button) findViewById(R.id.startThread);
		startThreadButton.setOnClickListener(startThread);

		Button stopThreadButton = (Button) findViewById(R.id.stopThread);
		stopThreadButton.setOnClickListener(stopThread);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.hello_thread, menu);
		return true;
	}

	private OnClickListener startThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View arg0) {
			if (runningAsyncTask) {
				return;
			}
			runningAsyncTask = true;
			asyncTask.execute((Object) null);
		}

	};

	private OnClickListener stopThread = new OnClickListener() {

		@Override
		public synchronized void onClick(View v) {
			asyncTask.cancel(false);
		}

	};

	private MyAsyncTask asyncTask = new MyAsyncTask();

	private class MyAsyncTask extends AsyncTask<Object, Integer, Long> {

		@Override
		protected Long doInBackground(Object... arg0) {
			int i = 0;
			while (true) {
				i++;
				if (i > 10) {
					break;
				}
				Log.i("HelloThread", "i is " + i);
				publishProgress(i);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (isCancelled()) {
					Log.i("HelloThread", "thread is canceled");
					break;
				}
			}
			return (long) -1;
		}

		protected void onProgressUpdate(Integer... progress) {
			TextView valueOfI = (TextView) findViewById(R.id.valueOfI);
			if (progress.length > 0) {
				valueOfI.setText("value of i is " + progress[0]);
			}
		}

		protected void onPostExecute(Long result) {
			TextView valueOfI = (TextView) findViewById(R.id.valueOfI);
			valueOfI.setText("async task finished" + result);
		}
	};
}