Android线程篇

在Android中,UI主线程并非线程安全的,所有UI相关的操作均需在UI主线程中完成。在默认情况下,开发者创建的Service、Activity、Broadcast均运行在UI主线程中,但将一些耗时操作,如网络下载、大文件读写、加解密计算、数据库操作等,也放在UI线程中执行,往往会阻塞UI线程,造成ANR异常,因此,在Android应用开发中,应特别注意线程的使用。

在Android中,实现多线程的方式有多种,开发者可以通过原生Java线程或Android对Java线程的封装即AsyncTask来实现多线程的效果。

为了开发出性能更好的应用,开发者必须对Android的多线程实现由清楚的了解,了解每种实现方式的优缺点和线程安全方面的问题,这样才能最大程度地发挥出Android的潜力。

1.Java线程实现

Android线程的实现本质上仍是Java线程,只是为了方便开发者进行实现,针对特定场景做了封装。本节将着重介绍基于Thread、Runnable的java线程的实现,并介绍Android的线程优先级。

(1)基于Thread的实现

基于Thread实现新线程和在Runnable构造器中实现新线程是传统Java实现线程的两种方式,基于Thread的实现非常简单。示例如下:

static class AThread extends Thread{

ActivityManagerService mService;

boolean mRead=false;

public AThread(){

super("ActivityManager");

}

public void run(){

Looper.prepare();      //初始化事件循环之后调用loop()

android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND):

android.os.Precess.setCanSelfBackground(false);

ActivityManagerService m=mew ActivityManagerService();

synchronized(this){

mSerice=m;

notifyAll();

}

synchronized(this){

while(!mReady){

try{

wait();

catch(InterruptedException e){

}

}

}

Looper.loop();

}

}

在Thread声明周期中,存在NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED等多个状态。线程的生命周期和状态迁移过程如下图:

下面是Thread中常用方法的说明,在实现Java线程时,务必掌握这些方法的用途。

getState()        //获取线程的当前状态                             interrupt()        //中断当前线程

interrupted()    //判断当前线程是否已中断                      isAlive             //判断线程是否仍在运行

isInterrupted() //判断是否处于“中断”状态                       sleep()             //线程睡眠指定的时间

join()               //线程同步处理                                        run()                //线程暂停一段时间

start()             //开启线程                                                yield()              //暂停当前线程,同优先级的其它线程获得时间片

(2)基于Runable的实现

对于APP开发来说,由于应用层本身的特点,Runnable使用更加广泛,下面是一个Runnable实现的示例:

private Runnable mForceCheckBoxRunnable =new Runnable(){

public void run(){

if(mCheckBoxPreference!=null){

mCheckBoxPreference.setChecked(!mCheckBoxPreference.isChecked());

}

mHandle.postDelayed(this, 1000);

}

};

另外,通过Handle的removeCallbacks()方法可以移除待运行的线程,通过postAtTime方法可以在特定时间将线程放入消息队列。

(3)线程优先级

Android线程优先级是以及Linux设置的,其优先级是用数字表示的,范围是-20~19,其中-20为最高优先级,19为最低优先级。优先级的设置通常用于处理并发线程产生的阻塞,防止重要性较低的线程占用大量的CPU资源。设置优先级的方法如下:

Process.setThreadPriority(priority);

根据适用场景的不同,Android提供了以下几种常用场景的线程优先级:

THREAD_PRIORITY_AUDIO                                  //值为-16,用于音乐播放场景

THREAD_PRIORITY_BACKGROUND                   //值为10,用于普通后台程序

THREAD_PRIORITY_DEFAULT                            //值为0,默认优先级

THREAD_PRIORITY_DISPLAY                             //值为-4,用于普通显示线程

THREAD_PRIORITY_FOREGROUND                  //值为-2,用于普通前台程序

THREAD_PRIORITY_LESS_FAVORABLE           //值为1,低于默认优先级

THREAD_PRIORITY_LOWEST                             //值为19,最低优先级

THREAD_PRIORITY_MORE_FAVORABLE          //值为-1,最高默认优先级

THREAD_PRIORITY_URGENT_AUDIO                //值为-19,重要的音频线程

