2018年03月24日 19:09:52 Chin_style 阅读数:1654 标签: 线程阻塞优化HandlerAsyncTask异步任务 更多
个人分类: 开发要求-线程
版权声明:因为个人水平有限,文章中可能会出现错误,如果你觉得有描述不当、代码错误等内容或者有更好的实现方式,欢迎在评论区告诉我,即刻回复!最后,欢迎关注博主!谢谢 https://blog.csdn.net/weixin_41101173/article/details/79680643
一、前期基础知识储备
当一个应用程序启动之后,android系统会为这个应用程序创建一个主线程(Main Thread),它负责渲染视图,分发事件到响应监听器并执行,对界面进行轮询的监听。因此,一般也叫做“UI线程”(UI Thread)。
android系统不会给应用程序的多个元素组件建立多个线程来执行。一个视图(Activity)中的多个view组件运行在同一个UI线程当中。因此,多个view组件的监听器的执行可能会相互影响。
如果在UI线程中做一些比较耗时的操作,比如访问网络或者数据库,都可能阻塞UI线程,导致时间停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程阻塞时间太长(超过5秒),android系统会弹出ANR(application not responding)错误提示框。
代码如下:
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//设置其耗时操作
try {
Thread.sleep(5000); //5秒- 为Activity不响应报ANR错误的时间
} catch (InterruptedException e) {
Log.i("线程沉睡",e.getMessage());
}
int sum=10;
btn.setText("计数:"+sum);
}
});
点击按钮之后,会发现界面卡死,这时就出现了UI阻塞。
二、Android中如何处理耗时操作
上官方文档:
There are basically two main ways of having a Thread execute application code. One is providing a new class that extends Thread and overriding its run() method. The other is providing a new Thread instance with a Runnable object during its creation. In both cases, the start() method must be called to actually execute the new Thread.
官方文档中对于耗时操作提出的两点必须遵守的开发规则:
①不要阻塞UI线程—即不要再UI线程中执行耗时操作;
② 不要在UI线程之外的其他线程中,对视图中的组件进行设置。
上述代码正确的写法:
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
//设置其耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Log.i("耗时操作",e.getMessage());
}
int sum=10;
btn.setText("计数:"+sum);
}
}).start();
运行结果,依然报错,我们已经开启支线程处理耗时操作,符合了第一条原则,但是,第二条“任何和UI界面相关的操作都应该放在主线程中完成”没有遵守。
Android对于这种情况,提供了两种方法,从支线程中回到主线程:
1)通过View.post(Runnable)方法;
view.post(new Runnable() { //使用View.post(Runnable)进行组件设置
@Override
public void run() {
//在这里进行UI操作,将结果显示在界面上
btn.setText("计数:"+sum)
)};
2)通过runOnUIThread(Runnable)方法。
runOnUIThread(new Runnable(){
@Override
public void run() {
//在这里进行UI操作,将结果显示在界面上
btn.setText("计数:"+sum)
)};
这种解决方法中,UI线程与我们新建的线程之间的关系类似于生产者与消费者之间的关系,新线程通过View.post(Runnable)和runOnUIThread(Runnable)方法在任务队列中加入任务,而UI线程对任务队列进行轮询,有任务的话就拿出来执行,修改界面。
但这种解决方法可读性和维护性较差,适用于切换线程较少的场景。
三、Android中执行耗时操作和返回主线程最好的实现方式
即为Android中实现异步任务的方式:
(1)Handler类,在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
(2)AsyncTask类,Android从1.5版本之后引入,使用它就可以非常灵活方便地从子线程切换到UI线程。
上代码,具体实现:
—————————————————---我是Handler类分隔线---————————————————
①不同的平台提供了不同的解决方案以实现跨线程跟新UI控件,Android为了解决这种问题引入了Handler机制。Handler类提供了两种方式解决我们在本文一开始遇到的问题(在一个新线程中更新主线程中的UI控件),一种是通过post方法,一种是调用sendMessage方法。
Post()方法:
class DownloadThread extends Thread{
@Override
public void run() {
try{
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
MainActivity.this.statusTextView.setText("文件下载完成");
}
};
uiHandler.post(runnable);//post()在支线程中完成UI操作
}catch (InterruptedException e){
e.printStackTrace();
}
};
sendMessage()方法:
class DownloadThread extends Thread{
@Override
public void run() {
try{
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
//文件下载完成后更新UI
Message msg = new Message();
msg.what = 1;
msg.arg1 = 123;
msg.arg2 = 321;
/ /将该Message发送给对应的Handler
uiHandler.sendMessage(msg);//注意此时还是在支线程中
}catch (InterruptedException e){
e.printStackTrace();
}
};
//uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler(){
Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("文件下载完成");
break;
}
}
};
注意:sendMessage()方法中,设置Message的what值
Message.what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别出不同的Message,以便我们做出不同的处理操作。 设置Message的所携带的数据,简单数据可以通过两个int类型的field arg1和arg2来赋值,并可以在handleMessage中读取。 如果Message需要携带复杂的数据,那么可以设置Message的obj字段,obj是Object类型,可以赋予任意类型的数据。
—————————————————---我是AsyncTask类分隔线---————————————————
②AsyncTask类,异步任务,从字面上说,就是我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们执行一个异步任务在后台。我们可以将耗时操作放在异步任务当中执行,并随时将任务执行的结果返回给我们的UI主线程来更新我们的UI控件。通过ASyncTask我们就可以轻松的解决多线程之间的通信问题。
private class task extends AsyncTask
@Override
protected String doInBackground(String... params) {
try {
Thread.sleep(5000);// 执行耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return "通过AsyncTask设置";
}
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
};
在activity中建一个内部类,继承AsyncTask,重写doInBackground()和onPostExecute()方法,doInBackground的返回值会做为onPostExecute的参数执行。然后我们只需要在需要调用的时候new task().execute()就可以执行了。
四、两种异步更新UI方法的对比:
①Handler类:
优点:结构清晰,功能定义明确,对于后台多个任务时,简单清晰;
缺点:在单个后台异步处理时,显得代码过多,结构过于复杂(相对性);
②AsyncTask类:
优点:简单快捷过程可控的轻量级异步类;
缺点:在使用多个异步操作和并需要UI变更时,就变得复杂起来;
总结:二者各有优劣,有各自对应的开发场景,开发者都需要掌握。
版权声明:本文为博主原创文章,未经博主允许不得转载。如果觉得文章不错,点个赞呗!