虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > Android编程 > Android线程篇

Android线程篇
类别:Android编程   作者:码皇   来源:互联网   点击:

在Android中,UI主线程并非线程安全的,所有UI相关的操作均需在UI主线程中完成。在默认情况下,开发者创建的Service、Activity、Broadcast均运行在UI主线程中,但将一些耗时操作,如网络下载、大

在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{

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 AsyncTaskexecute(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{

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());

 

相关热词搜索: 线程