AsyncTask 也会引起线程不安全(抛出异常:CalledFromWrongThreadException)?

Android 中最常见的引起线程不安全的操作就是在非主线程中操作线程的UI,在 AsyncTask  出现之前,一般采用Handler 机制异步操作UI。


做过Java的朋友都知道,java的异步线程源自于开源的Concurrent框架,AsyncTask  也正是移植自Concurrent框架。


关于 AsyncTask  和 Handler 的比较和各自用法,参考附录文档即可,在此不作累述,下面描述一种比较特别的场景:在程序启动后的第一个欢迎界面读取配置信息。


几个程序的声明:

异步任务执行类:

public class ExecuteAsyncTask extends AsyncTask<BaseTask, Integer, Object> 

        private TaskListener mListener;

基类:

public class BasicActivity  extends Activity implements TaskListener

protected void executeTask(BaseTask baseTask, TaskListener taskListener)
{
ExecuteAsyncTask mAsynctask = new ExecuteAsyncTask(baseTask, taskListener);
mAsynctask.execute((BaseTask) null);
}

        @Override
public void onTaskStart(Object object)
{}

        @Override
public void onTaskFinish(Object object)
{}

欢迎界面:

public class WelcomeActivity  extends BaseActivity
public void onTaskFinish(Object object)
{}

主界面:

public class MainActivity extends BaseActivity
public void onTaskFinish(Object object)
{}


如果我们直接用 Handler 发送一条延时执行的消息,来发起一个联网读取配置信息的任务(AsyncTask );信息获取成功后再通知软件,进入程序主界面。这时候会造成线程堵塞,也就是同步操作,如果网络异常,则会出现黑屏的情况。所以Handler需要与线程配合使用,提供两种写法:

第一种写法:

	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.welcome_layout);
		HandlerThread thread = 
			new HandlerThread(WelcomeActivity.class.getSimpleName());
		thread.start();
		
		mHandler = new MyHandler(thread.getLooper());
		mHandler.sendEmptyMessage(MESSAGE_WHAT_CONFIG)

第二种写法:

	class AsyncConfigThread extends Thread
	{
		public void run()
		{
			mHandler.sendEmptyMessageDelayed(MESSAGE_WHAT_CONFIG, 500);
		}
	}
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.welcome_layout);

		new AsyncConfigThread().start();

第二种写法比较常见,略过。


注意看,第一种写法中新创建了一个HandlerThread,然后将它的Looper传入到Handler中,相当于重新申请了一个消息队列;运行后发现线程安全问题:


WelcomeActivity(Thread id = 1) and BasicActivity(Thread id = 1)

MainActivity(Thread id = 10) and BasicActivity(Thread id = 1)


10-25 23:38:21.794 E/BaseActivity(24991): onCreate############################thread id = 1
10-25 23:38:25.494 E/WelcomeActivity(24991): executeTask############################thread id = 1
10-25 23:38:22.004 E/BaseActivity(24991): executeTask############################thread id = 10//新的消息队列线程号

10-25 23:38:27.384 I/ActivityManager( 1563): Starting: Intent { cmp=com.tfb.test/com.tfb.activity.MainActivity } from pid 24991

10-25 23:38:27.404 E/BaseActivity(24991): onCreate############################thread id = 1
10-25 23:38:27.564 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/BaseActivity(24991): executeTask############################thread id = 1
10-25 23:38:27.574 E/MainActivity(24991): ############################thread id = 1//此时线程号还是1,正确

10-25 23:38:29.704 E/MainActivity(24991): ############################thread id = 10//此时线程号发生了变化。。。
10-25 23:38:29.704 E/BaseActivity(24991): executeTask############################thread id = 1//BaseActivity的线程号一直保持1

10-25 23:48:35.754 E/AndroidRuntime(28680): FATAL EXCEPTION: WelcomeActivity
10-25 23:48:35.754 E/AndroidRuntime(28680): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.checkThread(ViewRoot.java:2932)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.invalidateChild(ViewRoot.java:642)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:668)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.invalidate(View.java:5279)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.widget.TextView.setPadding(TextView.java:1595)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.setBackgroundDrawable(View.java:7571)
10-25 23:48:35.754 E/AndroidRuntime(28680): at android.view.View.setBackgroundResource(View.java:7535)
10-25 23:48:35.754 E/AndroidRuntime(28680): at com.tfb.activity.MainActivity.onTaskFinished(MainActivity.java:445)


注意看异常信息,MainActivity.java抛出与WelcomeActivity的线程安全异常,在第二个程序(都是基于BasicActivity的异步消息onTaskFinished()中抛出),很有意思;更有意思的是,同样的代码,在Android 4.0以上系统不存在这样的线程安全问题,只在Android 2.3.x上才会存在线程安全问题。:D


我们在使用这些应用技术之前,还是要熟悉下Looper的消息机制:


Android通过Looper、Handler来实现消息循环机制。Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。


Android系统中的视图组件并不是线程安全的,如果要更新视图,必须在主线程中更新,不可以在子线程中执行更新的操作。


参考文档:

浅析Android中的消息机制 http://blog.csdn.net/liuhe688/article/details/6407225
详解Android中AsyncTask的使用 http://blog.csdn.net/liuhe688/article/details/6532519
AsyncTask和Handler对比 http://www.cnblogs.com/devinzhang/archive/2012/02/13/2350070.html



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值