首页

深入理解UI与线程

搜索

ANR术语

ANR的全称:Activity or Application is not responding,当用户操作超过系统规定的响应时间时,会弹出ANR对话框

,如图-3所示:

图-3

若选择Force close按钮将强制关闭当前的Activity;

若选择Wait按钮将保留当前的Activity继续等待。

出现ANR的条件:

1. 在main线程(或称主线程)中有一个耗时操作正在执行,此时用户输入事件并且这个事件

在5秒内没有得到响应,就会弹出ANR。

2. 广播接收者的onReceive()方法在10秒内没有执行完成,也会弹出ANR。

提示:在广播接收者的onReceive方法中要避免执行耗时的操作。

示例-测试ANR发生的两种情况

创建项目exer12_02,在该类中创建MyReceiver类,该类是BroadcastRecevier的子类。

图-4

1、单击download按钮,在主线程中执行一个循环模拟从网络下载数据的操作,该循环耗时10秒,

在该循环执行过程中,在图-4的标注所指的编辑框中连续试图输入字符串,5秒后,将弹出图-3所示的ANR对话框。

2、单击send broadcast按钮,发送广播,然后在图-4的标注所指的编辑框内连续试图输入字符串,

10秒后将弹出ANR对话框。

以下是关键代码:

步骤1、创建工具类-CommonUtils.java,该类中定义了一个模拟耗时操作的循环,代码如下所示:

