当应用程序启动,创建了一个叫“main”的线程,用于管理UI相关,又叫UI线程。其他线程叫工作线程(Work Thread)。
Single Thread Model
- 一个组件的创建并不会新建一个线程,他们的创建都在UI线程中进行,包括他们的回调方法,如onKeyDown()。
- 当在UI线程中进行某些耗时的操作时,将会阻塞UI线程,一般阻塞超过5秒就会显示一个ANR对话框。
- UI线程是非线程安全的,所以,不能在工作线程中操作UI元素。
两个原则
- Do not block the UI thread (不要阻塞UI线程)
- Do not access the Android UI toolkit from outside the UI thread (不要在工作线程中操作UI元素)
在工作线程更新UI方法
- Activity.runOnUiThread(Runnable)
- Handler
- sendMessage(Message)
- post(Runnable)
- AsyncTask
- execute()
- doInBackground()
- onPostExecute()
例子程序
- HandlerActivity01
- 在工作线程中进行UI操作。
- HandlerActivity02
- Handler的两个重要方法:sendMessage和post。
- HandlerActivity03
- 官方推荐最佳方法。
HandlerActivity01主要代码:
- btnEnd.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- new Thread(new Runnable() {
- @Override
- public void run()
- {
- //在新建的线程(工作线程)中改变Button的文字
- btnEnd.setText("Text Changed in Sub Thread");
- }
- }).start();
- }
- });
btnEnd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { //在新建的线程(工作线程)中改变Button的文字 btnEnd.setText("Text Changed in Sub Thread"); } }).start(); } });
这是一种错误的做法,运行程序,会报错误:
- android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
HandlerActivity02主要代码:
- public class HandlerActivity02extends Activity
- {
- private int title =0;
- Button btnStart,btnEnd;
- private Handler mHandler = new Handler()
- {
- public void handleMessage(Message msg)
- {
- //更新UI
- switch (msg.what)
- {
- case 1:
- updateTitle();
- break;
- }
- };
- };
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- btnStart = (Button)findViewById(R.id.start);
- btnEnd = (Button)findViewById(R.id.end);
- //新启动一个线程,进行耗时操作
- Timer timer = new Timer();
- //每六秒执行一次MyTask的run方法
- timer.scheduleAtFixedRate(new MyTask(this),1, 6000);
- }
- private class MyTaskextends TimerTask
- {
- private Activity context;
- MyTask(Activity context)
- {
- this.context = context;
- }
- @Override
- public void run()
- {
- //耗时操作略....
- //更新UI方法 1
- Message message = new Message();
- message.what = 1;
- mHandler.sendMessage(message);
- //更新UI方法 2
- mHandler.post(updateThread);
- //更新UI方法 3
- context.runOnUiThread(updateThread);
- }
- }
- public void updateTitle()
- {
- setTitle("Welcome to Mr Wei's blog " + title);
- title++;
- }
- Runnable updateThread = new Runnable()
- {
- @Override
- public void run()
- {
- //更新UI
- btnStart.setText(String.valueOf(title));
- btnEnd.setText(String.valueOf(title));
- }
- };
- }
public class HandlerActivity02 extends Activity { private int title = 0; Button btnStart,btnEnd; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { //更新UI switch (msg.what) { case 1: updateTitle(); break; } }; }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnStart = (Button)findViewById(R.id.start); btnEnd = (Button)findViewById(R.id.end); //新启动一个线程,进行耗时操作 Timer timer = new Timer(); //每六秒执行一次MyTask的run方法 timer.scheduleAtFixedRate(new MyTask(this), 1, 6000); } private class MyTask extends TimerTask { private Activity context; MyTask(Activity context) { this.context = context; } @Override public void run() { //耗时操作略.... //更新UI方法 1 Message message = new Message(); message.what = 1; mHandler.sendMessage(message); //更新UI方法 2 mHandler.post(updateThread); //更新UI方法 3 context.runOnUiThread(updateThread); } } public void updateTitle() { setTitle("Welcome to Mr Wei's blog " + title); title++; } Runnable updateThread = new Runnable() { @Override public void run() { //更新UI btnStart.setText(String.valueOf(title)); btnEnd.setText(String.valueOf(title)); } }; }
这里有个容易出错的地方,在更新UI方法2和3中,我们传入的参数是一个Runnable对象,一般认为这就会启动一个新的线程,而且常有人在这个Runnable对象的run方法中进行耗时操作。看过这块的源码就会知道,其实,android只是调用了这个Runnable对象的run方法而已,并没有启动新的线程,而且我们不应该在run方法中进行耗时操作,因为这个run方法最终是在UI线程里面执行的。也就是说,run方法里面只应该放更新UI的代码,handleMessage方法也一样。
如果你要看这部分源代码的话,相信这个图对你会有帮助:
HandlerActivity03主要代码:
- public class HandlerActivity03extends Activity
- {
- Button btnStart;
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- btnStart = (Button)findViewById(R.id.start);
- btnStart.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //开始执行AsyncTask,并传入某些数据
- new LongTimeTask().execute("New Text");
- }
- });
- }
- private class LongTimeTaskextends AsyncTask<String, Void , String>
- {
- @Override
- protected String doInBackground(String... params)
- {
- try
- {
- //线程睡眠5秒,模拟耗时操作,这里面的内容Android系统会自动为你启动一个新的线程执行
- Thread.sleep(5000);
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- return params[0];
- }
- @Override
- protected void onPostExecute(String result)
- {
- //更新UI的操作,这里面的内容是在UI线程里面执行的
- btnStart.setText(result);
- }
- }
- }
public class HandlerActivity03 extends Activity { Button btnStart; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main); btnStart = (Button)findViewById(R.id.start); btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开始执行AsyncTask,并传入某些数据 new LongTimeTask().execute("New Text"); } }); } private class LongTimeTask extends AsyncTask<String, Void , String> { @Override protected String doInBackground(String... params) { try { //线程睡眠5秒,模拟耗时操作,这里面的内容Android系统会自动为你启动一个新的线程执行 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return params[0]; } @Override protected void onPostExecute(String result) { //更新UI的操作,这里面的内容是在UI线程里面执行的 btnStart.setText(result); } } }
这个方法确实挺好,因为它为你封装了许多操作,你只需要记住在doInBackground方法中写耗时操作的代码,在onPostExecute方法中写更新UI的方法就行了。
-
Kaiwii注:
-
因为AsyncTask的onPreExecute()和onPostExecute()方法实际上是在UI线程上运行的。而操作UI的代码逻辑必须要在UI线程上