THREAD_PRIORITY_UNGENT_DISPLAY            //值为-8,重要的显示线程

2.Android线程封装

在Android中,为了简化开发者的工作,对Java线程进行了一定程度的封装,最主要的工作包括AsyncTask封装和接入UI线程。

(1)AsyncTask封装

AsyncTask通常用于后台线程和UI线程的交互。虽然AsyncTask本质上还是Thread加Handler的一个封装,但是由于采用了Java 1.5中的并发库FutureTask,故其在处理异步事务时表现卓越。使用AsyncTask的示例如下:

private class ReceicePushTask extends AsyncTask<Intent, Void, Void>{

protected Void doInBackground(Intent... instentd){

Intent intent-intents[0];

}

public void onReceive(Context context, Intent intent){}

}

AsyncTask中最基本的两个方法为doInBackground和onReceive,前者执行真正的后台计算,后者向UI主线程反馈计算结果。为了使用AsyncTask,开发者必须对AsyncTask进行继承。

AsyncTask的其他重要方法如下:

public final AsyncTask<Params, Progress,Result>execute(Params... params)            //执行AsyncTask

public void onPostExecute(Result result)                             //在doInBackGround方法执行后执行,该方法在UI线程中执行

protected void onPreExecute()                                            //在执行doInBackground方法前执行

protected final void publishProgress(Progress... values)    //向UI线程反馈计算进度

public final boolean cancel(boolean mayInterruptIfRunning)      //中断线程执行

public final boolean isCancelled()        //判断AsyncTask是否已经被中断

在Android3.0中,Google又引入了execute(Runnable runnable)等多个方法。AsyncTask可以支持多个输入参数,甚至可变参数,其参数在execute方法中传入。

在中断开始执行时,系统将会调用AsyncTask的onCancelled方法,开发者可以在该方法中进行一些收尾工作,但是要注意,必须显式判断线程是否中断。假设在某个目录下寻找和关键字匹配的文件和文件夹,后台计算的工作被放置在AsyncTask中,显式判断线程是否中断的工作在dosth中进行,示例如下:

SearchTask mSearchTask=new SearchTask();

mSearchTask.execute(path)       //执行线程

public void dosth(String value){

for(...){

if(mSearchTask.isCancelled()){

return;

}

}

}

下面是SearchTask的实现,其中包含了后台计算的入口、正常结束的入口、线程中断的入口等。

class SearchTask extends AsyncTask<String,Void, Void>{

public void doInBackground(String... value){

dosth(value[0]);

}

pretected void onCanceled(){

//执行中断的善后工作

}

protected void onPostExecute(Result result){     //该方法在UI线程执行

//执行正常的结束工作

}

}

(2)接入UI线程

在多线程编程中,执行后台计算的线程通常需要和UI线程进行交互,将计算的结果反馈给UI线程,呈现给用户,而传统的方法一般需要借组Handle。在Android中,充分考虑了开发者者方面的需要,从Activity、View和UI主线程3个层次提供了4种方式来接入UI线程。

1)Activity的runOnUiThread(Runnable)方法

Activity提供的接入UI线程的方法实际上是通过runOnUiThread(Runnable)方法进行的,从本质上讲,runOnUiThread方法时基于Handler的post(Runnable r)方法实现的。runOnUiThread(Runnable)的示例如下:

mActivity.runOnUiThread(new Runnable(){

public void run(){

mToast.setGravity(Gravity.BOTTOM, 0, 0);

mToast.show();

}

});

2)View的post(Runnable)方法

View的post(Runnable)方法从控件层面对多线程提供了支持,是开发更加灵活,其实现原理与基于Activity的runOnUiThread(Runnable)方法类似,均是借助Handle进行的。关于View的post(Runnable)方法的示例如下:

