Handler和Toast
来源:互联网 发布:美国东密西根大学知乎 编辑:程序博客网 时间:2024/05/18 02:26
http://blog.csdn.net/androidbluetooth/article/details/6384641Handler:更新UI的方法
总是感觉 android 中 UI 更新很让人纠结!自己小结一下,算是抛砖引玉。读这篇文章之前,假设你已经明白线程、Handler 的使用。
在文章的最后,附录一张草图,主要用于说明 Handler、Message、MessageQueue、Looper 之间的关系。
1. 在 onCreate() 方法中开启线程更新 UI
- public class MasterActivity extends Activity {
- TextView tv = null;
- Button btn = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv = (TextView)findViewById(R.id.text);
- btn = (Button)findViewById(R.id.btn);
- /*onCreate中开启新线程,更新UI。没有报错或者异常信息!*/
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv.setText("update UI is success!");
- btn.setText("update UI is success!");
- }});
- thread.start();
- }
随便折腾,不会报错或者异常!以为开启的线程和 UI 线程(主线程)是同一个线程,但是很不幸,他们的线程id根本是风牛马不相及!
不知道为什么在这里开启子线程更新UI就没有问题!真的想不明白????
2. 在 activity 如 onResume、onStart、反正是以 on 开头的回调方法
- @Override
- protected void onRestart() {
- super.onRestart();
- /*onRestart中开启新线程,更新UI*/
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv.setText("update UI is success!");
- btn.setText("update UI is success!");
- }});
- thread.start();
- }
不好意思,按下返回按钮在启动程序,或者按 Home 键再启动程序,就这么折腾几下,就会包异常!信息如下:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
意思是:只有主线程才可以更新 UI。
解决办法:加上 postInvalidate() 方法。
- @Override
- protected void onRestart() {
- super.onRestart();
- /*onRestart中开启新线程,更新UI*/
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv.postInvalidate();
- btn.postInvalidate();
- tv.setText("update UI is success!");
- btn.setText("update UI is success!");
- }});
- thread.start();
- }
postInvalidate() 方法,源码:
- public void postInvalidate() {
- postInvalidateDelayed(0);
- }
- public void postInvalidateDelayed(long delayMilliseconds) {
- // We try only with the AttachInfo because there's no point in invalidating
- // if we are not attached to our window
- if (mAttachInfo != null) {
- Message msg = Message.obtain();
- msg.what = AttachInfo.INVALIDATE_MSG;
- msg.obj = this;
- mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
- }
- }
其实,是调用了 Handler 的处理消息的机制!该方法可以在子线程中直接用来更新UI。还有一个方法 invalidate (),稍候再说!
3. 在 Button 的事件中开启线程,更新 UI
- public class MasterActivity extends Activity {
- TextView tv = null;
- Button btn = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv = (TextView)findViewById(R.id.text);
- btn = (Button)findViewById(R.id.btn);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv.setText("update UI is success!");
- btn.setText("update UI is success!");
- }});
- thread.start();
- }
- });
- }
Sorry,报错!即使你加上 postInvalidate() 方法,也会报这个错误。
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
- public class MasterActivity extends Activity {
- TextView tv = null;
- Button btn = null;
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if(msg.what == 1) {
- tv.setText("update UI is success!");
- btn.setText("update UI is success!");
- }
- super.handleMessage(msg);
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- tv = (TextView)findViewById(R.id.text);
- btn = (Button)findViewById(R.id.btn);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
- Message msg = mHandler.obtainMessage();
- msg.what = 1;
- msg.sendToTarget();
- }});
- thread.start();
- }
- });
- }
- public void invalidate ()
- Since: API Level 1
- Invalidate the whole view. If the view is visible, onDraw(Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
- public void invalidate() {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
- }
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
- mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
- final ViewParent p = mParent;
- final AttachInfo ai = mAttachInfo;
- if (p != null && ai != null) {
- final Rect r = ai.mTmpInvalRect;
- r.set(0, 0, mRight - mLeft, mBottom - mTop);
- // Don't call invalidate -- we don't want to internally scroll
- // our own bounds
- p.invalidateChild(this, r);
- }
- }
- }
Android 在 onDraw 事件处理绘图,而 invalidate() 函数可以再一次触发 onDraw 事件,然后再一次进行绘图动作。
- public class MasterActivity extends Activity {
- static int times = 1;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView( new View(null){
- Paint vPaint = new Paint(); //绘制样式物件
- private int i = 0; //弧形角度
- @Override
- protected void onDraw (Canvas canvas) {
- super.onDraw(canvas);
- System.out.println("this run " + (times++) +" times!");
- // 设定绘图样式
- vPaint.setColor( 0xff00ffff ); //画笔颜色
- vPaint.setAntiAlias( true ); //反锯齿
- vPaint.setStyle( Paint.Style.STROKE );
- // 绘制一个弧形
- canvas.drawArc(new RectF(60, 120, 260, 320), 0, i, true, vPaint );
- // 弧形角度
- if( (i+=10) > 360 )
- i = 0;
- // 重绘, 再一次执行onDraw 程序
- invalidate();
- }
- });
- }
- }
经过测试,发现 times 一直在++,说明 onDraw 被多次调用,并且一致在画图!
SDK 的 API 有时候让人很郁闷,无语.....关于 invalidate 的使用,还待探索。革命尚未成功,同志仍需努力!
博客更新,推荐文章:
View编程(2): invalidate()再探
View编程(3): invalidate()源码分析
附录: Handler、Message、MessageQueue、Looper 之间的关系
这里说明:
1. Looper 使用无限循环取出消息,是有 android os 控制的。
2. android 线程是非安全的,即不要在子线程中更新 UI。
3. Looper 取出来的消息,handler 可以通过 what、obj 等量来区别分别获取属于自己的消息,所以推荐使用这些量。
http://blog.csdn.net/androidbluetooth/article/details/6840283
Handler: Service中使用Toast
关于 android 的线程模型,建议阅读 http://blog.csdn.net/androidbluetooth/article/details/6547166,这只是一个建议,你看不看这篇博客都不会影响阅读本篇博客。
Handler 的使用在 android App 开发中用的颇多,它的作用也很大,使用 Handler 一般也会使用到多线程,相信大家对 Handler 不会陌生,在这篇博客中,重点说一下 android 组件之一 Service 与 TaskTimer 结合 Handler 使用,共享之!
*****************************************阅读这篇博客,需要你知道的知识:
<1> 知道在 Activity 中如何启动、停止 Service 以及 Service 的生命周期。
<2> 使用过 TimerTask 的Api,不过这个不难,如果之前没有接触过现在拿出几分钟学习一下吧!
<3> Handler 基本用法,推荐博客:http://blog.csdn.net/androidbluetooth/article/details/6384641
<4> Looper 基本用法,推荐下载:http://download.csdn.net/detail/AndroidBluetooth/3650576 好好看看,肯定对你有用!
好嘞,开始说这篇博客的内容。
界面很简单,就是两个Button,启动之后,效果如下:
Activity 代码:
- package mark.zhang;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- public class ServiceToastActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Log.d("mark", "activity: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + "," + "当前线程名称:"
- + Thread.currentThread().getId());
- // 启动服务
- findViewById(R.id.button_startservice).setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(ServiceToastActivity.this,
- MyService.class);
- startService(intent);
- }
- });
- // 停止服务
- findViewById(R.id.button_stopservice).setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(ServiceToastActivity.this,
- MyService.class);
- stopService(intent);
- }
- });
- }
- }
上述代码就是给两个 Button 设置监听器,启动和停止服务。
然后,在 Service 中 开启一个线程,并在该线程中 Toast 一下!代码如下:
- package mark.zhang;
- import java.util.Timer;
- import java.util.TimerTask;
- import android.app.Service;
- import android.content.Intent;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Looper;
- import android.util.Log;
- import android.widget.Toast;
- public class MyService extends Service {
- private Handler handler = null;
- private Timer timer;
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- new Thread(new Runnable() {
- public void run() {
- Log.d("mark", "Service in Thread: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + "," + "当前线程名称:"
- + Thread.currentThread().getId());
- Toast.makeText(MyService.this, "Service中子线程启动!", Toast.LENGTH_LONG).show();
- }
- }).start();
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.d("mark", "Service: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + "," + "当前线程名称:"
- + Thread.currentThread().getId());
- Toast.makeText(this, "启动服务成功!", Toast.LENGTH_LONG).show();
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- }
- }
点击界面的“启动服务”,打印信息:
- D/mark ( 310): activity:
- D/mark ( 310): 当前线程名称:main,当前线程名称:1
- D/mark ( 310): Service:
- D/mark ( 310): 当前线程名称:main,当前线程名称:1
- D/mark ( 310): Service in Thread:
- D/mark ( 310): 当前线程名称:Thread-8,当前线程名称:8
如果你复制我的代码实际运行一下,你会发现子线程中的 Toast 根本没有起作用,并且程序会崩溃,显示异常如下:
- 10-02 05:25:32.818: ERROR/AndroidRuntime(325): Uncaught handler: thread Thread-8 exiting due to uncaught exception
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): at android.os.Handler.<init>(Handler.java:121)
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): at android.widget.Toast.<init>(Toast.java:68)
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): at android.widget.Toast.makeText(Toast.java:231)
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): at mark.zhang.MyService$1.run(MyService.java:34)
- 10-02 05:25:32.828: ERROR/AndroidRuntime(325): at java.lang.Thread.run(Thread.java:1096)
这下应该明白,在子线程直接 Toast 是错误的!根据提示信息,我们需要调用 Looper.prepare(), 根据 Looper 的 Api 说明,我们还应该调用 Looper.loop(),那麽我们修改一下代码,将 new Thread中的代码修改如下:
- new Thread(new Runnable() {
- public void run() {
- Log.d("mark", "Service in Thread: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + "," + "当前线程名称:"
- + Thread.currentThread().getId());
- Looper.prepare();
- Toast.makeText(MyService.this, "Service中子线程启动!", Toast.LENGTH_LONG).show();
- Looper.loop();
- }
- }).start();
该部分完整示例代码下载:http://download.csdn.net/detail/AndroidBluetooth/3653420
接着看看在Service中如何使用 TimerTask 以及 Toast。Activity 的代码不变,修改 Service 代码:
- package mark.zhang;
- import java.util.Timer;
- import java.util.TimerTask;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.util.Log;
- import android.widget.Toast;
- public class MyService extends Service {
- private Timer timer;
- private TimerTask task = new TimerTask() {
- @Override
- public void run() {
- Log.d("mark", "task: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + "," + "当前线程名称:"
- + Thread.currentThread().getId());
- Toast.makeText(getApplicationContext(), "呵呵,您好!",
- Toast.LENGTH_SHORT).show();
- }
- };
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- // 当前Task中线程的名称为myservice
- timer = new Timer("myservice");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // 100ms之后,每隔5000ms启动定时器
- timer.scheduleAtFixedRate(task, 100, 5000);
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- timer.cancel();
- }
- }
启动服务之后,启动 TimerTask,在 TimerTask 中每隔5秒中 Toast 一下,在终止服务的时候取消 Timer。
打印信息如下:
- D/mark ( 441): activity:
- D/mark ( 441): 当前线程名称:main,当前线程名称:1
- D/mark ( 441): task:
- D/mark ( 441): 当前线程名称:myservice,当前线程名称:8
- D/mark ( 441): task:
- D/mark ( 441): 当前线程名称:myservice,当前线程名称:8
- D/mark ( 441): task:
- D/mark ( 441): 当前线程名称:myservice,当前线程名称:8
- D/mark ( 441): task:
- D/mark ( 441): 当前线程名称:myservice,当前线程名称:8
- D/mark ( 441): task:
- D/mark ( 441): 当前线程名称:myservice,当前线程名称:8
可以看出,TimerTask开启一个线程(名称为myservice,线程id是8),按照原来的想法一样,每隔五秒 TimerTask 的run() 方法会执行一次,直到 “停止服务”,但是 Toast 并没有起作用。看来我们需要修改代码。
当然,可以像上面那样使用 Looper 的两个静态方法prepare()、loop(),可以保证Toast 完美运行,但是肯定还有其它办法,仔细看来,呵呵!
修改 Service 代码,这次主要使用 Handler:
- package mark.zhang;
- import java.util.Timer;
- import java.util.TimerTask;
- import android.app.Service;
- import android.content.Intent;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Looper;
- import android.util.Log;
- import android.widget.Toast;
- public class MyService extends Service {
- private Handler handler;
- private Timer timer;
- private TimerTask task = new TimerTask() {
- @Override
- public void run() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getApplicationContext(), "呵呵,您好!",
- Toast.LENGTH_SHORT).show();
- Log.d("mark", "service in Handler run: " + "\n" + "当前线程名称:"
- + Thread.currentThread().getName() + ","
- + "当前线程名称:" + Thread.currentThread().getId());
- }
- });
- }
- };
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- @Override
- public void onCreate() {
- super.onCreate();
- // 为当前线程获得looper
- handler = new Handler(Looper.getMainLooper());
- // 当前Task中线程的名称为myservice
- timer = new Timer("myservice");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // 100ms之后,每隔5000ms启动定时器
- timer.scheduleAtFixedRate(task, 100, 5000);
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- timer.cancel();
- }
- }
- D/mark ( 495): activity:
- D/mark ( 495): 当前线程名称:main,当前线程名称:1
- D/mark ( 495): service in Handler run:
- D/mark ( 495): 当前线程名称:main,当前线程名称:1
- D/mark ( 495): service in Handler run:
- D/mark ( 495): 当前线程名称:main,当前线程名称:1
- D/mark ( 495): service in Handler run:
- D/mark ( 495): 当前线程名称:main,当前线程名称:1
该部分完整代码示例,下载地址:http://download.csdn.net/detail/AndroidBluetooth/3653469
这里还需要提醒大家一句:在子线程中我们不可以直接 new Handler(),但是在 TimerTask 的 run() 方法中直接 new Handler() 是没有问题的。TimerTask 的确开启一个子线程,但是为什么在这里可以直接创建 Handler 对象呢?
如果,你有兴趣可以继续:
学习 android 的 Looper 源码以及 TimerTask 设计理念。
多说一句:在 Service 中不可以显示对话框,如果想通过 Service 来显示对话框需要使用 Handler 通知 Activity 来显示对话框。
在 Activity 子线程中显示对话框,可以这样做:
- Looper.prepare();
- showMyDialog();
- Looper.loop();
好嘞,这篇博客只是借助 Service 来说明在子线程中如何使用TimerTask、Toast,在Activity 中类似,说到这里吧!
- Handler和Toast
- Toast和Looper。Handler消息循环机制。
- Toast和Looper。Handler消息循环机制。
- Toast和Looper。Handler消息循环机制。
- Toast和Looper。Handler消息循环机制。
- Toast和Looper。Handler消息循环机制。
- Toast和Looper,Handler消息循环机制
- Toast和Looper,Handler消息循环机制
- Toast和Looper。Handler消息循环机制
- Toast和Looper、Handler消息循环机制
- Toast和Looper。Handler消息循环机制。
- 【Android】Toast和Looper。Handler消息循环机制
- Toast和Looper、Handler消息循环机…
- 其他线程handler和ui组件与toast深入探讨
- Handler 中使用 Toast
- Handler: Service中使用Toast
- Handler: Service中使用Toast
- Handler: Service中使用Toast
- Visual C++ 6.0 快捷键和使用技巧(更新中)
- 自己动手编写CSDN博客备份工具-blogspider之源码分析(1)
- HashMap和Hashtable的区别
- hibernate参数说明
- 教你怎么做metro map app
- Handler和Toast
- 自己动手编写CSDN博客备份工具-blogspider之源码分析(2)
- SQL 连接 JOIN 例解。(左连接,右连接,全连接,内连接,交叉连接,自连接)
- 自己动手编写CSDN博客备份工具-blogspider之源码分析(3)
- Unix用户新建修改以及环境变量设置总结说明
- CButton类的学习
- linux设备模型之mmc,sd子系统<二>
- 23种设计模式
- 一分钟学会Django的表单