安卓线程操作技术

时间:2022-06-02 17:37:08


1、安卓线程简介

1.1android线程

当android程序第一次启动时,android系统会启动一个Linux进程和一个主线程。默认的情况下,多数的程序的组件都在该进程和线程中运行。Android会尽量保留一个正在运行的进程,除非到内存资源不足时才去释放其占有的资源供其他线程使用,这样就保证了用户正在访问的当前进程有足够的资源区响应用户事件。但是android系统是根据线程的优先级区分选择停止某个线程的。Android线程根据重要性分为5个级别:

(1)前台进程

也就是用户当前正在使用的进程。

(2)可见进程

前台进程不包含前台组件,但是屏幕上会显示一个可见正在运行的进程。例如download线程。

(3)服务进程

运行一个通过startService()启动的service。一般在此执行一些耗时任务,例如播放MP3,从网络下载数据等。

(4)后台进程

运行着一个用户不可见的activity(调用过onStop()方法)。这些进程对用户体验没有直接的影响。他们会保存在LRU(least recently used)列表中,一但内存不足时被第一时间回收

(5)空进程

作为缓存而运行的进程。而并没有运行任何组件。这种进程的重要程度最低。

1.2单线程模型

当一个程序第一次启动时,Android系统会开启一个对应的主线程(UI线程),这个主线程主要处理与UI想关的事件,例如用户点击Button事件,用户触摸屏幕事件等,并把这些时间分布到对应的组件上。所以主线程又叫UI线程。在开发Android应用的时候要遵守单线程模型原则:Android UI操作并不是线程安全的,而且这些操作必须在UI线程(主线程)中执行。线程安全就是当多个线程执行相同一段代码时结果相同。

我们已经知道AndroidUI是单线程的(single thread),为了避免因为加载数据过多而拖住GUI,我们应该把一些耗时的工作放在子线程中去做,但是如果幕后的线程执行了UI对象,就会报出错误信息:CalledFromWrongThreadException。下面我们将介绍关于安卓线程的一些操作。

1.3安卓消息处理

安卓消息处理的基本流程:

安卓线程操作技术

1.3.1 Message

Message消息可以理解为线程之间交流的信息,后台线程处理数据后如果想更新UI,就发送Message内含一些数据给UI线程,由UI线程区更行UI。Message即消息对象,Message Queue中的存放的对象。一个Message Queue中包含多个Message。Message实例对象的取得,通常使用Message类里的静态方法obtain(),该方法有多个重载版本可供选择;它的创建并不一定是直接创建一个新的实例,而是先从Message Pool(消息池)中看有没有可用的Message实例,存在则直接取出返回这个实例。如果Message Pool中没有可用的Message实例,则才用给定的参数创建一个Message对象。调用removeMessages()时,将Message从Message Queue中删除,同时放入到Message Pool中。除了上面这种方式,也可以通过Handler对象的obtainMessage()获取一个Message实例。

1.3.2 Handler

Hander理解为处理者,是Message的主要处理者,负责Message的发送和Message内容的执行处理,后台进程通过传递进来的Handler对象来使用sendMessage(message)发送消息。使用Handler,需要实现它的handleMessage(message)方法,在这个方法里处理Message的操作内容,例如updateUI。通常需要子类化Handler来实践handleMessage方法。

在初始化handler时有不同的构造函数,可以带一个Looper类型的参数。通过判断Handler对象里的Looper对象属于哪个线程,决定由哪个线程执行。

1.3.3 Message Queen

在单线程模型下,为了解决线程更新UI的问题,Android设计了一个MessageQueen(消息队列),线程可以通过该Message Queen并结合Handler和Looper组件进行信息交换。MessageQueue是一种数据结构,是一个消息队列,存放消息的地方。每一个线程最多只可以拥有一个MessageQueue数据结构。创建一个线程的时候,并不会自动创建其MessageQueue。通常使用一个Looper对象对该线程的MessageQueue进行管理。主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。

因此主线程会自动创建一个MessageQueue消息队列。其他非主线程,不会自动创建Looper,要需要的时候,通过调用prepare函数来实现创建looper对象,用于接收消息。下面就简单介绍一下这几个类的功能。

Message Queen消息队列,用来存放Handler发布的消息,遵守队列的先进先出原则。每一个Message Queen都会对应一个Handler。Handler会通过两种方法向Message Queen发送消息,一种是sendMessage一种是post。这两种方法发送的消息都会插到Message Queen的尾部按照先进先出的原则执行,但是两种方法的消息执行还是有区别的:通过sendMessage发送的是一个Message对象,会被Handler的handleMessage方法处理,而Post发送的是一个runnable对象,会自己执行。

1.3.4 Looper

我们知道handler是负责处理Message Queen,而Looper是负责管理Message Queen的,Andrid没有全局的Message Queen,Android会自动为主线程创建一个Message Queen,但是子线程并不会自己创建Message Queen,所有调用Looper.getMainLooper得到的对象不为null,但是调用Looer.myLooper()得到当前线程的Looper对象就有可能为null。在Handler类的dispatchMessage()方法里如何处理Message由用户指定,有三个判断,优先级由高到低是:

