翔如菲菲

其实天很蓝,阴云总会散;其实海不宽,此岸连彼岸.

导航

【译】Android – Multithreading in a UI environment

为什么我们在Android程序中需要多线程?
比如说当用户按下一个Button,你想要做一个非常长时间的操作。如果你不用开启一个额外的线程,那么它看起来会如下代码所示:

((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(

new OnClickListener() {

@Override

public void onClick(View v) {

(new Thread(new Runnable() {

@Override

public void run() {

int result = doLongOperation();

updateUI(result);

}

})).start();

}

上述示例的结果是:程序崩溃(crash)
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 ...

显而易见的,Android OS 并不允许额外的线程来代替主线程修改UI元素.

但是,这是为什么呢?

Android UI toolkit, 像其他许多的UI环境一样,它不是线程安全的(hread-safe)

解决方案

  • 一个消息队列. 每一个消息是一个将被处理的job(这个job我也不知如何翻译更恰当,故这里用原词)
  • 能添加消息的线程
  • 仅仅一个单个线程从消息队列一个一个的获取消息.

同样的解决方案在JAVA swing实现中: (Event dispatching thread and wingUtilities.invokeLater() )

Handler
Handler 是一个新线程(thread)和消息队列essage queue)的中介者。

Option 1 – 运行新线程和使用handler来发送消息改变UI元素

final Handler myHandler = new Handler(){

@Override

public void handleMessage(Message msg) {

updateUI((String)msg.obj);

}

};

(new Thread(new Runnable() {

@Override

public void run() {

Message msg = myHandler.obtainMessage();

msg.obj = doLongOperation();

myHandler.sendMessage(msg);

}

})).start();

* 这里要谨记:更新UI应该是一个短操作(不应该超过5秒,不然会像未使用Handler一样,UI会报ANR异常), 在更新操作处理期间,UI会被冻结阻塞甚至程序会崩溃。

其他的可能性:
handler.obtainMessage()的重载函数
handler.sendMessageAtFrontOfQueue()
handler.sendMessageAtTime()
handler.sendMessageDelayed()
handler.sendEmptyMessage()

Option 2 –运行新的线程并使用handler来post一个runnable来更新UI.

final Handler myHandler = new Handler();

(new Thread(new Runnable() {

@Override

public void run() {

final String res = doLongOperation();

myHandler.post(new Runnable() {

@Override

public void run() {

updateUI(res);

}

});

}

})).start();

}

android multithreading
循环(Looper)
如果我们想深入的介绍android mechanism,那么我们必须要知道什么是 Looper.

我们已经谈论了消息队列及 UI线程(主线程)提取消息,从消息队列运行并执行它们.
我们也说了每一个我们创建的handler有一个相关联的消息队列.
我们还未谈论的是UI线程(主线程)有一个引用/关联(reference )对象Looper.
Looper对象让线程访问消息队列.
默认的只有UI(主)线程能运行Looper.

比如说你想要构建一个新的线程并你也想使用消息队列的功能在你构建的线程:

(new Thread(new Runnable() {

@Override

public void run() {

innerHandler = new Handler();


Message message = innerHandler.obtainMessage();

innerHandler.dispatchMessage(message);

}

})).start();
上述代码我们创建了一个新的线程,线程用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
为新线程创建一个Looper我们需要使用2格函数: Looper.prepare() and Looper.loop().

(new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

innerHandler = new Handler();

Message message = innerHandler.obtainMessage();

innerHandler.dispatchMessage(message);

Looper.loop();

}

})).start();
如果我们用上述代码,我们不能忘记用quit() 函数,以便于退出loop.

@Override

protected void onDestroy() {

innerHandler.getLooper().quit();

super.onDestroy();

}
AsyncTask

上边,我已经像你解释了在新线程中如何用一个handler和UI线程通讯。
如果你阅读这些,并且自己思考:是否有一个更容易的方式像上述的操作那样和UI线程通讯?你知道,Android团队已经为我们构建了一个AsyncTask 类 you know what?! There is.

可以简洁的异步通讯和UI线程.

就像在JAVA Swing中,你继承一个线程类SwingWorker 一样,在Android中,你可以继承类AsyncTask.
这里没有一个类似Runnable 接口的实现,我担心.

class MyAsyncTask extends AsyncTask<Integer, String, Long> {

@Override

protected Long doInBackground(Integer... params) {

long start = System.currentTimeMillis();

for (Integer integer : params) {

publishProgress("start processing "+integer);

doLongOperation();

publishProgress("done processing "+integer);

}

return start - System.currentTimeMillis();

}

@Override

protected void onProgressUpdate(String... values) {

updateUI(values[0]);

}

@Override

protected void onPostExecute(Long time) {

updateUI("Done with all the operations, it took:" +

time + " millisecondes");

}

@Override

protected void onPreExecute() {

updateUI("Starting process");

}

public void doLongOperation() {


try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}
下面就是如何Start上述线程:

MyAsyncTask aTask = new MyAsyncTask();

aTask.execute(1, 2, 3, 4, 5);
AsyncTask 定义了3格泛型参数:
AsyncTask<{输入类型}, {更新单元}, {结果类型}>
你不需要全部使用它们,简单的,你可以用‘Void’来替换不用的参数.

注意:AsyncTask 有4格操作它们是按顺序执行的:
1. onPreExecute() – 在执行前被调用(这里我们可以做初始化工作)。
2. onPostExecute() - 执行完成后,调用(这里处理我们的结果)。
3. doInBackground() - 在这里我们书写我们的逻辑代码。
4. onProgressUpdate() – 指示给用户处理进程. 每次 publishProgress() 被调用的时候会触发.

* 注意:: doInBackground() 在后台线程调用的, 由于onPreExecute(), onPostExecute() and onProgressUpdate() 的目的是更新UI,故它们是在UI线程被调用的 .

Android 开发者站点也提及了4条规则关于AsyncTask:

  • The task instance must be created on the UI thread.
  • execute(Params…) must be invoked on the UI thread.
  • Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…) manually.
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.)

Timer + TimerTask
另一个选择是用 Timer.
在未来,无论是单次执行还是多次轮询,Timer 是一种派发线程的舒适方式.

Runnable threadTask = new Runnable() {


@Override

public void run() {


while(true){

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}


doSomething();

}

}

};


(new Thread(threadTask)).start();

do this:

TimerTask timerTask = new TimerTask() {     

@Override

public void run() {

doSomething();          

}

}; 

Timer timer = new Timer();

timer.schedule(timerTask, 2000,2000);
timer是相当的高雅.

记住:如果你需要同UI线程交互(通讯),你仍然需要使用Handler

源码下载:

zip Download demo project 1
zip Download demo project 2

原文地址:http://www.aviyehuda.com/2010/12/20/android-multithreading-in-a-ui-environment/

posted on 2012-11-06 16:15  翔如飞飞  阅读(469)  评论(0编辑  收藏  举报