引言
众所周知,无论是在任何的程序语言和操作系统中,多线程、多进程和异步同步始终都是经久不衰的话题。当然在我们实际的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()方法写到线程里面:
```java
new Thread(new Runnable(){
@Override
public void run() { //在这里做耗时操作
}
}).start();
```
2. 实现Runnable接口,让Activity类实现Runnable接口,然后把run方法单独提出来:
```java
public class MyActivity extends Activity implements Runnable {
@Override
public void run() {
//在这里做耗时操作
}
}
```
以下是根据您提供的内容重构的代码:
```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadActivity extends Activity implements Runnable {
@Override
public void run() {
// 在这里执行耗时操作
}
}
// 利用线程池ExecutorService接口创建多线程
// 3.1 使用步骤
// 3.1.1 利用Executors的静态方法 newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor() 及重载形式实例化ExecutorService接口即得到线程池对象
// 动态线程池 newCachedThreadPool():根据需求创建新线程,需求多时,创建的就多,需求少时,JVM自己会慢慢的释放掉多余的线程
// 固定数量的线程池 newFixedThreadPool():内部有个任务阻塞队列,假设线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务阻塞队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的
// 单线程 newSingleThreadExecutor():返回一个线程池(不过这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
ExecutorService service = Executors.newFixedThreadPool(3); // 创建一个固定数量为3的线程池
service.submit(new MultiThreadActivity()); // 提交一个Runnable对象到线程池中执行
```
线程池的优势在于可以避免创建过多线程导致系统崩溃的问题。例如,如果要展示800张图片,如果创建800个线程去加载,系统可能会崩溃。而使用线程池,我们可以创建6个线程轮流执行,每6个线程一组。执行完的线程不会立即回收,而是等待下次执行。这样可以减小系统的开销,保证系统稳定运行。线程池适用于有大量线程、高工作量的情景下使用。
在Android中实现异步任务机制有两种方式:Handler和AsyncTask。AsyncTask是一个抽象类,使用时需要继承这个类。将耗时的后台操作放到doInBackground()方法里,在onPostExecute()中完成UI操作。调用execute()方法时需要注意继承时需要设定三个泛型参数:Params、Progress和Result。其中,Params是指调用execute()方法时传入的参数类型和doInBackground()的参数类型;Progress是指更新进度时传递的参数类型,即publishProgress()和onProgressUpdate()的参数类型;Result是指doInBackground()的返回值类型。
几个常用的方法包括:
- doInBackground():这个方法是继承AsyncTask必须要实现的,运行于后台,耗时的操作可以在这里做。
- onPostExecute():在主线程中运行,可以用来写一些开始提示代码。
- publishProgress():更新进度,给onProgressUpdate()传递进度参数。
AsyncTask是Android提供的一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。在使用AsyncTask时,AsyncTask类必须在主线程中加载。
当主线程调用AsynTask子类实例的execute()方法后,首先会调用onPreExecute()方法。onPreExecute()在主线程中运行,可以用来写一些开始提示代码。之后启动新线程,调用doInBackground()方法,进行异步数据处理。处理完毕之后异步线程结束,在主线程中调用onPostExecute()方法。onPostExecute()可以进行一些结束提示处理。补充:在doInBackground()方法异步处理的时候,如果希望通知主线程一些数据(如:处理进度),这时,可以调用publishProgress()方法。这时,主线程会调用AsynTask子类的onProgressUpdate()方法进行处理。
各个函数间数据的传递通过上面的调用关系,我们就可以大概看出一些数据传递关系:execute()向doInBackground()传递;doInBackground()的返回值会传递给onPostExecute();publishProgress()向progressUpdate()传递。
关于WebView结合使用异步加载指定网页的简单应用可以参考这篇文章。
```xml
android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> android:id="@+id/title" android:layout_height="wrap_content" android:layout_width="match_parent"/> android:id="@+id/progressbar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#00f" android:layout_gravity="center"/> android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content"/>
```
这段代码是一个名为HandlerActivity的Android活动,它实现了OnClickListener接口。在这个活动中,有一个按钮、一个文本框、一个进度条和一个文本视图。当用户点击按钮时,将启动一个新的WebGetAsyncTask来异步获取网页内容。这个活动的主要功能是在后台线程中加载网页内容,并在主线程中更新UI。
解析:
1. 创建一个名为HandlerActivity的类,继承自Activity,并实现OnClickListener接口。
2. 在onCreate方法中,初始化界面组件,并调用init方法。
3. init方法中,获取界面组件的引用,并设置按钮的点击事件监听器。
4. 创建一个名为WebGetAsyncTask的内部类,继承自AsyncTask
5. 在doInBackground方法中,使用HttpClient发送HttpGet请求,获取网页内容。
6. onPostExecute方法中,将获取到的网页内容设置为文本视图的内容。
7. onProgressUpdate方法中,更新进度条的进度。
8. 在onClick方法中,执行WebGetAsyncTask并传入要访问的网址。
重构后的代码:
```java
package cmo.learn.activity;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
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();
} finally {
}
return null;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
mContentTxt.setText(result);
}
}
@Override
public void onClick(View v) {
new WebGetAsyncTask().execute(mTitleEdt.getText().toString());
}
}
```
使用异步必须遵守的准则
在Android开发中,使用AsyncTask时需要遵守以下准则:
1. AsyncTask的实例必须在UI Thread中创建。如果在非UI线程(如子线程)中创建AsyncTask实例,将导致程序崩溃。
2. execute方法必须在主线程(UI Thread)中调用。如果在其他线程中调用execute方法,可能导致数据不同步或其他问题。
3. 不要手动调用onPreExecute()、onPostExecute(Result)、doInBackground(Params...)和onProgressUpdate(Progress...)这几个方法。这些方法会自动在相应的时机被调用,如果手动调用可能导致逻辑错误。
4. 该AsyncTask实例只能被执行一次,否则多次调用时将会出现异常。为了避免这种情况,可以在执行完任务后正确地移除任务。
6 AsyncTask小结
初次接触这个异步调用结构可能觉得很复杂,但熟悉了之后会发现这种结构非常实用。它将所有的线程通信都封装成回调函数,使得调用逻辑变得简单易写。特别是在异步处理结束后,有回调函数进行收尾处理。如果是使用Thread的run()方法,run()结束之后没有返回值,所以必须要自己建立通信机制。然而,实际上使用Handler+Thread机制完全可以替代AsynTask的这种调用机制。只需将Handler对象传给Thread,就可以进行方便的异步处理。个人经验认为,Handler+Thread适合进行大型框架的异步处理,而AsyncTask适用于小型简单的异步处理。这仅仅是个人观点和见解。
以下是重构后的代码:
```xml
android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone"/>
```
```java
package cmo.learn.activity;
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 {
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;
}
private class LoadImgAsyncTask extends AsyncTask
@Override
protected Drawable doInBackground(String... params) {
return loadImageFromNet(IMAGE_URL, "Thread Runnable AsyncTask");
}
@Override
protected void onPostExecute(Drawable result) {
mImg.setImageDrawable(result);
}
}
}
```