(1)Message里的callback,一个实现runnable接口的对象,其中run函数做处理工作。

(2)Message里的mCallback指向一个实现Callback接口的对象,由其handleMessage()进行处理。

(3)处理Message的Handler对象对应的类,继承并实现了其中的handleMessage()方法,通过实现handleMessage()实现处理Message。可见我们实现handleMessage()方法是优先级最低的。对于主线程和子线程之间交互,传送消息,最终谁来处理消息可以按照下面的标准:就是Handler对象里的Looper是属于哪条线程的,就由谁来处理。当Handler得构造函数参数为空时则为当前所在线程的Looper。也就是默认的是用当前所在线程的Looper。

2、安卓线程的实现方式

2.2继承Runnable接口

利用子类实现该接口,在run()方法中实现需要在线程中进行的复杂操作。

classMyRunnable implements Runnable{

       public MyRunnable() {

           // TODO Auto-generated constructorstub

       }

       @Override

       public void run() {

           // TODO Auto-generated method stub

           //在这里进行复杂操作

       }  

    }//Runnable接口

2.3继承Thread类

子类继承Thread类,主要实现三个函数:destroy();run();start()。run()函数用来执行复杂操作,start()用于启动线程,destory()函数用于销毁线程。


安卓线程操作技术

classMyThread extends Thread{

       @Override

       public void destroy() {

           // TODO Auto-generated method stub

           super.destroy();

       }

       @Override

       public void run() {

           // TODO Auto-generated method stub

           super.run();

       }

       @Override

       public synchronized void start() {

           // TODO Auto-generated method stub

           super.start();

       }

    }//Thread

2.4继承TimerTask类

TimerTask实际上是实现了Runnable接口:

classMyTimerTask extends TimerTask {

       @Override

       public void run() {

           // TODO Auto-generated method stub

           Message message=new Message();

           Bundle data=new Bundle();

           data.putCharSequence("data","利用Bundle回传的字符串");

           message.setData(data);

           myhandler.sendMessage(message);

       }

    }

2.5AsyncTask

相对于上面的例子的创建一个普通的thread,下面有利用AsyncTask去一步加载的过程:

利用AsyncTask去更新UI变得异常简单,不需要借助线程和Handler即可以实现,AsyncTask类必须被子类继承。AsyncTask实际上就是一个线程池,实际上要比handler更耗资源,因为AsyncTask底层是一个线程池!在代码中要实现以下步骤:

1. 实例化AsyncTask

2. 实现AsyncTask的下面全部或部分方法:

3. onPreExcute()该方法在执行后台操作前调用,可以做一下准备工作。

4. doInBackground(Params…)该方法执行后台数据处理,注意这里不能直接操作UI。

5. onPostExcute()该方法在执行完后台数据后把结果传递给UI线程。相当于handler处理UI的方式,在这里可以使用在doinbackground()得到的结果处理操作UI。在执行过程中可以调用publishProgress(progress…)来更新任务的进度。此方法在主线程执行,任务执行的结果作为此方法的参数返回。

这些方法都是不能手都调用的,而且这三个方法都是由UI线程调用的,所以AsyncTask对象实例一定要在主线程中创建,而且AsyncTask.excute()方法一定要在主线程中调用。

另外AsyncTask的构造函数的参数设置要明白AsyncTask<Params, Progress, Result>,这三个参数是数据类型第一个参数Params就是doInBackground(Params…)里的参数。Progress是onProgressUpdate(Progress...)的参数,result对应的是onPostExcute()的参数类型。当以上的参数类型都不需要指明某个参数时,则使用Void,注意V要大些,不是void。

3、遇到的问题及解决方法

在onCreate ()方法中创建线程,在onClick()方法中执行线程的run()方法,在run()中更新主线程的TextView会成功,这与安卓的线程不安全是矛盾的。

通过查资料得知:

Android 为了限制子线程更新UI,设置了线程的检查机制,例如从子线程对Textview.setText,通过报错的log能看出,抛出异常的位置是在android.view.ViewRoot.checkThread(),从字面的意思看,这个方法就是用来检查修改view的Thread是否为主线程。这个检查方法在view的requestLayout()里面被调用,但是requestLayout()有些情况下不会被调用:
if(mParent != null){
    if (!mParent.isLayoutRequested()) {
         mParent.requestLayout();
   }

}

 唯一的解释就是以上2个if中的一个不符合,导致后面的checkThread()没被调用,子线程就成功地修改了界面UI。

这个可能是在activity 被创建的初期,layout或者其他动作初始化还没有完成,导致线程的检查没有发生。在new Thread修改textview之前让线程睡眠2秒,这时候再来修改UI就会抛出异常了。或者在Thread对象中不直接执行run()方法,而是执行start()方法,也会出现异常。