引言

众所周知,无论是在任何的程序语言和操作系统中,多线程、多进程和异步同步始终都是经久不衰的话题。当然在我们实际的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"/>

android:id="@+id/load_web"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="加载指定网页"/>

```

这段代码是一个名为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"/>

android:id="@+id/thread"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Load and set in Main Thread"/>

android:id="@+id/thread2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Load and set in Thread"/>

android:id="@+id/thread3"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Load in Thread,set by View.post(Runnable)"/>

android:id="@+id/thread4"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Load in Thread,set by AsyncTask"/>

```

```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);

}

}

}

```