说实话,之前在java开发的时候,很少涉及多线程的东西。由于开发的项目体量比较小,技术也比较差,所以更多的考虑的是功能,很少对并发做优化
如今借着学习Android的机会,希望可以对多线程的知识有一个更好更全面地认识。哎,感觉自己技术基础还是太差,好好加油吧
首先,安卓使用的时单线程模型:
当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。
在开发Android 应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
如果在非UI线程中直接操作UI线程,会抛出android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch itsviews,这与普通的java程序不同。
由于UI线程负责事件的监听和绘图,因此,必须保证UI线程能够随时响应用户的需求,UI线程里的操作应该向中断事件那样短小,费时的操作(如网络连接)需要另开线程,否则,如果UI线程超过5s没有响应用户请求,会弹出对话框提醒用户终止应用程序。
如果在新开的线程中需要对UI进行设定,就可能违反单线程模型,因此android采用一种复杂的Message Queue机制保证线程间通信。
以上内容摘自http://www.cnblogs.com/nio-nio/archive/2012/07/23/2604900.html,是我在网上看到的相对比较容易理解的说明。读完以上这几段话之后,我对安卓单线程模式的理解:如果你要对UI进行操作那这些操作应该都放到主线程中;如果你要处理一些业务逻辑或者其它的比较费时的操作,那就把这些操作放到子线程中。那因为大多数的UI更新都是基于业务逻辑的判断,如何让处理逻辑的子线程和处理UI的主线程通信呢,这就需要用到Handler
Handler是干什么的:
Handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程),它有两个作用: (1): 安排消息或Runnable 在某个主线程中某个地方执行, (2)安排一个动作在不同的线程中执行。
关于Handler的使用,我们首先来看一个简单例子:
我们假设有这么一个需求:我们需要在程序中点击一个开始按钮,然后我们的程序会在后台启动一个定时任务(每隔一秒打印一个语句)。另外,我们还需要一个取消按钮,点击它终止后台的定时任务.
我们先在界面中加入两个按钮,一个开始,一个取消。接下来是activity
package com.example.hander1; import java.util.Date; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { Button startButton = null; Button stopButton = null; Handler handler = new Handler(); protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startButton = (Button) findViewById(R.id.startButton); stopButton = (Button) findViewById(R.id.stopButton); //为button绑定onclicklistener startButton.setOnClickListener(new ButtonOnclikListener()); stopButton.setOnClickListener(new ButtonOnclikListener()); } Runnable going = new Runnable() { @Override public void run() { //线程每次执行,打印日志 Log.i("run", "going "+new Date().getSeconds()); //延时一秒钟后,再次将线程加入队列中 handler.postDelayed(going, 1000); } }; class ButtonOnclikListener implements OnClickListener{ @Override public void onClick(View v) { switch(v.getId()){ case R.id.startButton: begin(); break; case R.id.stopButton: stop(); break; default: break; } } } //该方法将going添加到message queue中 void begin(){ handler.post(going); } //从message queue中移除going void stop(){ handler.removeCallbacks(going); } }
下面是运行结果,程序启动后是这样
当我点击启动按钮后,后台每秒中打印一条语句
当我点击取消按钮后,后台不再打印语句
对于这个程序的运行过程我是这样理解的:
1、当我点击启动按钮时:调用handler.post(going);
handler的post方法说明是这样的:Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.
也就是说,当我调用了handler.post(going);这个方法之后,handler会将我实现的Runnable对象加入到message queue中,系统会根据message queue来执行。后面一句很重要,runnable会在当前handler所属的那个线程中执行,也就是说我的going会在程序的主线程中执行,而不是新开启的其它线程。
· 2、在going的run方法中,我调用了handler.postDelayed(going, 1000);
postDelayed这个方法和前面的post方法类似,只不过第二个参数的作用是让我的going延迟一秒钟执行。
这样其实就形成了一个死循环(我没有规定跳出的条件),程序在我点击启动之后,每隔一秒钟会在message queue中加入执行going的内容。
3、当我点击取消按钮时:调用handler.removeCallbacks(going);
虽在我在上面的写法中形成了一个死循环,但是我可以通过handler.removeCallbacks(going)这个方法,将message queue这个队列中的going去掉,这样就不再执行going了。而且根据这个方法源码中的注释:Remove any pending posts of Runnable r that are in the message queue. 应该是会把message queue中所有未执行going全部去掉。
这个程序虽然很简单,但是我在一次看到的时候,仍然陷入了误区。由于没有仔细阅读API,我刚开始以为handler.post()这个方法会新开启一个线程。后来看到API的时候才明白原来这个程序,只有一个主线程,只不过是通过handler将实现的Runnable 对象加入到message queue中执行的。