public void onClick(View v){

new Thread(new Runnable(){

public void run(){

finnal Bitmap bitmap=loadImageFromNetwork(http://example.com/image.png);

mImageView.post(new Runnable(){

public void run(){

mImageView.setImageBitmap(bitmap);

}

});

}

}).start

}

(3)View的postDelayed(Runnable, long)方法

View的postDelayed(Runnable, long)方法提供了延时处理的能力,示例如下:

final static int PAUSE_DELAY=100;

Runnable mRunnable=new Runnable(){

public void run(){

mView.postDelayed(mRunnable, PAUSE_DELAY);

}

};

(4)Handler方法

利用Message消息向UI主线程发送后台计算的消息,及时将计算结果反馈给实际用户,这一切均需要借助Handle进行消息处理。当UI线程接收到Message消息后,会调用其Handler进行消息处理。Handler处理消息的过程如下:

Message message=mHandle.obtainMessage(ORIENTATION_CHANGED);

mHandler.sendMessage(message);

Handler mHandler=new Handler(){

public void handleMessage(Message msg){

switch(msg.what){

case ORIENTATION_CHANGED;

break;

}

}

};

3.线程间的消息通信

相比Thread加Handler的封装,Async更为可靠,更易于维护。AsyncTask的缺点是,一旦线程开启即dobackground方法执行后无法向线程发送消息,仅能通过预先设置好的标记来控制,当然可以通过挂起线程并等待标志位的改变来进行通信。

对于某些应用,原生的Thread加Handler以及Looper可能更灵活。下图揭示了三者之间的关系。

(1)消息队列

消息队列的维护是在线程中进行的,但默认除UI主线程外,其他线程并不拥有消息队列。为了维护一个消息队列,需要在线程中调用Looper的prepare方法进行初始化。为了使线程可以接收发送过来的消息,通常需要借组Handler来接收消息。一个维护消息队列的示例如下:

private class Queryrunner extends Thread{

public Handler mHandle

public void run(){

Looper.prepare();       //loop()方法应随后调用

mHandler=new Handler(){

public void handleMessage(Message msg){//处理收到的消息}

};

Looper.loop();

};

}

通过Looper还可以判断当前线程是否为UI主线程,方法如下:

private void assertUiThread(){

if(!Looper.getMainLooper().equals(Looper.myLooper())){

throw new RuntimeException("Not on the UI thread!");

}

}

通过查看Message的实现即可发现,Message继承于Parcelable,在消息的传递过程中,实际上传递的是序列化数据。Message的实现如下:

public final class message implements Parcelable{

public int what;        //消息表示符

public int arg1;        //传递参数

public int arg2;        //传递对象

public Object obj;    //传递的对象

public Messager replyTo;

long when;

Bundle data;

Handle target;        //目标Handler

Runnable callback;

Message next;

private static final int MAX_POOL_SIZE=10;

...

}

通过分析Message的源代码实现可以发现,一个线程会自动维护一个消息池,该消息池的大小为10.在需要生成消息时,首先从消息池中获取消息,只有当消息池中的消息均被使用时,才会重新创建新消息,当消息使用结束时,消息池会回收消息。

从obtain方法的实现中可以看出,在发送消息时,通过obtain方法获得Message比创建Message的效率更高。发送消息的示例如下:

public void progress(boolean progress){

android.os.Message msg=android.os.Message.obtain();

msg.what=MSG_PROGRESS;

msg.arg1=progress ? 1: 0;

sendMessage(msg);

}

(2)消息分发

消息的分发和接收均与Handler密切相关,对于消息的分发,Android目前提供了两种消息类型,一种是post消息,一种是send消息。其中post消息会在为了某一时间加入消息队列,而send消息则会立即加入到消息队列。

1)send消息

分发一个简单的send消息的方法如下:

Messagemsg=mHandler.obtainMessage(KILL_APPLICATION_MSG);

msg.arg1=uid;

msg.arg2=0;

msg.obj=pkg;

mHandler.sendMessage(msg);

如果没有参数需要传递,那么可以发送仅携带消息表示符的空消息,方法如下:

mHandler.sendEnptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG);

2)post消息

通过post消息可以实现类似循环计算的功能,方法如下:

mTicker = new Runnable(){

public void run(){

if(mTickerStopped) return;

mCalender.setTimeInMillis(System.currentTimeMillis());

setText(DateFormat(mFormat, mCalendar));

invalidate();

long now=SystemClock.uptimerMillis();

long next=now + (1000-now%1000);

mHandler.postAtTime(mTicker, next);

}

};

mTicker.run();

(3)消息接收

