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

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值