众所周知,无论是在任何的程序语言和操作系统中。多线程、多进程和异步同步始终都是经久不衰的话题。当然在我们实际的Android项目需求中也是如此,很多的业务需求都通过多线程及异步任务以便用户能够在使用App中得到优秀的体验。而很多App在使用过程中出现各种莫名其妙的问题,多是由于开发人员使用多线程不当造成的,因此掌握在多线程及异步任务的原理和使用方法非常有必要。
在开始总结多线程前,先讲下UI 线程(主线程)的一个基本原则——不要Block UI Thread;不要在UI线程外直接操作UI,每一个App运行之时,Android系统会自动为每一个App创建一个线程即主线程,只能在主线程(UI线程)中操作UI,这是因为在Android源码在线阅读中并没有对UI操作部分做线程同步处理,如果在非UI(非主线程)中操作UI就会导致线程安全问题,所以在非UI线程中操作UI运行时直接报错了。
我们在开发的过程中,很多业务需求都是非常耗时的,比如说IO操作、网络访问、数据库操作、上传下载文件等等,如果我们全部都放到主线程中去执行就会可能导致主线程阻塞,用户在使用APP的过程中就会产生卡顿的不良体验,自然对于APP满意度下降。为了给用户以最优秀的体验,前辈建议对于超过50ms(因为1000ms/50ms=20fps刚好是人眼的能感受到的最大值)的操作,都应该使用多线程去处理,才不至于给用户以卡顿的感受。
new Thread(new Runnable(){
@Override
public void run() {
//在这里做耗时操作
});
}
}).start();
public class MutilThreadActivity extends Activity implements Runnable {
@Override
public void run() {
//在这里做耗时操作
}
}
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool();
service.submit(new Runnable(){
@Override
public void run() {
//
}
});
private ExecutorService service =Executors.newFixedThreadPool(6);
private void testByExecutors(){
service.submit(new Runnable(){
@Override
public void run() {
//在这做耗时操作
}
});
}
比如说现在我们要展示800张图片如果创建800个线程去加载,保证系统会死掉。用线程池就可以避免这个问题,我们可以差创建用6个线程轮流执行,6个一组,执行完的线程不直接回收而是等待下次执行,这样对系统的开销就可以减小不少。所以用线程池来管理的好处是,可以保证系统稳定运行,适用与有大量线程,高工作量的情景下使用。
在Android中实现异步任务机制有两种方式,Handler和AsyncTask。在这里先总结下AsyncTask
AsyncTask主要用于后台与界面持续交互的,AsyncTask是个抽象类,使用时需要继承这个类,(把耗时的后台操作放到doInBackgound() 方法里,在onPostExecute()中完成UI操作),然后调用execute()方法。注意继承时需要设定三个泛型Params,Progress和Result的类型,其中:
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()传递的类型。传递的数据都是对应类型的数组,数组都是可变长的。可以根据具体情况使用。
结合WebView通过异步加载指定网页
<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{
@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());
}
}
初识这个异步调用关系可能觉得很复杂,但其实熟悉了之后会发现这种结构很好用。这种结构将所有的线程通信都封装成回调函数,调用逻辑容易书写。尤其是在异步处理结束之后,有回调函数进行收尾处理。如果是使用Thread的run()方法,run()结束之后没有返回值。所以必须要自己建立通信机制。但是,其实使用Handler+Thread机制其实完全可以替代AsynTask的这种调用机制。只要将Handler对象传给Thread,就可以进行方便的异步处理。个人经验,Handler+Thread适合进行大框架的异步处理,而AsynTask适用于小型简单的异步处理。仅仅代表个人观点和见解。
布局文件很简单
<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{
//在工作线程中完成获得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);
}
}
}