引言
众所周知,无论是在任何的程序语言和操作系统中。多线程、多进程和异步同步始终都是经久不衰的话题。当然在我们实际的Android项目需求中也是如此,很多的业务需求都通过多线程及异步任务以便用户能够在使用App中得到优秀的体验。而很多App在使用过程中出现各种莫名其妙的问题,多是由于开发人员使用多线程不当造成的,因此掌握在多线程及异步任务的原理和使用方法非常有必要。
一Android UI主线程设计原则
在开始总结多线程前,先讲下UI 线程(主线程)的一个基本原则——不要Block UI Thread;不要在UI线程外直接操作UI,每一个App运行之时,Android系统会自动为每一个App创建一个线程即主线程,只能在主线程(UI线程)中操作UI,这是因为在Android源码在线阅读中并没有对UI操作部分做线程同步处理,如果在非UI(非主线程)中操作UI就会导致线程安全问题,所以在非UI线程中操作UI运行时直接报错了。
二使用多线程的意义
我们在开发的过程中,很多业务需求都是非常耗时的,比如说IO操作、网络访问、数据库操作、上传下载文件等等,如果我们全部都放到主线程中去执行就会可能导致主线程阻塞,用户在使用APP的过程中就会产生卡顿的不良体验,自然对于APP满意度下降。为了给用户以最优秀的体验,前辈建议对于超过50ms(因为1000ms/50ms=20fps刚好是人眼的能感受到的最大值)的操作,都应该使用多线程去处理,才不至于给用户以卡顿的感受。
三使用多线程的方式
1、和Java的一样扩展java.lang.Thread类,即new 一个线程对象把run()方法写到线程里面
new Thread(new Runnable(){
@Override
public void run() {
//在这里做耗时操作
});
}
}).start();
2、实现Runnable接口,让Activity类实现Runnable接口,然后把run方法单独提出来:
public class MutilThreadActivity extends Activity implements Runnable {
@Override
public void run() {
//在这里做耗时操作
}
}
3、利用线程池ExecutorService接口创建多线程
3.1 使用步骤
3.1.1利用Executors的静态方法newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor()及重载形式实例化ExecutorService接口即得到线程池对象
- 动态线程池newCachedThreadPool() 是根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM自己会慢慢的释放掉多余的线程
- 固定数量的线程池newFixedThreadPool()内部有个任务阻塞队列,假设线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务阻塞队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的
- 单线程newSingleThreadExecutor()返回一个线程池(不过这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool();
3.1.2 创建ExecutorService对象之后,然后执行提交submit,可以发起一个Runnable对象。
service.submit(new Runnable(){
@Override
public void run() {
//
}
});
3.2完整使用线程池的代码片段
private ExecutorService service =Executors.newFixedThreadPool(6);
private void testByExecutors(){
service.submit(new Runnable(){
@Override
public void run() {
//在这做耗时操作
}
});
}
3.3 线程池的优势
比如说现在我们要展示800张图片如果创建800个线程去加载,保证系统会死掉。用线程池就可以避免这个问题,我们可以差创建用6个线程轮流执行,6个一组,执行完的线程不直接回收而是等待下次执行,这样对系统的开销就可以减小不少。所以用线程池来管理的好处是,可以保证系统稳定运行,适用与有大量线程,高工作量的情景下使用。
4 异步任务AsyncTask
四异步任务AsyncTask
在Android中实现异步任务机制有两种方式,Handler和AsyncTask。在这里先总结下AsyncTask
1 AsyncTask概述
AsyncTask主要用于后台与界面持续交互的,AsyncTask是个抽象类,使用时需要继承这个类,(把耗时的后台操作放到doInBackgound() 方法里,在onPostExecute()中完成UI操作),然后调用execute()方法。注意继承时需要设定三个泛型Params,Progress和Result的类型,其中:
- Params是指调用execute()方法时传入的参数类型和doInBackgound()的参数类型
- Progress是指更新进度时传递的参数类型,即publishProgress()和onProgressUpdate()的参数类型
- Result是指doInBackground()的返回值类型
2几个常用方法
- doInBackgound() 这个方法是继承AsyncTask必须要实现的,运行于后台,耗时的操作可以在这里做
- onPostExecute 在主线程中运行,可以用来写一些开始提示代码。
- publishProgress() 更新进度,给onProgressUpdate()传递进度参数
- onProgressUpdate() 在publishProgress()调用完被调用,更新进度
3 AsyncTask运行机制
1. 主线程调用AsynTask子类实例的execute()方法后,首先会调用onPreExecute()方法。onPreExecute()在主线程中运行,可以用来写一些开始提示代码。
2. 之后启动新线程,调用doInBackground()方法,进行异步数据处理。
3. 处理完毕之后异步线程结束,在主线程中调用onPostExecute()方法。onPostExecute()可以进行一些结束提示处理。
补充:在doInBackground()方法异步处理的时候,如果希望通知主线程一些数据(如:处理进度)。这时,可以调用publishProgress()方法。这时,主线程会调用AsynTask子类的onProgressUpdate()方法进行处理。
4. 各个函数间数据的传递通过上面的调用关系,我们就可以大概看出一些数据传递关系。如下:
execute()向doInBackground()传递。
doInBackground()的返回值会传递给onPostExecute()。
publishProgress()向progressUpdate()传递。
5. Android为了调用关系明确及安全,AsynTask类在继承时要传入3个泛型。第一个泛型对应execute()向doInBackground()的传递类型。第二个泛型对应doInBackground()的返回类型和传递给onPostExecute()的类型。第三个泛型对应publishProgress()向progressUpdate()传递的类型。传递的数据都是对应类型的数组,数组都是可变长的。可以根据具体情况使用。
4 异步的简单应用
结合WebView通过异步加载指定网页
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_width="match_parent"
/>
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00f"
android:layout_gravity="center"
/>
<TextView android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/load_web"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加载指定网页"/>
</LinearLayout>
package cmo.learn.activity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
public class HandlerActivity extends Activity implements OnClickListener {
private Button mUpdProgressBtn;
private EditText mTitleEdt;
private ProgressBar mProgressBar;
private TextView mContentTxt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_asynctask);
init();
}
private void init(){
getView();
mTitleEdt.setText("http://www.hao123.com");
mUpdProgressBtn.setOnClickListener(this);
}
private void getView(){
mTitleEdt=(EditText) findViewById(R.id.title);
mProgressBar=(ProgressBar) findViewById(R.id.progressbar);
mUpdProgressBtn=(Button) findViewById(R.id.load_web);
mContentTxt=(TextView) findViewById(R.id.content);
}
//异步加载网页内容
class WebGetAsyncTask extends AsyncTask<String, Integer, String>{
@Override
protected String doInBackground(String... params) {
try {
HttpClient client=new DefaultHttpClient();
HttpGet get=new HttpGet(params[0]);
HttpResponse response=client.execute(get);
HttpEntity entity=response.getEntity();
long length=entity.getContentLength();
InputStream inStream=entity.getContent();
String s=null;
int toCase=0;
if(inStream !=null){
ByteArrayOutputStream boas=new ByteArrayOutputStream();
byte[] buf=new byte[128];
int ch=-1;
int count=0;
while((ch=inStream.read(buf))!=-1){
boas.write(buf, 0, ch);
count +=ch;
if(length>0){
toCase=(int)((count/(float)length)*100);
publishProgress(toCase);
}
Thread.sleep(100);
}
s=new String(boas.toByteArray());
return s;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
mContentTxt.setText(result);
}
@Override
protected void onProgressUpdate(Integer... values) {
mProgressBar.setProgress(values[0]);
}
}
@Override
public void onClick(View v) {
new WebGetAsyncTask().execute(mTitleEdt.getText().toString());
}
}
5 使用异步必须遵守的准则
- AsyncTask的实例必须在UI Thread中创建
- execute方法必须在主线程(UI Thread)中调用
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…),
onProgressUpdate(Progress…)这几个方法 - 该AsyncTask实例只能被执行一次,否则多次调用时将会出现异常
6AsyncTask小结
初识这个异步调用关系可能觉得很复杂,但其实熟悉了之后会发现这种结构很好用。这种结构将所有的线程通信都封装成回调函数,调用逻辑容易书写。尤其是在异步处理结束之后,有回调函数进行收尾处理。如果是使用Thread的run()方法,run()结束之后没有返回值。所以必须要自己建立通信机制。但是,其实使用Handler+Thread机制其实完全可以替代AsynTask的这种调用机制。只要将Handler对象传给Thread,就可以进行方便的异步处理。个人经验,Handler+Thread适合进行大框架的异步处理,而AsynTask适用于小型简单的异步处理。仅仅代表个人观点和见解。
五一个综合的例子
布局文件很简单
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
/>
<Button
android:id="@+id/thread"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load and set in Main Thread"/>
<Button
android:id="@+id/thread2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load and set in Thread"/>
<Button
android:id="@+id/thread3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load in Thread,set by View.post(Runnable)"/>
<Button
android:id="@+id/thread4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load in Thread,set by AsyncTask"/>
</LinearLayout>
package cmo.learn.activity;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class MutilThreadActivity extends Activity {
/**
*
* Android UI主线程简单原则:不要Block UI Thread;不要在UI线程外直接操作UI
*
*/
private Button mMainThreadBtn;
private Button mThread2Btn;
private Button mThread3Btn;
private Button mThread4Btn;
private ImageView mImg;
private final static String IMAGE_URL="http://www.lhzhang.org/image.axd?pictrue=/201102/46613566.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mutilthread);
init();
//1 在主线程中加载图片到Image中
mMainThreadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Drawable drawable=loadImageFromNet(IMAGE_URL,"Main Thread");
mImg.setImageDrawable(drawable);
}
});
//2 在Thread子线程中加载到ImageView,但是会有线程安全问题,直接报错
mThread2Btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable(){
@Override
public void run() {
Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread");
mImg.setImageDrawable(drawable);
}
}).start();
}
});
//3 加载图片在子线程,把是通过View.post(Runnable)设置图片到ImageView
mThread3Btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//在子线程中从网络中加载Image
new Thread(new Runnable(){
@Override
public void run() {
final Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread By Post");
//通过post把set操作放到了UI线程
mImg.post(new Runnable(){
@Override
public void run() {
mImg.setImageDrawable(drawable);
}
});
}
}).start();
}
});
//4加载图片 在子线程中,通过异步AsyncTask设置到ImageView
mThread4Btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new LoadImgAsyncTask().execute(IMAGE_URL);
}
});
}
private void init(){
getView();
}
private void getView(){
mImg=(ImageView) findViewById(R.id.img);
mMainThreadBtn=(Button) findViewById(R.id.thread);
mThread2Btn=(Button) findViewById(R.id.thread2);
mThread3Btn=(Button) findViewById(R.id.thread3);
mThread4Btn=(Button) findViewById(R.id.thread4);
}
private Drawable loadImageFromNet(String imageUrl,String tag){
Drawable drawable=null;
try{
drawable=Drawable.createFromStream(new URL(imageUrl).openStream(), "img_1.png");
}catch(Exception e){
}
if(drawable==null){
Log.d(tag, "null drawable");
}else{
Log.d(tag, "not null drawable");
}
return drawable;
}
//在后台操作获取数据,再把操作数据集返回到前台进行一些UI更新操作
private class LoadImgAsyncTask extends AsyncTask<String, Void, Drawable>{
//在工作线程中完成获得Image,并把返回结果传递到AsyncTask.execute()
@Override
protected Drawable doInBackground(String... params) {
return loadImageFromNet(IMAGE_URL, "Thread Runnable AsyncTask");
}
//把doInBackGround的结果接收并作为参数传递到UI线程中实现设置到ImageView
@Override
protected void onPostExecute(Drawable result) {
mImg.setImageDrawable(result);
}
}
}