通常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>
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方法显示结果。
run()方法相当于调用你那个类的一个方法了,即使你没有实现Runnable接口写个run方法进行调用,与调用run方法是一样的,并不会开启一个线程,就相当于还是在主线程中执行任务,开启线程必须用Start方法,他自己去调run方法。
Android中AsyncTask的简单用法-http://blog.csdn.net/cjjky/article/details/6684959