消息的接收相对简单,但是如果存在重入问题,应注意数据的同步。关于数据安全的内容。在Handler中消息接收的过程如下:

public void dispatchMessage(Message msg){

if(msg.callback !=null){

handleCallback(msg);        //通过Message自身回调进行处理

}else{

if(mCallback.handleMessage(msg)){

return;

}

}

handleMessage(msg);       //handle自己处理

}

对于消息队列而言,如何监听消息的来临呢,笔者的经验是,在Looper的loop方法中执行对消息的监听。loop方法的实现如下:

public static final void loop(){

Looper me=myLooper();

MessageQueue queue=me.mQueue;

Binder.clearCallingIdentity();

finnal long ident=Binder.clearCallingIdentity();

if(msg!=null){

if(msg.target==null){

return;

}

msg.target.dispatchMessage(msg);        //调用Handler进行消息处理

msg.recycle();              //消息回收

}

}

4.线程安全处理

线程安全多是由多线程对共享资源的访问引起的,在线程安全层面,Android更多的是采用Java的实现,除了Java的join()、wait()、sleep()、notify()等安全方法和synchronized关键字外,还有Java的并发库。

除了synchronized关键字外,基于Java的多线程安全均比较复杂,对于不是十分复杂的场景,优先进行多线程处理。

(1)synchronized同步

在Android应用层,线程的并发很多是靠synchronized关键字来实现的,这样的实现非常简单。通过synchronized关键字,可以实现方法和语句块的同步。同步的本质是对特定的对象加锁,他们可以是来自调用方法的对象,也可以是开发者设计的对象。

另外,synchronized关键字并不能继承,对于父类的同步方法,在子类中必须再次显式声明才能成为同步方法。

synchronized关键字的局限在于在试图获得锁时无法设定超时和中断,每个锁只有一个条件,在某些场景下可能不够用。

另外,同步会带来较大的系统开销,甚至造成死锁,因此在进行开发时应避免无谓的同步。synchronize关键字可以实现方法同步和语句块同步。

1)方法同步

方法同步分一般方法同步和静态方法同步两种。一般方法同步的本质在于将synchronized关键字作用于对象引用(object referece),作用域仅限类的单个对象,下面是AbsSeekBar中的一个实现:

public synchronized void setMax(int max){

super.setMax(max);

}

以上实现等同于如下实现:

public void setMax(int max){

synchronized(this){

super.setMax(max);

}

}

静态方法同步的本质是将类本身作为所,其作用域是该类的所有对象。下面是BitmapManaget中利用synchronized实现单子模式的过程。

private static BitmapManager sManager=null;

public static synchronized BitmapManaget instance(){

if(sManager==null){

sManager=new BitmapManager();

}

}

2)语句块的同步

方法同步的作用域较大,但事实上需要同步的范围可能并没那么大。当需要同步的范围不是很大时,可采用语句块同步。下面是AccelerometerListener中的一个示例:

private void setOrientation(int orientation){

synchronized(this){

}

}

将对象引用本身作为锁,显然影响并发效率,更灵巧的设计是自定义锁。自定义锁可以是任何类型的对象,但通常将其类型设计为Object。AbstractPreferences中的一个示例如下:

public abstract class AbstractPreferences extends Preferences

{

protected final Object lock;

protected AbstractPreferences getChild(String name) throws

BackingStoreException{

synchronized(lock){

}

}

}

采用自定义锁可以带来更高的并发效率,当然具体采用什么样的锁,需要根据实际场景决定。

(2)RPC通信

在基于AIDL的通信中,由于允许多个客户端的存在,其实现必须是线程安全的,开发者要注意,对于IPC调用方法,如果是一种耗时的操作,应避免在UI主线程中调用,以免阻塞UI主线程。

(3)SQLite调用

对于SQLite的调用,可能会存在多处执行读写操作的场景,这种场景也需要考虑线程的安全性。为了方便开发者操作,Android提供了类似于AsyncTask的AsyncQueryHandler方法来解决这一问题,将耗时的查询等操作放置在非UI主线程中,操作结束后,通过Handler调用相应的UI主线程的方法处理操作执行的结果,示例如下:

