13.1.深入理解AnsyncTask

13.1.1.publishProgress()方法传递对象参数

13.1.1.1.概述

AnsyncTask.publishProgress方法不仅可以传递整形实参,也可以传递任意类型的对象

13.1.1.2.操作步骤

步骤1、自定义类,示例代码:

//自定义类,存放下载进度值和下载提示信息

privateclassInfo{

privateintprogress;//下载进度值

privateStringinfo;//下载提示信息

publicintgetProgress(){

returnprogress;

}

publicStringgetInfo(){

returninfo;

}

//带参数的构造方法

publicInfo(intprogress,Stringinfo){

super();

this.progress=progress;

this.info=info;

}

}

步骤2、在自定义AsyncTask类时,将第二个泛型参数设置为步骤1定义的类,示例代码:

privateclassMyAsyncTaskextendsAsyncTask<URL,Info,String>{

步骤3、在doInBackground方法中,将需要在UI中更新的数据存放在自定义的对象中,然后调用publishProgress方法,在参数中将info类的一个对象发送给onProgressUpdate()方法。示例代码:

publishProgress(newInfo(i+1,"progress="+(i+1)+"%"));

步骤4、在onProressUpdate方法中用获得的对象更新UI,示例代码:

//重写本方法,在UI中显示下载进度和提示信息,参数Info类的对象

@Override

protectedvoidonProgressUpdate(Info...info){

super.onProgressUpdate(info);

mProgressBar.setProgress(info[0].getProgress());

mtvProgress.setText(""+info[0].getInfo());

}

13.1.2.publishProgress()方法中可变参数的用法

13.1.2.1.概述

publishProgress方法可以向onProgressUpdate发送数量任意多的实参。但这些数据必须是同一类型的。

13.1.2.2.操作步骤

步骤1、在doInBackground方法中,定义需要传递的参数的数组,示例代码:

Integer[]values=newInteger[2];

values[0]=i+1;//第一进度值

values[1]=i+1+10;//第二进度值

步骤2、用publishProgress方法发送步骤1定义的values数组,示例代码:

publishProgress(values);

步骤3、在onProgressUpdate方法中接收,并使用传递过来的数组更新UI,示例代码:

//重写本方法,更新进度

@Override

protectedvoidonProgressUpdate(Integer...values){

super.onProgressUpdate(values);

mProgressBar.setProgress(values[0]);//更新进度条中的第一个进度值

mProgressBar.setSecondaryProgress(values[1]);//更新第二个进度值

}

13.2.深入理解Handler

13.2.1.sendEmptyMessage()方法

1sendEmptyMessage(intwhat);

2、作用:向Handler对象所工作的线程的消息队列发送空消息。

3、说明:参数what用于在消息队列中标识不同的消息。

4、示例:在窗口中单击一个按钮,然后启动一个工作线程执行一段时间,执行完毕后,向主线程的消息队列发送一个空消息,主线程在窗口显示一段文字信息。

关键步骤及代码如下所示:

步骤1、在Activity.onCreate()方法中创建Handler对象,重写handleMessage方法,代码如下所示:

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mtvResult=(TextView)findViewById(R.id.tvResult);

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

btnDownload.setOnClickListener(this);//注册单击事件

//在主线程创建mHandler并接收工作线程发送的消息

mHandler=newHandler(){

publicvoidhandleMessage(android.os.Messagemsg){

//判断msg.what

switch(msg.what){

caseDOWNLOAD_FINISHED://若是该常量

//在标签的标题上显示donwloadfinished

mtvResult.setText("donwloadfinished");

break;

}

};

};

}

步骤2、在按钮的单击事件中创建并启动线程,在线程中模拟耗时操作结束后,向主线程的消息队列发送一个空消息,代码如下所示:

//实现单击事件

@Override

