通常Android运行时只有一个线程,就是UI主线程,负责更新ui,也可以处理一些逻辑工作,但遇到复杂的工作,就不可以直接丢给主线程来处理,不然UI线程就会卡在那,导致系统无响应。
android中多线程实现主要依靠Handler和AsyncTask。
Hander的例子
首先看一下将复杂操作直接放在UI进程的例子.这里的复杂计算是计算圆周率。
布局文件,界面上只有一个非常嘲讽的Button。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginRight="20dp"
android:layout_marginTop="30dp"
android:text="求点我!" />
</RelativeLayout>
然后看MainActivity
package com.empty.threaddemo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button mButton;
private Runnable mRunnable;
private Handler mHandler;
double result;
private int count = 0;
private int i = 0;
public static final int FINISHED= 0x000001;
private static final String TAG = "ThreadDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton=(Button)findViewById(R.id.button1);
mButton.setOnClickListener(new MyButtonListener());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
class MyButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
result=getPI(100000000);
new AlertDialog.Builder(MainActivity.this)
.setTitle("result")
.setMessage(""+result)
.setIcon(android.R.drawable.ic_dialog_info)
.setCancelable(true)
.show();
}
}
public double getPI(int time)
{
float result=0;
for(int i=1;i<=time;i++){
double value=4.0/(2*i-1);
if (i % 2 == 1) result+=value;
else result-=value; }
return result;
}
}
编译,运行,点Button,然后发现被坑了...
点击按钮后,UI线程就被阻塞用于计算PI,由于耗时太久,系统直接无响应了。
下面使用Handler来改进,先复习一下Handler的用法:
1)在Activity或Activity的Widget中开发Handler类的对象,并重写handleMessage方法。
2)在新启动的线程中调用sendEmptyMessage或者sendMe ssage方法向Handler发送消息。
3)Handler类的对象用handleMessage方法接收消息,然后根据消息的不同执行不同的操作。
MainActivity改写之后变成这样:
package com.empty.threaddemo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button mButton;
private Runnable mRunnable;
private Handler mHandler;
double result;
private int count = 0;
private int i = 0;
public static final int FINISHED= 0x000001;
private static final String TAG = "ThreadDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton=(Button)findViewById(R.id.button1);
mButton.setOnClickListener(new MyButtonListener());
mHandler = new Handler()
{
@Override
public void handleMessage(Message msg) {
if (msg.what == FINISHED) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("Result")
.setMessage(""+result)
.setIcon(android.R.drawable.ic_dialog_info)
.setCancelable(true)
.show();
}
super.handleMessage(msg);
}
};
new MyThread().start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
class MyButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("爽!")
.setIcon(android.R.drawable.ic_dialog_info)
.setCancelable(true)
.show();
}
}
public double getPI(int time)
{
float result=0;
for(int i=1;i<=time;i++){
double value=4.0/(2*i-1);
if (i % 2 == 1) result+=value;
else result-=value; }
return result;
}
public class MyThread extends Thread {
public void run() {
Message msg = new Message();
msg.what = FINISHED;
result=getPI(100000000);
mHandler.sendMessage(msg);
}
}
}
程序运行的过程是:当Activity启动的时候,初始化了一些View,同时创建了一个handler用于处理消息,最后启动了一个Thread,计算PI放在了Thread中而不是占用UI线程,所以Button一直可以与用户进行交互。当计算完成的时候,Handler会接受到来自Thread的Message,Handler处理消息,更新UI,即弹出对话框。
使用AsyncTask
先大概认识下Android.os.AsyncTask类:
* android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。
* AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
* Params 启动任务执行的输入参数,比如HTTP请求的URL。
* Progress 后台任务执行的百分比。
* Result 后台执行任务最终返回的结果,比如String,Integer等。
* AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,开发者需要实现这些方法。
* 1) 继承AsyncTask
* 2) 实现AsyncTask中定义的下面一个或几个方法
* onPreExecute(), 该方法将在执行实际的后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。
* doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
* onProgressUpdate(Progress...),在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
* onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.
* onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI 线程中创建
2) execute方法必须在UI 线程中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。
修改上面的例子,用AsyncTask来实现。
修改一下布局文件,添加一个进度条。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ProgressBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/button1"
android:layout_marginTop="20dp" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="26dp"
android:text="独孤求点" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_marginLeft="54dp"
android:layout_toRightOf="@+id/button1"
android:text="TextView" />
</RelativeLayout>
然后是MainActivity.
package com.empty.threaddemo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button mButton;
private Handler mHandler;
private ProgressBar pb;
private TextView resultView;
double result;
private int TIME= 10000;
private int i = 0;
public static final int FINISHED= 0x000001;
private static final String TAG = "ThreadDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton=(Button)findViewById(R.id.button1);
mButton.setOnClickListener(new MyButtonListener());
pb=(ProgressBar)findViewById(R.id.pb);
resultView=(TextView)findViewById(R.id.textView1);
CalculateTask dTask = new CalculateTask();
dTask.execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
class MyButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("爽!")
.setIcon(android.R.drawable.ic_dialog_info)
.setCancelable(true)
.show();
}
}
class CalculateTask extends AsyncTask<Integer, Integer, String>{
//后面尖括号内分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值 类型
@Override
protected void onPreExecute() {
//第一个执行方法
Toast.makeText(getApplicationContext(), "Begin calculate!", Toast.LENGTH_SHORT).show();
super.onPreExecute();
}
@Override
protected String doInBackground(Integer... params) {
//第二个执行方法,onPreExecute()执行完后执行
for(int i=1;i<=TIME;i++){
double value=4.0/(2*i-1);
if (i % 2 == 1) result+=value;
else result-=value;
publishProgress((int) (100*(i*1.0/TIME)));
}
return ""+result;
}
@Override
protected void onProgressUpdate(Integer... progress) {
//这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数
//但是这里取到的是一个数组,所以要用progesss[0]来取值
//第n个参数就用progress[n]来取值
pb.setProgress(progress[0]);
resultView.setText(progress[0]+"%");
super.onProgressUpdate(progress);
}
@Override
protected void onPostExecute(String r) {
//doInBackground返回时触发,换句话说,就是doInBackground执行完后触发
//这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
//setTitle(result);
resultView.setText(r);
super.onPostExecute(r);
}
}
}
运行结果:
运行过程:Activity初始化之后,直接开一个创建一个task在后台跑,一边跑一边通过 onProgressUpdate方法更新UI,与此同时,UI线程还是可以响应Button。
最后当task运行完之后回调onPostExecute方法显示结果。
Thread中的run和start
run()方法相当于调用你那个类的一个方法了,即使你没有实现Runnable接口写个run方法进行调用,与调用run方法是一样的,并不会开启一个线程,就相当于还是在主线程中执行任务,开启线程必须用Start方法,他自己去调run方法。
参考
Android中AsyncTask的简单用法-http://blog.youkuaiyun.com/cjjky/article/details/6684959