AsyncQueryHandler mAsyncQueryHandler=new AsyncQueryHandler(getContectResolver()){

protected void onQueryComplete(int token, Object cookie, Cursor cursor){

if(cursor!=null && cursor.moveToFirst()){

}

}

}

mAsyncQueryHandler.startQuery(0, null, mUri, new String[] {

MediaStore.Audio.Media.TITLE,MediaStore.Audio.Media.ARTIST

},MediaStore.Audio.Media,DATA+”=?“, new string[]{path}, null());

时间: 2024-10-17 09:43:46

Android线程篇的相关文章

Java(Android)线程池---基础篇

1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? 1 newThread(newRunnable(){ 2 3 @Override 4 publicvoidrun(){ 5 // TODO Auto-generated method stub 6 } 7 }).start(); 那你就out太多了,new Thread的弊端如下: a. 每次new Thread新建对象性能差.b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致

Java(Android)线程池 总结

一种是使用Executors工厂生产线程池:另一种是直接使用ThreadPoolExecutor自定义. Executors工厂生产线程池 Java(Android)线程池 Trinea 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new Thread吗? Java 1 2 3 4 5 6 7 newThread(newRunnable(){ @Ove

android线程学习心得

有一篇关于android线程讲的非常好,大家可以参考下,其中有一句话讲的非常好,就拿来做开篇之句: 当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理,所以主线程通常又被叫做UI线程.在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行. 咱新手在第一次接

优化 Android 线程和后台任务开发

在 Android 开发中,你不应该做任何阻碍主线程的事情.但这究竟意味着什么呢?在这次海湾 Android 开发者大会讲座中,Ari Lacenski 认为对于长时间运行或潜在的复杂任务要特别小心.这一次演讲,我们将根据一个真实场景应用的需求,讨论 AsyncTask, Activity, 和 Service,逐步建立一个更易于维护的解决方案. Android 线程 (0:46) 当我们谈论线程,我们知道一个 Android 应用程序至少有一个主线程.这个线程是随着你的Application 

[android架构篇]mvp+rxjava+retrofit+eventBus

android架构篇 mvp+rxjava+retrofit+eventBus 高层不应该知道低层的细节,应该是面向抽象的编程.业务的实现交给实现的接口的类.高层只负责调用. 首先,要介绍一下一个项目中好架构的好处:好的软件设计必须能够帮助开发者发展和扩充解决方案,保持代码清晰健壮,并且可扩展,易于维护,而不必每件事都重写代码.面对软件存在的问题,必须遵守SOLID原则(面向对象五大原则),不要过度工程化,尽可能降低框架中模块的依赖性. 之前的一段时间,学习了一些新的技术,并把自己关注的技术整合

Android线程之主线程向子线程发送消息

和大家一起探讨Android线程已经有些日子了,谈的最多的就是如何把子线程中的数据发送给主线程进行处理,进行UI界面的更新,为什么要这样,请查阅之前的随笔.本篇我们就来讨论一下关于主线程向子线程如何发送数据,这个用的地方也是非常的多,例如当我们为了优化用户体验,我们会在不影响用户使用的情况下进行后台数据更新,好了废话不多说,开始我们今天的讨论. public class ThreadActivity extends Activity implements OnClickListener{ pri

理解Android线程创建流程(转)

/android/libcore/libart/src/main/java/java/lang/Thread.java /art/runtime/native/java_lang_Thread.cc /art/runtime/native/java_lang_Object.cc /art/runtime/thread.cc /system/core/libutils/Threads.cpp /system/core/include/utils/AndroidThreads.h /framewor

Android(线程二) 线程池详解

我们在ListView中需要下载资源时,赞不考虑缓存机制,那么每一个Item可能都需要开启一个线程去下载资源(如果没有线程池),如果Item很多,那么我们可能就会无限制的一直创建新的线程去执行下载任务,最终结果可能导致,应用卡顿.手机反应迟钝!最坏的结果是,用户直接卸载掉该App.所以,我们在实际开发中需要考虑多线程,多线程就离不开线程池.如果你对线程还不了解,可以看看这篇文章,Android(线程一) 线程. 使用线程池的优点: (1).重用线程,避免线程的创建和销毁带来的性能开销: (2).

Android 线程操作之线程池

Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }).start(); 那你就out太多了,new Thre