public final class CommonUtils {

public static final String ACTION_RECEIVER="com.tarena.ACTION_RECEIVER";

public static void timeConsuming(int n){

for(int i=0;i<n;i++){

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

步骤2、创建MyReceiver.java类,该类继承自BroadcastReceiver类,代码如下所示:

public class MyReceiver extends BroadcastReceiver {

private static final String tag="MyReceiver";

@Override

public void onReceive(Context context, Intent intent) {

Log.i(tag,"onReceiver");

CommonUtils.timeConsuming(15);

}

}

步骤3、以下是MainActivity.java类的代码,该代码负责发送广播,模拟下载的耗时操作,代码如下所示:

public class MainActivity extends Activity implements OnClickListener{

 TextView mTextView;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mTextView=(TextView)findViewById(R.id.tv);

        //实例化按钮对象

        Button btnDownload=(Button)findViewById(R.id.btnDownload);

        Button btnSendBraodcast=(Button)findViewById(R.id.btnSendBroadcast);

        //注册按钮对象的单击事件

        btnDownload.setOnClickListener(this);

        btnSendBraodcast.setOnClickListener(this);

    }

    //实现按钮的单击事件

@Override

public void onClick(View v) {

switch(v.getId()){

case R.id.btnDownload://下载按钮

CommonUtils.timeConsuming(10);//模拟(耗时10秒的)下载

mTextView.setText("finished");

break;

case R.id.btnSendBroadcast://发送广播按钮

//发送广播

Intent intent=new Intent(CommonUtils.ACTION_RECEIVER);

sendBroadcast(intent);

mTextView.setText("finished");

break;

}

}

}

Message对象

概述

Message类用于存放消息,该类通常与Handler类配合使用。

常用属性

1、int arg1:存放一个int类型的数据。

2、int arg2:存放一个int类型的数据。

3、int what:存放一个int类型的数据。

4、Object obj:存放任意类型的对象。

示例代码

Message msg=new Message();

msg.what=1;

msg.arg1=100;

msg.obj=”hello”;

msg.obj=new Runnable();

用Handler更新UI

概述

由以上所述,在主线程中不宜执行耗时操作,因此,通常的耗时操作都放在其它线程中,

Androidghi称这样的线程为work thread(工作线程)。但Android还规定:只有主线程才能修改Activity中的控件,其它线程不能修改。

解决以上问题有多种方法,本节介绍如何通过Handler类提供的方法解决工作线程不能直接修改UI的问题。

Handler修改主线程UI的思路:Handler对象通过在工作线程中发送消息,该消息发送至消息对列中,等待处理。

在主线程中从消息对列中接收消息,根据消息中的信息决定如何更新主线程中UI.

常用方法

1、sendEmptyMessage(int what);

作用:从work thead(工作线程)向主线程发送一个空消息。

说明:若多个线程向主线程发送消息,则参数what用于区别不同的线程。

2、sendEmptyMessageAtTime(int what,long uptime);

作用:从work thead(工作线程)按指定时间发送空消息。

说明:第二个参数uptime:指定的时间。

3、sendEmptyMessageDelayed(int what,long delay);

作用:从work thead(工作线程)延迟发送空消息。

说明:第二个参数用于指定延迟的时间,单位:毫秒。

5、sendMessage(Message msg);

作用:从work thead(工作线程)向主线程发送消息;

说明:msg是存放消息数据的对象。

6、sendMessageAtTime(Message msg,long uptime);

作用:从work thead(工作线程)按指定时间向主线程发送消息

7、sendMessageDelayed(Message msg,long delay);

作用:从work thead(工作线程)延迟指定时间向主线程发送消息。

8、handleMessage(Message msg);

作用:接收并处理从work thread发送的消息。

说明:参数-msg:send***Message发送过来的消息对象。

图-5是Android处理消息机制的示意图:

图-5

图-5显示了Android系统提供了一个称为Looper(循环对列)用来管理消息对列,

各线程通过Handler类的Send***Message命令将消息发送至消息对列,Looper再将消息对列中的消息依次交给主线程处理。

示例

从两个工作线程向主线程发送不同的消息,主线程接收消息并显示不同的处理信息。

步骤1、以下是Activity类中的onClick()方法中的代码,该方法用于处理按钮单击事件。

@Override

public void onClick(View v) {

switch(v.getId()){

case R.id.btnDownload://若单击了标题为下载的按钮

//创建一个work thread(工作线程)线程对象

new Thread(){

public void run() {

//以下一行代码模拟下载进度,执行时间约为10秒

CommonUtils.timeConsuming(10);

//创建消息对象

Message msg=new Message();

//CommonUtils.FLAG_DOWNLOAD值代表下载操作

msg.what=CommonUtils.FLAG_DOWNLOAD;

msg.obj="download finished";

mHandler.sendMessage(msg);//发送消息

};

}.start();//启动线程

break;

case R.id.btnUpdate://若单击了标题为更新的按钮

//创建一个work thread(工作线程对象)

new Thread(){

public void run() {

//以下一行代码模拟更新进度,执行时间约为10秒

CommonUtils.timeConsuming(8);

//创建消息对象

Message msg=new Message();

//CommonUtils.FLAG_UPDATE代表更新操作

msg.what=CommonUtils.FLAG_UPDATE;

msg.obj="update finished";

mHandler.sendMessage(msg);//发送消息

};

}.start();//启动线程

break;

}

}

步骤2、以下是在Activity.onCreate()方法中(主线程)中创建的Handler对象-mHandler:

Button btnDownload.setOnClickListener(this);

Button btnUpdate.setOnClickListener(this);

mHandler=new Handler(){

@Override

public void handleMessage(Message msg) {

    switch(msg.what){

    case CommonUtils.FLAG_DOWNLOAD:

        mTextView.setText(“下载结束”);

        break;

    case CommonUtils.FLAG_UPDATE:

         mTextView.setText(“更新结束”);

         break;

    }

}

说明:

1、CommonUtils是一个自定义的工具类,该类中包括以下两个int类型的常量:

public static final int FLAG_DOWNLOAD=1;

public static final int FLAG_UPDATE=2;

2、msg是work thread线程发送过来的消息对象,该线程代码在步骤1中列出。

3、若msg.what的值是CommonUtils.FLAG_DOWNLOAD,则在标签中显示:下载结束。

   若msg.what的值是CommonUtils.FLAG_UPDATE ,则在标签中显示:更新结束。

发送Runnalbe对象-更新UI

概述

1、Runnable接口

该接口的源代码如下所示:

public interface Runnable {

     public void run();

}

Java规定:一个线程要实现Runnable中的run方法。Android的线程也同样如此。在new 一个Thread时,

查看Android源代码,将发现内部代码也是要创建一个Runnable的对象,并实现run方法。

2、Handler.post()方法

Handler类中有一个方法:post(Runnable r);

作用:将Runnable对象作为参数发送至消息对列中,以下是post方法的源代码:

    public final boolean post(Runnable r)

    {

       return  sendMessageDelayed(getPostMessage(r), 0);

    }

由上代码可知,Runnable对象r被当作消息的第一个参数发送至消息队列,然后由Looper交给主线程处理,如图-5所示。

根据以上原理,Runnable.run中的代码可以在主线程中运行,那么在Runnable.run方法中可以编写更新主线程UI的代码。

示例

在按钮单击事件方法中创建一个工作线程,在该线程中先模拟下载操作,操作结束后,

向主线程发送了一个实现Runnable的内部匿名类,代码如下所示:

public void onClick(View v) {

switch(v.getId()){

case R.id.btnDownload:

//创建Handler对象

final Handler handler=new Handler();

//new一个工作线程模拟下载操作

new Thread(){

public void run() {

CommonUtils.timeConsuming(5);//模拟下载操作

/*下载结束后,

*将Runable的内部匿名类的代码

*当作消息中的第一个参数发送至消息对列,

 * 再由Looper交给主线程执行*/

handler.post(new Runnable() {

@Override

public void run() {

mTextView.setText("download finished");

}

});

};

}.start();//启动工作线程

break;

case R.id.btnUpdate:

break;

}

}

runOnUiThread()发送Runnable对象

概述

Activity类中提供了一个runOnUiThread方法,该方法封装了Handler.post方法,因此作用与Handler.post相同。

示例

用runOnUiThread方法发送Runnable对象,让主线程执行该对象中的run方法。示例代码如下:

//new一个工作线程

new Thread(){

public void run() {

CommonUtils.timeConsuming(8); //模拟更新操作

//创建一个Runnable接口的实例

Runnable action=new Runnable() {

//实现run方法,在该方法中可修改UI

@Override

public void run() {

mTextView.setText("update finished");

}

};

//将action对象发送至消息对列,交由主线程执行run方法中的代码

runOnUiThread(action);

};

}.start();//启动线程

View.post()发送Runnable对象-更新UI

概述

View类中也提供了发送Runnable对象至消息对列,然后由Looper交给主线程的方法:

1、Post(Runnable r);

2、postDelayed(Runnable r,long delayMillis);

作用:延迟指定时间,再将r对象发送至主线程执行。

示例

@Override

public void onClick(final View v) {

switch (v.getId()) {

case R.id.download:

new Thread() {

public void run() {

CommonUtils.timeConsuming(1);

Runnable action = new Runnable() {

@Override

public void run() {

mTextView.setText("下载完成");

}

};

//v-当前的按钮对象,延迟1秒发送action对象由主线程执行

v.postDelayed(action, 1000);

}

}.start();

break;

case R.id.update:

new Thread() {

public void run() {

CommonUtils.timeConsuming(8);

Runnable action = new Runnable() {

@Override

public void run() {

mTextView.setText("更新新闻完成");

}

};

/v-当前的按钮对象,延迟3秒发送action对象由主线程执行

v.postDelayed(action, 3000);

}

}.start();

break;

post总结

概述

Android提供了post方法,将Runnable对象(当作消息中的第一参数)发送至消息对列,

然后由Looper将消息对列中的消息交给主线程执行。如此,就可以在Runnable.run中编写修改UI的代码。

但要注意:不要在run方法中编写耗时操作的代码。

耗时的代码要放在工作线程的run方法中运行。以下列出已介绍过的post的方法

1、runOnUiThread(Runnable):该方法对handler.post进行了封装。

  2、View.post(Runnable):运行被选择的控件对象发送Runnable对象。

 3、Handler.post(Runnable)

 4、View.postDelayed(Runnable, long):延迟发送

 5、Handler.postDelayed(Runnable, long):延迟发送

Handler.post与runOnUiThread的比较

1. Handler可以实现线程间消息通讯。

2. 在代码的可读性和性能(创建对象的个数)方面,Handler都更有优势。

AsyncTask更新UI

概述

前面我们采用的都是new一个线程对象,采用这种匿名线程的方式存在以下缺陷:

第一,线程的开销较大,如果每个任务都要创建一个线程,那么应用程序的效率要低很多;

第二,线程无法管理,匿名线程创建并启动后就不受程序的控制了,如果有很多个请求发送,那么就会启动非常多的线程,

系统将不堪重负。 另外,在工作线程中更新UI还必须要引入handler,这让代码看上去非常臃肿。

为了解决这一问题,引入了AsyncTask。AsyncTask的特点是任务在主线程之外运行,而回调方法是在主

线程中执行, 这就有效地避免了使用Handler带来的麻烦。阅读AsyncTask的源码可知,AsyncTask是使用java.util.concurrent 

框架来管理线程以及任务的执行的,

concurrent框架是一个非常成熟,高效的框架,经过了严格的测试。这说明AsyncTask的设计很好的解决了匿名线程存在的问题。

     AsyncTask是抽象类,子类必须实现抽象方法doInBackground(Params... p) ,在此方法中实现任务

的执行工作,比如连接网络获取数据等。通常还应该实现onPostExecute(Result r)方法,因为应用程

序关心的结果在此方法中返回。

提示:AsyncTask一定要在主线程中创建实例。

AsyncTask定义了三种泛型类型 Params,Progress和Result。

    (1)Params 启动任务执行的输入参数,比如HTTP请求的URL。

(2)Progress 后台任务执行的百分比。

(3)Result 后台执行任务最终返回的结果,比如String。

常用方法

     AsyncTask 的执行分为四个步骤,每一步都对应一个回调方法,需要注意的是这些方法不应该由应用

程序调用,开发者需要做的就是实现这些方法。在任务的执行过程中,这些方法被自动调用。

1、 onPreExecute();

作用:当任务执行之前开始调用此方法,可以在这里显示进度对话框。
2、doInBackground(Params...);

作用:在后台线程执行,执行耗时操作。在执行过程中可以调用publicProgress(Progress...)来更新任务的进度。

提示:publicProgress()相当于Handler.sendmessage()方法

3、onProgressUpdate(Progress...);

作用:此方法在主线程执行,用于显示任务执行的进度。
4、onPostExecute(Result);

作用:此方法在主线程执行,任务执行的结果作为此方法的参数返回。

示例

用AsyncTask实现图-6效果,当单击dwonload按钮后,在进度条中显示模拟的下载进度,

并在标签中显示进度的百分比。下载结束后显示download finised。

图-6a 图-6b

public class MainActivity extends Activity implements OnClickListener{

    Handler mHandler;

    ProgressBar mProgressBar;//声明进度条

    TextView mTextView;//标签:显示进度的百分比和操作的结果

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        //实例化各控件

        mProgressBar=(ProgressBar)findViewById(R.id.progressBar);

        mTextView=(TextView)findViewById(R.id.tvMessage);

        Button btnDownload=(Button)findViewById(R.id.btnDownload);

        Button btnUpdate=(Button)findViewById(R.id.btnUpdate);

        //注册按钮的单击事件

        btnDownload.setOnClickListener(this);

        btnUpdate.setOnClickListener(this);

    }

    //实现按钮的单击时间事件

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btnDownload:

//创建对象

MyAsyncTask myAsyncTask=new MyAsyncTask();

myAsyncTask.execute(null);//执行任务

break;

case R.id.btnUpdate:

break;

}

}

private class MyAsyncTask extends AsyncTask<URL, Integer, String>{

//在UI中执行,更新UI

@Override

protected void onProgressUpdate(Integer... values) {

mProgressBar.setProgress(values[0]);

if(values[0]<100){

mTextView.setText("progress="+values[0]+"%");

}

}

//现在work thread中执行耗时操作

@Override

protected String doInBackground(URL... params) {

for (int i = 0; i < 100; i++) {

publishProgress(i+1);//向onProgressUpdate发送消息

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

return "download finished";

}

//doInBackground结束后,执行本方法,result是doInBackground方法返回的数据

@Override

protected void onPostExecute(String result) {

mTextView.setText(result);

}

}

}

软件开发术语

性能

    临时对象越多,垃圾回收(GC)的频率越高

    GC占用CPU,CPU被占用时,无法响应用户的操作

    用户感觉到卡,影响用户体验。

资源池

   存放一定数量的同样类型的对象,当程序需要使用时,可以从资源池中获取,使用完成,收回资源池。

   等待下一次被使用。

   示例:从资源池中获取Message对象。

  Message msg=Message.obtainMessage();

   提示:若之前没有创建过Message,则创建给对象。Android系统会在适当时候回收该对象,方便下次取用。

提示:解决性能问题的前提是不能影响功能。

 

 

 

上一页 下一页