[置顶] android 深入理解AnsyncTask
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()方法
1、sendEmptyMessage(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类中的post和postDelayed方法,这两个方法通过发送一个消息至主线程的消息队列(其中该消息的第二参数是一个实现了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通过Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。
Android系统中Looper负责管理线程的消息队列(messagequeue)和消息循环。
13.2.3.2.重要方法
1、Looper.prepare():
作用:创建Looper对象和MessageQueue(消息队列)对象。
2、Looper.getMainLooper();
作用:获得主线程的Looper对象。
3、Looper.myLooper();
作用:获得当前工作线程的Looper对象。
4、Looper.loop();
作用:进入消息循环。
13.2.3.3.Looper运行机制
Looper负责管理线程的消息队列和消息循环,通过Loop.myLooper()得到当前线程的Looper对象,通过Loop.getMainLooper()获得主线程的Looper对象。Looper不能用new来实例化,而是要用Looper.prepare()实例化。
Android中每一个线程都可以(但不是必须)跟着一个Looper,Looper可以帮助线程维护一个消息队列,每个线程可以存在一个消息队列和一个消息循环(Looper),特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具有消息队列和消息循环,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。如下例所示
Looper.prepare();//创建消息队列
myLooper=Looper.myLooper();//获得当前线程中的Looper对象
Looper.loop();//进入当前线程的消息循环
一个Activity中可以创建多个工作线程,如果这些线程把它们的消息放入主线程消息队列,那么该消息就会在主线程中处理了。因为主线程一般负责界面的更新操作,所以这种方式可以很好的实现Android界面更新。Activity、Looper、Handler的关系如下图所示
其它线程通过Handle对象将消息放入主线程的消息队列。只要Handler对象以主线程的Looper创建,那么调用Handler的sendMessage等方法,会把消息放入主线程的消息队列。并且Handler将会在主线程中调用handleMessage方法来处理消息。
13.2.3.4.示例
创建一个Thread类线程,该线程具有消息队列和处理消息队列的Looper:
classLooperThreadextendsThread{
publicHandlermHandler;
publicvoidrun(){
Looper.prepare();
mHandler=newHandler(){
publicvoidhandleMessage(Messagemsg){
/*processincomingmessageshere*/
};
Looper.loop();
不管是主线程还是工作线程,只要有Looper的线程,别的线程就可以向这个线程的消息队列中发送消息和计划任务,然后做相应的处理。
13.2.4.Handler、Message、MessageQueue和Looper四者关系
Looper对象由Looper.prepare()方法来创建,该方法同时会创建MessageQueue,该消息队列中的Message由Handler发送至主线程的消息队列,由主线程的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不一定都在主线程中创建,也可以在工作线程中创建。如果构造参数为空,那么在哪个线程中new的Handler,则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对象,通过该参数将mHandler与mThread关联起来。
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()之后执行
更多相关文章
- Android各框架的总结及选型
- SpringBoot webSocket 发送广播、点对点消息,Android接收
- Android消息机制——Handler
- android Fragments详解四:管理fragment
- Android学习笔记-Intent(一)
- Android系统 应用图标显示未读消息数(BadgeNumber) 桌面app图标
- android如何查找安装的apk app的包名和Activity
- php如何解析IOS/Android上传的Json消息
- Android消息机制系列(2)——Handler源码解析及用法实例