主线程(UI线程)

    Android的单线程模型原则

  当应用启动,系统会创建一个主线程(main thread)。

  这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的UI组件发生交互。

      所以主线程也叫做UI线程。

  系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去

  结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。 

  当App做一些比较重(intensive)的工作的时候,除非你合理地实现,否则单线程模型的performance会很poor。

  特别的是,如果所有的工作都在UI线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞UI线程导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。

  另外,Andoid UI toolkit并不是线程安全的,所以你不能从非UI线程来操纵UI组件。你必须把所有的UI操作放在UI线程里,所以Android的单线程模型有两条原则:

  1.不要阻塞UI线程。

  2.不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view)。

(即对UI的操作,永远在UI线程中进行)

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}
咋一看,这个没有问题,不阻塞UI线程,但是UI线程是非安全的线程,这个例子在新的线程外对Imagview组件进行了操作,会发生不安全因素。因此

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 

从错误信息不难看出Android禁 止其他子线程来更新由UI thread创建的试图。

(以上内容摘自圣骑士Wind的博客)

与UI线程建立联系

Message Queue 
在单线程模型下,为 了解决类似的问题,Android设 计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组 件进行信息交换。下面将对它们进行分别介绍: 

Message Queue 

Message Queue是一个消息队列,用来存放通过Handler发 布的消息。消息队列通常附属于某一个创建它的线程,可以通过Looper.myQueue()得 到当前线程的消息队列。Android在 第一启动程序时会默认会为UI thread创建一个关联的消息队列,用来管理程序的一些上层组件,activities,broadcast receivers 等等。你可以在自己的子线程中创建Handler与UI thread通讯。 

Handler 

通过Handler你 可以发布或者处理一个消息或者是一个Runnable的 实例。没个Handler都 会与唯一的一个线程以及该线程的消息队列管理。当你创建一个新的Handler时候,默认情况下,它将关联到创建它的这个线程和该线程的消息队列。也就是说,如果你通过Handler发 布消息的话,消息将只会发送到与它关联的这个消息队列,当然也只能处理该消息队列中的消息。 

主要的方法有: 

1)   public final boolean sendMessage(Message msg) 

把消息放入该Handler所 关联的消息队列,放置在所有当前时间前未被处理的消息后。 

2)   public void handleMessage(Message msg) 

关联该消息队列的线 程将通过调用Handler的handleMessage方 法来接收和处理消息,通常需要子类化Handler来 实现handleMessage。 (Handler发送)

Looper 


Looper扮演着一个Handler和 消息队列之间通讯桥梁的角色。程序组件首先通过Handler把 消息传递给Looper,Looper把 消息放入队列。Looper也 把消息队列里的消息广播给所有的Handler,Handler接 受到消息后调用handleMessage进 行处理。 (一对多的关系)

1)   可以通过Looper类 的静态方法Looper.myLooper得 到当前线程的Looper实 例,如果当前线程未关联一个Looper实 例,该方法将返回空。 

2)   可以通过静态方法Looper. getMainLooper方法得到主线程的Looper实 例 

 AsyncTask 

虽然借助消息队列已经可以较为完美的实现对UI的更新,但是你还是不得不自己管理子线程,尤其当你的需要有一些复杂的逻辑以及需要频繁的更新UI的时候,这样的方式使得你的代码难以阅读和理解。 

幸运的是Android另外提供了一个工具类:AsyncTask。它使得UI thread的使用变得异常简单。它使创建需要与用户界面交互的长时间运行的任务变得更简单,不需要借助线程和Handler即可实现。 

1)   子类化AsyncTask 

2)   实现AsyncTask中定义的下面一个或几个方法 

?  onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。 

?  doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。 

?  3. onProgressUpdate(Progress...),在publishProgress方 法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。 

?  4. onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread. 

为了正确的使用AsyncTask类,以下是几条必须遵守的准 则: 

1)   Task的实例 必须在UI thread中创建 

2)   execute方 法必须在UI thread中调用 

3)   不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法 

4)   该task只能被执行一次,否则多次调用时将会出现异常 

下面我们将通过AsyncTask并且严格遵守上面的4条准则来改写天气预报的例子:

public void onCreate(Bundle savedInstanceState) {   
  
       super.onCreate(savedInstanceState);   
  
       setContentView(R.layout.main);   
  
       editText = (EditText) findViewById(R.id.weather_city_edit);   
  
       Button button = (Button) findViewById(R.id.goQuery);   
  
       button.setOnClickListener(this);   
  
    }   
  
    public void onClick(View v) {   
  
       //获得用户输 入的城市名称   
  
       String city = editText.getText().toString();   
  
    //必须每次都 重新创建一个新的task实例进行 查询,否则将提示如下异常信息   
  
    //the task has already been executed (a task can be executed only once)   
  
       new GetWeatherTask().execute(city);   
  
    }   
  
    class GetWeatherTask extends AsyncTask<String, Integer, String> {   
  
       @Override   
  
       protected String doInBackground(String... params) {   
  
           String city = params[0];   
  
           //调用Google 天气API查询指定 城市的当日天气情况   
  
           return getWetherByCity(city);   
  
       }   
  
       protected void onPostExecute(String result) {   
  
           //把doInBackground处理的结果 即天气信息显示在title上   
  
           setTitle(result);   
  
       }   
  
    }   

注意这行代 码:new GetWeatherTask().execute(city); 值得一提的是必须每次都重新创建一个新的GetWeatherTask来执行后台任务,否则Android会提示“a task can be executed only once”的错误信息。 

经过改写后的 程序不仅显得非常的简洁,而且还减少了代码量,大大增强了可读性和可维护性。因为负责更新UI的onPostExecute方 法是由UI thread调用,所以没有违背单线程模型的原则。良好的AsyncTask设计大大降低了我们犯错误的几率。 

  在使用过程中,发现AsyncTask的构造函数的参数设置需要看明白:AsyncTask<Params, Progress, Result> 
    Params对应doInBackground(Params...)的参数类型。而new AsyncTask().execute(Params... params),就是传进来的Params数据,你可以execute(data)来传送一个数据,或者execute(data1, data2, data3)这样多个数据。 
    Progress对应onProgressUpdate(Progress...)的参数类型; 
    Result对应onPostExecute(Result)的参数类型。 
    当以上的参数类型都不需要指明某个时,则使用Void,注意不是void。不明白的可以参考上面的例子,或者API Doc里面的例子。 

其实这篇文章大部分都是转载的,但是可以加深理解,特别上面对Handler的剖析,很好。


你可能感兴趣的:(UI线程)