我们为什么需要多线程在Android应用程序吗?
让我们说你想做一个非常长的操作,当用户按下一个按钮。
如果你不使用另一个线程,看上去就像这样:
((Button)findViewById(R.id.Button01)).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
int result = doLongOperation();
updateUI(result);
}
});
将会发生什么?
可能大家都知道了,UI冻结。这是一个非常糟糕的UI体验。该计划甚至可能崩溃。
这个问题在使用线程在一个UI环境
那么将会发生什么如果我们用一个线程对一个长时间运行的操作。
让我们看一个简单的例子:
((Button)findViewById(R.id.Button01)).setOnClickListener( |
02 | new OnClickListener() { |
05 | public void onClick(View v) { |
07 | ( new Thread( new Runnable() { |
11 | int result = doLongOperation(); |
在本例中,结果是,应用程序崩溃。
12-07 16:24:29.089: ERROR/AndroidRuntime(315): FATAL EXCEPTION: Thread-8
12-07 16:24:29.089: ERROR/AndroidRuntime(315): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
12-07 16:24:29.089: ERROR/AndroidRuntime(315): at ...
显然安卓系统不让主线程的线程改变UI元素。
为什么了?
Android UI toolkit,像许多其他用户界面环境,不是线程安全的。
解决方法:
1:一个队列的消息。每个消息处理的工作。
2:线程可以添加信息。
3:只有一个线程将消息从队列中一个接一个。
Handler
这个handler处理线程和消息队列的中间人。
方案一:
运行新线程和使用处理程序发送消息ui更改
01 | final Handler myHandler = new Handler(){ |
03 | public void handleMessage(Message msg) { |
04 | updateUI((String)msg.obj); |
09 | ( new Thread( new Runnable() { |
13 | Message msg = myHandler.obtainMessage(); |
15 | msg.obj = doLongOperation(); |
17 | myHandler.sendMessage(msg); |
注意:更新UI应该仍然是一个短的操作,因为UI冻结在更新过程。
其他的可能性:
handler.obtainMessage with parameters
handler.sendMessageAtFrontOfQueue()
handler.sendMessageAtTime()
handler.sendMessageDelayed()
handler.sendEmptyMessage()
方案二:
运行新线程和使用处理程序来发布一个可运行更新GUI。
01 | final Handler myHandler = new Handler(); |
03 | ( new Thread( new Runnable() { |
07 | final String res = doLongOperation(); |
08 | myHandler.post( new Runnable() { |
可看Android Runnable 运行在那个线程。
Looper
如果我们想要较深入地介绍了android机制我们必须了解什么是Looper。
我们已经谈过了,主要的线程消息队列提取消息,并执行他们。
我们知道,每个处理程序创建引用此队列。
我们还没说但就是主要的线程对象的引用命名Looper。
Looper给线程执行消息队列。
只有主要的线程执行默认的Looper。创建一个新的线程,用消息队列的功能在这个线程上。
01 | ( new Thread( new Runnable() { |
06 | innerHandler = new Handler(); |
08 | Message message = innerHandler.obtainMessage(); |
09 | innerHandler.dispatchMessage(message); |
这里我们创建一个新的线程,使用handler处理程序将消息在消息队列。
运行的结果:
12-10 20:41:51.807: ERROR/AndroidRuntime(254): Uncaught handler: thread Thread-8 exiting due to uncaught exception
12-10 20:41:51.817: ERROR/AndroidRuntime(254): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
12-10 20:41:51.817: ERROR/AndroidRuntime(254): at android.os.Handler.(Handler.java:121)
12-10 20:41:51.817: ERROR/AndroidRuntime(254): at ...
新创建的线程没有Looper和队列连接到它。只有Ui线程有Looper。
但是我们也可以创建Looper给这个新的线程。
为了做到这一点,我们需要使用2功能: Looper.prepare() andLooper.loop().
01 | ( new Thread( new Runnable() { |
07 | innerHandler = new Handler(); |
09 | Message message = innerHandler.obtainMessage(); |
10 | innerHandler.dispatchMessage(message); |
如果我们使用了这样操作,不要忘记使用也quit()函数所以Looper不会永远循环。
2 | protected void onDestroy() { |
3 | innerHandler.getLooper().quit(); |
AsyncTask
Android已经创建了一个类称为AsyncTask,总之一个线程能够处理UI。
01 | class MyAsyncTask extends AsyncTask<Integer, String, Long> { |
04 | protected Long doInBackground(Integer... params) { |
06 | long start = System.currentTimeMillis(); |
07 | for (Integer integer : params) { |
08 | publishProgress( "start processing " +integer); |
10 | publishProgress( "done processing " +integer); |
13 | return start - System.currentTimeMillis(); |
19 | protected void onProgressUpdate(String... values) { |
24 | protected void onPostExecute(Long time) { |
25 | updateUI( "Done with all the operations, it took:" + |
26 | time + " millisecondes" ); |
30 | protected void onPreExecute() { |
31 | updateUI( "Starting process" ); |
35 | public void doLongOperation() { |
39 | } catch (InterruptedException e) { |
像这样执行线程:
new MyAsyncTask().excute();
AsyncTask提供了方法来处理
1. onPreExecute()在执行前被调用。
2,doInBackground()主要的操作
3,onPostExecute()后才调用执行。
Android开发者网站也提到这四个规则对于AsyncTask:
1:必须创建任务实例在UI线程上。
2:execute(Params…)必须在UI线程上调用。
3:不要手动唤醒onPreExecute(),onPostExecute(result),InBackground(Params…),。
4:这个任务可以只执行一次(如果第二个执行是企图将抛出一个异常)。
Timer + TimerTask
另一个选择是使用一个计时器。
定时器是一个舒适的方式分派线程在未来,无论是一次或更多。
不要这样做:
01 | Runnable threadTask = new Runnable() { |
09 | } catch (InterruptedException e) { |
18 | ( new Thread(threadTask)).start(); |
而是要这样做:
1 | TimerTask timerTask = new TimerTask() { |
8 | Timer timer = new Timer(); |
9 | timer.schedule(timerTask, 2000 , 2000 ); |
参考资料:http://www.aviyehuda.com/blog/2010/12/20/android-multithreading-in-a-ui-environment/