在Android中,我们绘制图形界面的线程即是主线程,也叫UI线程。由于在主线程中进行过于耗时的操作(Activity超过5秒,BroadCast超过10秒)会导致ANR(Application Not Responding,应用程序无响应),而且Android4.0以后也规定,不允许在主线程中进行网络操作(耗时操作),因此对于耗时操作我们并需在新开启的线程中执行,并通过线程间的通信机制在主线程中更新UI。以下总结了几种多线程编程的方法:
1.开启新线程,使用handler通信
这是界面的xml代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"
tools:context="com.example.test.MyActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1+1 = "
android:textSize="24sp" />
<TextView
android:id="@+id/ans"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\?"
android:textSize="24sp" />
</LinearLayout>
布局只有一行文字,非常简单。
再来是Activity的代码:
package com.example.test;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity{
// 使用handler实现线程间通信
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
tv.setText(msg.arg1+"");
};
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 使用匿名类开启新线程
new Thread(new Runnable() {
@Override
public void run() {
// 模拟耗时操作,等待3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
// 使用handler实现线程间通信
// 发送消息
Message message = Message.obtain();
message.arg1 = 2;
handler.sendMessage(message);
// post Runnable也可以
// handler.post(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
// runOnUiThread也行
// MyActivity.this.runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
}
}).start();
}
}
在上面我们开启了一个子线程来处理延时事件(让线程休眠模拟延时),并使用了handler发送消息来与主线程通信,在主线程中更新UI界面。我们还可以使用handler.post和Activity的runOnUiThread来传入Runnable参数更新UI界面,但其实两者都是使用handler发送消息并添加回调函数来实现的。
效果图如下:
2.Executor
除了上面手动新建线程来进行多线程编程,我们还可以使用JavaSE5为我们提供的Executor(执行器),他将替我们管理Thread对象,简化了我们的多线程编程。代码如下:
package com.example.test;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity {
// 使用handler实现线程间通信
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
tv.setText(msg.arg1 + "");
};
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 使用线程池来开启新线程
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
@Override
public void run() {
// 模拟耗时操作,等待3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
}
;
// 使用handler实现线程间通信
// 发送消息
Message message = Message.obtain();
message.arg1 = 2;
handler.sendMessage(message);
// post Runnable也可以
// handler.post(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
// runOnUiThread也行
// MyActivity.this.runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// tv.setText("2");
// }
// });
}
});
}
}
上面使用了CachedThreadPool线程池,他会在执行过程中创建与所需数目相同的线程,然后会回收不使用的旧线程而非创建新线程。除此以外Java还提供了别的多种线程池,在此不一一描述。使用Executor会更便于我们管理线程,而且在线程数较多的时候也能使代码看起来更优雅。
由于效果图一样,在此就不再贴图。
3.AsyncTask
AsyncTask是Android提供的异步任务类,本质是用Java线程池改造的。以下是Java代码:
package com.example.test;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
public class MyActivity extends Activity {
// 使用AsyncTask实现多线程编程
private AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
// UI线程预处理
}
@Override
protected Void doInBackground(Void... params) {
// 线程休眠3秒模拟耗时操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
// doInBackground调用publishProgress(values)时调用该方法;
// 该方法处于UI线程,可以更新UI!
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// 处理结果
tv.setText("2");
}
};
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
// 必须在UI线程使用
task.execute();
}
}
AsyncTask的三个泛型参数分别代表:处理时传给Task的参数类型,打印过程时所需数据的参数类型,返回结果的类型,如果不需要该参数只要填写Void即可。AsyncTask需要重写几个关键的方法,在代码中已有注释在此不再赘述。值的注意的是,同一个AsyncTask不可execute多次,否则会发生java.lang.IllegalStateException: Cannot execute task: the task is already running.的错误。如果你想开启多个新线程,应该考虑继承AsyncTask类并创建多个实例。同时AsyncTask还存在着些许奇奇怪怪的问题,本文在此也不做深入探究。
4.使用Loader类
Android在3.0以后,SDK提供了Loader技术,使用Loader技术可以很容易进行数据的异步加载。Loader技术为我们提供的核心类有:
- LoaderManager:可以通过Activity或者的Fragment的getLoaderManager()方法得到LoaderManager,用来对Loader进行管理,一个Activity或者Fragment只能有一个LoaderManager。
- LoaderManager.LoaderCallbacks:用于同LoaderManager进行交互,可以在其中创建Loader对象。
- AsyncTaskLoader:抽象类,可以进行异步加载数据的Loader,貌似内部也是通过AsynTask实现的,可以通过继承它构建自己的Loader,也可以使用现有的子类,例如异步查询数据库可以使用CursorLoader。
以下是我自己重写Loader的例子:
package com.example.test;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MyActivity extends Activity {
private static String TAG = "MyActivity";
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.ans);
getLoaderManager().initLoader(0, null, new MyLoaderCallbacks());
}
private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks<Integer> {
@Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
Log.v(TAG, "onCreateLoader");
// UI预处理
return new MyLoader(getApplicationContext());
}
@Override
public void onLoaderReset(Loader<Integer> loader) {
Log.v(TAG, "onLoaderReset");
// 处理Loader被reset的情况,在此需要清除掉对上一个data的引用
}
@Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
Log.v(TAG, "onLoadFinished");
// 处理UI结果
tv.setText(data+"");
}
}
// 必须是静态类,否则会有RuntimeException
private static class MyLoader extends AsyncTaskLoader<Integer> {
public MyLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
Log.v(TAG, "onStartLoading");
// 调用forceLoad来开启新线程执行任务
forceLoad();
}
@Override
public Integer loadInBackground() {
Log.v(TAG, "loadInBackground");
// 模拟耗时操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {};
return 2;
}
}
}
值的注意的有几个地方:
- LoaderCallbacks的泛型代表处理结果的返回类型
- 自己写的AsyncTaskLoader的子类必须为静态类
- 必须在自己写的Loader中调用forceLoad方法,否则Loader不会开启新线程执行操作
与AsyncTask相比,Loader有了更多处理上的优化,比如加载被中断后自动保存现场等,本文在此不做深入探讨。
综上所述,在Android中实现多线程编程的方法有:
- 手动新建线程,使用Handler进行通信
- 使用线程池新建线程,使用Handler进行通信
- 使用Android提供的AsyncTask工具类
- 使用Android提供的Loader工具类