publicvoidonClick(Viewv){

switch(v.getId()){

caseR.id.btnDownload:

//创建并启动线程,模拟下载

newThread(){

publicvoidrun(){

for(inti=0;i<3;i++){

try{

Thread.sleep(1000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

//循环结束后,向主线程发送一个空消息,并传递一个整数值

mHandler.sendEmptyMessage(DOWNLOAD_FINISHED);

};

}.start();

}

}

13.2.2.巧用Handler.postDelayed()

13.2.2.1.概述

上一章介绍了Handler类中的postpostDelayed方法,这两个方法通过发送一个消息至主线程的消息队列(其中该消息的第二参数是一个实现了Runnable接口的对象),主线程执行该对象中的run方法中的代码,达到让主线程执行工作线程中发送的代码片段的效果。

以下介绍不用new线程对象,只用postDelayed方法,即可实现一个工作线程定时修改UI的效果。

13.2.2.2.示例

单击图-1中的run按钮,将在窗口中以秒为单位显示一个计器。

单击stop按钮,计时器停止计时。

图-1

步骤1、在Activity.onCreate()方法中创建按钮控件,创建mHandler对象。

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

//创建控件

mtvResult=(TextView)findViewById(R.id.tvResult);

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

ButtonbtnPause=(Button)findViewById(R.id.btnPause);

//注册按钮的单击事件

btnDownload.setOnClickListener(this);

btnPause.setOnClickListener(this);

mHandler=newHandler();

}

步骤2、在按钮单击事件中编写响应单击run按钮和stop按钮的代码:

//实现单击事件

@Override

publicvoidonClick(Viewv){

switch(v.getId()){

caseR.id.btnDownload://单击下载按钮

/*向消息队列发送消息,callback对象作为该消息的第二个参数,

*该对象中封装的代码片段将被主线程执行

*/

mHandler.post(callback);

break;

caseR.id.btnPause://若是暂停按钮,将callback所在的消息从消息队列中移除

mHandler.removeCallbacks(callback);

break;

}

}

说明:

第一个红框中的代码将callback对象中封装的代码发送至主线程的消息队列,由主线程执行。

第二个红框中的removeCallbacks方法的作用是将参数callbakc所在的消息从主线程的消息队列中删除。

步骤3创建一个实现了Runnable接口的对象-callback,该类实现了run方法,在run方法中以秒为单位,动态显示时间。代码如下所示:

//创建一个Runnable接口的实现对象

privateRunnablecallback=newRunnable(){

//实现run方法

@Override

publicvoidrun(){

//显示时间(单位:秒)再递增1

mtvResult.setText(mProgress+++"s");

//一秒后再次向消息队列发送封装在callback对象中的代码片段

mHandler.postDelayed(callback,1000);

}

};

说明:

红框中代码调用mHandler.postDelayed方法将callback对象再次发送至消息队列由主线程执行。如此循环反复。这种做法部分代替了工作线程的作用,即不用再new一个工作线程来刷新UI.

但要注意:不要在以上代码的run方法中执行耗时操作,因为Runnable只是一个接口,而不是线程,该处的代码是被发送至主线程中执行,若出现耗时操作同样要阻塞主线程中其它的用户输入操作,同样可能出现ANR现象。

13.2.3.Looper

13.2.3.1.概述

Android应用程序是消息驱动的,Android系统提供了消息循环机制。Android通过LooperHandler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。

Android系统中Looper负责管理线程的消息队列(messagequeue)和消息循环。

13.2.3.2.重要方法

1Looper.prepare()

作用:创建Looper对象和MessageQueue(消息队列)对象。

2Looper.getMainLooper();

作用:获得主线程的Looper对象。

3Looper.myLooper();

作用:获得当前工作线程的Looper对象。

4Looper.loop();

作用:进入消息循环。

13.2.3.3.Looper运行机制

Looper负责管理线程的消息队列和消息循环,通过Loop.myLooper()得到当前线程的Looper对象,通过Loop.getMainLooper()获得主线程的Looper对象。Looper不能用new来实例化,而是要用Looper.prepare()实例化。

Android中每一个线程都可以(但不是必须)跟着一个LooperLooper可以帮助线程维护一个消息队列,每个线程可以存在一个消息队列和一个消息循环(Looper),特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具有消息队列和消息循环,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。如下例所示

Looper.prepare();//创建消息队列

myLooper=Looper.myLooper();//获得当前线程中的Looper对象

Looper.loop();//进入当前线程的消息循环

一个Activity中可以创建多个工作线程,如果这些线程把们的消息放入主线程消息队列,那么该消息就会在主线程中处理了。因为主线程一般负责界面的更新操作,所以这种方式可以很好的实现Android界面更新。ActivityLooperHandler的关系如下图所示

其它线程通过Handle对象消息放入主线程的消息队列只要Handler对象以主线程的Looper创建,那么调用HandlersendMessage方法,会把消息放入主线程的消息队列。并且Handler将会在主线程中调用handleMessage方法来处理消息。

13.2.3.4.示例

创建一个Thread类线程,该线程具有消息队列和处理消息队列的Looper

 classLooperThreadextendsThread{

  publicHandlermHandler;

  publicvoidrun(){

   Looper.prepare();

   mHandler=newHandler(){

   publicvoidhandleMessage(Messagemsg){

   /*processincomingmessageshere*/

  };

Looper.loop();

  不管是主线程还是工作线程,只要有Looper的线程,别的线程就可以向这个线程的消息队列中发送消息和计划任务,然后做相应的处理。

13.2.4.HandlerMessageMessageQueueLooper四者关系

Looper对象由Looper.prepare()方法来创建,该方法同时会创建MessageQueue,该消息队列中的MessageHandler发送至主线程的消息队列,由主线程的MainLooper处理消息。

MainLooper从上至下依次从消息队列中取出消息进行处理。如图-2所示:

图-2

Handler对象在创建时,可以在构造方法中带参数,也可以不带参数。若用无参构造方法创建,则Handler在所在的线程中工作。

若用带参构造方法创建,则按参数在指定的线程中工作。

示例1

//获取主线程的Looper对象

LoopermainLooper=Looper.getMainLooper();

Handlerhandler=newHandler(mainLooper);

handler对象工作在主线程。以上代码的第二行也可以写成:

Handlerhandler=newHandler(Looper.getMainLooper);

示例2

//获取当前线程的Looper对象

LoopermyLooper=Looper.myLooper();

//handler工作在当前线程

Handlerhandler=newHandler(myLooper);

可以做以下比喻:

Message集装箱:消息中存放各种类型的数据

MessageQueue码头:存放许多Message

Handler船:将工作线程中的消息发送至主线程的消息队列中

Looper搬运工:处理当前线程的消息队列中的消息

提示:

Handler不一定都在主线程中创建,也可以在工作线程中创建。如果构造参数为空,那么在哪个线程中newHandler,则handlerMessage()方法就在哪个线程中处理该线程中的消息队列中的消息。

13.3.Handler.Callback接口

13.3.1.概述

Handler在创建时,通常用如下代码:

mHandler=newHandler(Looper.getMainLooper()){

publicvoidhandleMessage(Messagemsg){

//编写处理消息的代码

}

};

以下介绍Handler的另一种创建方法,该方法需要使用Handler类的一个内部接口Callback,该接口源代码如下所示:

publicinterfaceCallback{

publicbooleanhandleMessage(Messagemsg);

}

通过创建一个实现了该接口的对象,然后将该对象作为创建Handler的参数,也能创建Handler对象。

13.3.2.创建步骤

步骤1、创建Handler.Callback对象:

Handler.Callbackcallback=newHandler.Callback(){

//实现handleMessage方法

@Override

publicbooleanhandleMessage(Messagemsg){

//编写处理消息的代码

returnfalse;

}

};

步骤2、将callback作为参数,用来创建Handler对象:

Handlerhandler=newHandler(callback);

13.4.HandlerThread类

13.4.1.概述

上面所示创建能处理消息的线程类的代码有些麻烦,Android提供了一个线程类HanderThread类,HanderThread类继承了Thread类,它封装了Looper对象,我们不用关心Looper的开启和释放的细节问题。HandlerThread对象中可以通过getLooper方法获取一个Looper对象引用。

13.4.2.创建HandlerThread对象的步骤

步骤1、创建handlerThread类型的线程对象,如下代码所示

mThread=newHandlerThread("MyThread");

步骤2、启动线程,如下代码所示:

mThread.start();

步骤3、创建与mThread线程相关联的Handler对象,代码如下所示:

//重新创建mHandler,此时的mHandler工作在workthread

mHandler=newHandler(mThread.getLooper()){

publicvoidhandleMessage(Messagemsg){

Log.i("looper",Thread.currentThread().getName());

};

};

说明:第一行的参数mThread.getLooper()方法获得mThread线程对象中的Looper对象,通过该参数将mHandlermThread关联起来。

13.5.列表对话框

13.5.1.概述

AlertDialog提供了多种风格的对话框,本节介绍列表对话框,列表对话框如图-3所示:

图-3

13.5.2.重要方法

setItems((CharSequence[]items,finalOnClickListenerlistener);

1、作用:创建并显示列表对话框,并返回AlertDialog.Builder类的对象。

2、参数说明:

(1)第一个参数:存放菜单中各菜单项标题的数组。

(2)第二个参数,菜单项单击事件。

提示:OnClickListener接口在android.content.DialogInterface包下,与按钮等控件的OnClickListener接口名相同,但所在包不同。

13.5.3.示例

以下代码创建并显示图-3,并将被单击的列表项显示出来

//从数组资源中解析获得存放星期的数组

weekDay=getResources().getStringArray(R.array.weekDay);

//创建对话框

Builderdialog=newAlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("列表对话框");//设置标题

dialog.setItems(weekDay,newandroid.content.DialogInterface.OnClickListener(){

@Override

publicvoidonClick(DialogInterfacedialog,intwhich){

Toast.makeText(mContext,weekDay[which],3000).show();

}

});

dialog.show();//显示对话框

13.6.单选列表对话框

13.6.1.概述

AlertDialog提供了单选列表风格的对话框,如图-4所示:

图-4

13.6.2.重要方法

BuildersetSingleChoiceItems(CharSequence[]items,intcheckedItem,

finalOnClickListenerlistener)

1、作用:设置单选列表对话框,并返回AlertDialog.Builder类的对象。

2、参数说明

(1)第一个参数:存放菜单项的字符串数组。

(2)第二个参数:设置默认被选中的菜单项的索引值。如图-4中第一个菜单项默认被选中,该值设置为0.

3、第三个参数:菜单项单击事件。

13.6.3.示例

以下代码创建并显示图-4,并将被单击的列表项显示出来

weekDay=getResources().getStringArray(R.array.weekDay);

//创建对话框

Builderdialog=newAlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("单选列表对话框");

dialog.setSingleChoiceItems(weekDay,0,

newDialogInterface.OnClickListener(){

@Override

publicvoidonClick(DialogInterfacedialog,intwhich){

Toast.makeText(mContext,weekDay[which],3000).show();

}

});

dialog.show();//显示对话框

13.7.复选列表对话框

13.7.1.概述

AlertDialog提供了复选列表风格的对话框,如图-5所示:

图-5

13.7.2.重要方法

BuildersetMultiChoiceItems(CharSequence[]items,boolean[]checkedItems,

finalOnMultiChoiceClickListenerlistener)

1、作用:创建并设置复选列表对话框。

2、参数说明:

(1)第一个参数:存放列表项标题的字符串数组。

(2)第二个参数:默认选中/未选中的布尔数组。例如图-5默认第一、第三个列表项被选中,则checkedItems数组被定义为如下格式:

BooleancheckedItems={true,false,true,false,false,false,false};

(3)第三个参数:复选列表的单击事件,实现OnMultiChoiceClickListener接口时,要实现接口中定义的OnClick方法。

(4)OnMultiChoiceClickListener.onClick()方法声明为如下格式:

onClick(DialogInterfacedialog,intwhich,booleanisChecked)

第一个参数:当前的对话框;

第二个参数:被单击的列表项的索引值。

第三个参数:该列表项是否被选中,选中值为true,否则为false

13.7.3.示例

以下代码创建并显示图-5的对话框并响应单击事件,将被单击的列表项显示出来:

weekDay=getResources().getStringArray(R.array.weekDay);

//创建对话框

Builderdialog=newAlertDialog.Builder(this);

dialog.setIcon(R.drawable.icon);

dialog.setTitle("复选列表对话框");

dialog.setMultiChoiceItems(weekDay,checkedItems,

newDialogInterface.OnMultiChoiceClickListener(){

@Override

publicvoidonClick(DialogInterfacedialog,intwhich,

booleanisChecked){

Toast.makeText(mContext,weekDay[which],3000).show();

}

});

dialog.show();//显示对话框

13.8.常见问题

现象:在成员变量中初始化UI,得到的组件引用为空,运行时出现空指针异常的提示。

原因:成员变量初始化在构造方法之前,

例如:

Buttonbutton=(Button)findViewById(R.id.button1);

setContentView(R.layout.main);

这时还没有执行setContentView()方法,所以得不到组件的引用,button为空

解决方法:

findViewById()方法要放在setContentView()之后执行


更多相关文章

  1. Android各框架的总结及选型
  2. SpringBoot webSocket 发送广播、点对点消息,Android接收
  3. Android消息机制——Handler
  4. android Fragments详解四:管理fragment
  5. Android学习笔记-Intent(一)
  6. Android系统 应用图标显示未读消息数(BadgeNumber) 桌面app图标
  7. android如何查找安装的apk app的包名和Activity
  8. php如何解析IOS/Android上传的Json消息
  9. Android消息机制系列(2)——Handler源码解析及用法实例

随机推荐

  1. [Android1.5]Android2.0版本以下Activity
  2. android--------根据文件路径加载指定文
  3. Android中自定义ProgressDialog实现加载
  4. TouchSlop的初步认识(Android(安卓)判断
  5. android API文档下载地址
  6. android studio 3.0配置自定义打包名称
  7. [Java][Android][Process] ProcessBuilde
  8. Android学习笔记(四十):Preference的使用
  9. [置顶] android textview 首行缩进 多行
  10. Android x86用来做模拟器调试