Android 之异步任务(AsyncTask,Handler,Message,looper)
AsyncTask: 3个类型(Params,Progress和Result),4个步骤(onPreExecute(),doInBackground(Params…),onProgressUpdate(Progress…), onPostExecute(Result) )
AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。
AsyncTask定义了三种泛型类型 Params,Progress和Result。可以为Void表示无类型
- Params 启动任务执行的输入参数,比如HTTP请求的URL。
- Progress 后台任务执行的百分比。
- Result 后台执行任务最终返回的结果,比如String。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:
- doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
- onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话你还得重写以下这三个方法,但不是必须的:
- onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
- onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
- onCancelled() 用户调用取消时,要做的操作
使用AsyncTask类,以下是几条必须遵守的准则:
- Task的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
一个最简单的异步任务例子:从网上下载一副图片
1 package com.example.android_03asynctask; 2 3 import org.apache.http.HttpEntity; 4 import org.apache.http.HttpResponse; 5 import org.apache.http.client.HttpClient; 6 import org.apache.http.client.methods.HttpGet; 7 import org.apache.http.impl.client.DefaultHttpClient; 8 import org.apache.http.util.EntityUtils; 9 10 import android.app.Activity; 11 import android.app.ProgressDialog; 12 import android.graphics.Bitmap; 13 import android.graphics.BitmapFactory; 14 import android.os.AsyncTask; 15 import android.os.Bundle; 16 import android.view.Menu; 17 import android.view.MenuItem; 18 import android.view.View; 19 import android.widget.Button; 20 import android.widget.ImageView; 21 22 public class MainActivity extends Activity { 23 24 private Button button; 25 private ImageView imageView; 26 private String image_Path="http://pic14.nipic.com/20110522/7411759_164157418126_2.jpg"; 27 private ProgressDialog progressDialog; 28 @Override 29 protected void onCreate(Bundle savedInstanceState) { 30 super.onCreate(savedInstanceState); 31 setContentView(R.layout.activity_main); 32 button=(Button)this.findViewById(R.id.button1); 33 imageView=(ImageView)this.findViewById(R.id.imageView1); 34 imageView=(ImageView)this.findViewById(R.id.imageView1); 35 progressDialog=new ProgressDialog(this); 36 progressDialog.setTitle("提示信息"); 37 progressDialog.setMessage("正在下载...."); 38 button.setOnClickListener(new View.OnClickListener() { 39 40 @Override 41 public void onClick(View v) { 42 // TODO Auto-generated method stub 43 //在UI主线程中不能直接访问网络,所以下面的写法是不正确的,必须使用异步任务 44 /* HttpClient httpClient=new DefaultHttpClient(); 45 HttpGet httpGet=new HttpGet(image_Path); 46 try { 47 httpClient.execute(httpGet); 48 } catch (Exception e) { 49 // TODO: handle exception 50 e.printStackTrace(); 51 }*/ 52 53 //执行异步任务操作 54 new myTask().execute(image_Path); 55 } 56 }); 57 } 58 59 /** 60 * 1.声明异步类继承AsyncTask,含有三个参数 61 * params 要执行的任务,一般为网络路径url 62 * progress 进度的刻度 一般为 Void,表示没有类型 63 * result任务执行的返回值 64 * 2. 65 * @author Administrator 66 * 67 */ 68 public class myTask extends AsyncTask<String, Void, Bitmap>{ 69 70 //任务执行之前的操作 71 @Override 72 protected void onPreExecute() { 73 // TODO Auto-generated method stub 74 super.onPreExecute(); 75 progressDialog.show(); 76 } 77 78 //主要完成耗时操作 79 @Override 80 protected Bitmap doInBackground(String... params) { 81 // TODO Auto-generated method stub 82 //使用网络连接类HttpClient完成对网络数据的提取 83 HttpClient httpClient=new DefaultHttpClient(); 84 HttpGet httpGet=new HttpGet(params[0]);//从可变参数取得第一个参数 85 Bitmap bitmap=null; 86 try { 87 HttpResponse httpResponse=httpClient.execute(httpGet); 88 if(httpResponse.getStatusLine().getStatusCode()==200){ 89 HttpEntity httpEntity=httpResponse.getEntity();//获取结果实体 90 byte[] data=EntityUtils.toByteArray(httpEntity);//将结果实体转换成字节数组 91 bitmap=BitmapFactory.decodeByteArray(data, 0, data.length);//将数据转换成bitmap对象 92 93 } 94 95 } catch (Exception e) { 96 // TODO: handle exception 97 e.printStackTrace(); 98 } 99 return bitmap; 100 } 101 102 //主要是更新UI操作 103 @Override 104 protected void onPostExecute(Bitmap result) { 105 // TODO Auto-generated method stub 106 super.onPostExecute(result); 107 imageView.setImageBitmap(result); 108 progressDialog.dismiss(); 109 } 110 } 111 @Override 112 public boolean onCreateOptionsMenu(Menu menu) { 113 // Inflate the menu; this adds items to the action bar if it is present. 114 getMenuInflater().inflate(R.menu.main, menu); 115 return true; 116 } 117 118 @Override 119 public boolean onOptionsItemSelected(MenuItem item) { 120 // Handle action bar item clicks here. The action bar will 121 // automatically handle clicks on the Home/Up button, so long 122 // as you specify a parent activity in AndroidManifest.xml. 123 int id = item.getItemId(); 124 if (id == R.id.action_settings) { 125 return true; 126 } 127 return super.onOptionsItemSelected(item); 128 } 129 }
上面的实例中并未显示进度条上的刻度,对上个例子完善如下:
1 package com.example.android_03asynctsk2; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 8 import org.apache.http.HttpEntity; 9 import org.apache.http.HttpResponse; 10 import org.apache.http.client.HttpClient; 11 import org.apache.http.client.methods.HttpGet; 12 import org.apache.http.impl.client.DefaultHttpClient; 13 import org.apache.http.util.EntityUtils; 14 15 import android.app.Activity; 16 import android.app.ProgressDialog; 17 import android.graphics.Bitmap; 18 import android.graphics.BitmapFactory; 19 import android.os.AsyncTask; 20 import android.os.Bundle; 21 import android.view.Menu; 22 import android.view.MenuItem; 23 import android.view.View; 24 import android.widget.Button; 25 import android.widget.ImageView; 26 27 public class MainActivity extends Activity { 28 private Button button; 29 private ImageView imageView; 30 private String image_Path = "http://pic14.nipic.com/20110522/7411759_164157418126_2.jpg"; 31 private ProgressDialog progressDialog; 32 33 @Override 34 protected void onCreate(Bundle savedInstanceState) { 35 super.onCreate(savedInstanceState); 36 setContentView(R.layout.activity_main); 37 button = (Button) this.findViewById(R.id.button1); 38 imageView = (ImageView) this.findViewById(R.id.imageView1); 39 progressDialog = new ProgressDialog(this); 40 progressDialog.setTitle("提示"); 41 progressDialog.setMessage("正在下载...."); 42 progressDialog.setCancelable(false);// 使屏幕失去焦点,直至下载完成才恢复焦点 43 progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);// 设置进度条为横向的 44 button.setOnClickListener(new View.OnClickListener() { 45 46 @Override 47 public void onClick(View v) { 48 // TODO Auto-generated method stub 49 new myTask().execute(image_Path); 50 51 } 52 }); 53 } 54 55 public class myTask extends AsyncTask<String, Integer, Bitmap> { 56 57 @Override 58 protected void onPreExecute() { 59 // TODO Auto-generated method stub 60 super.onPreExecute(); 61 progressDialog.show(); 62 } 63 64 @Override 65 protected Bitmap doInBackground(String... params) { 66 // TODO Auto-generated method stub 67 HttpClient httpClient = new DefaultHttpClient(); 68 HttpGet httpGet = new HttpGet(params[0]);// 从可变参数取得第一个参数 69 Bitmap bitmap = null; 70 ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); 71 InputStream inputStream=null; 72 try { 73 HttpResponse httpResponse = httpClient.execute(httpGet); 74 if (httpResponse.getStatusLine().getStatusCode() == 200) { 75 inputStream=httpResponse.getEntity().getContent();//得到输入流 76 long file_Length=httpResponse.getEntity().getContentLength();//获得文件总长度 77 int len=0,total_length=0; 78 byte[] data=new byte[1024];//每次读取的内容 79 while((len=inputStream.read(data))!=-1){ 80 total_length+=len; 81 int value=(int)((total_length/(float)file_Length)*100); 82 publishProgress(value);//发布刻度到onProgressUpdate 83 byteArrayOutputStream.write(data, 0, len); 84 } 85 byte []result=byteArrayOutputStream.toByteArray(); 86 bitmap=BitmapFactory.decodeByteArray(result, 0, result.length);//将数据转换成bitmap对象 87 } 88 89 } catch (Exception e) { 90 // TODO: handle exception 91 e.printStackTrace(); 92 } 93 finally{ 94 if(inputStream!=null){ 95 try { 96 inputStream.close(); 97 } catch (IOException e) { 98 // TODO Auto-generated catch block 99 e.printStackTrace(); 100 } 101 } 102 } 103 return bitmap; 104 } 105 106 @Override 107 protected void onProgressUpdate(Integer... values) { 108 // TODO Auto-generated method stub 109 super.onProgressUpdate(values); 110 progressDialog.setProgress(values[0]); 111 } 112 113 @Override 114 protected void onPostExecute(Bitmap result) { 115 // TODO Auto-generated method stub 116 super.onPostExecute(result); 117 imageView.setImageBitmap(result); 118 progressDialog.dismiss(); 119 } 120 121 } 122 123 @Override 124 public boolean onCreateOptionsMenu(Menu menu) { 125 // Inflate the menu; this adds items to the action bar if it is present. 126 getMenuInflater().inflate(R.menu.main, menu); 127 return true; 128 } 129 130 @Override 131 public boolean onOptionsItemSelected(MenuItem item) { 132 // Handle action bar item clicks here. The action bar will 133 // automatically handle clicks on the Home/Up button, so long 134 // as you specify a parent activity in AndroidManifest.xml. 135 int id = item.getItemId(); 136 if (id == R.id.action_settings) { 137 return true; 138 } 139 return super.onOptionsItemSelected(item); 140 } 141 }
Handler:异步处理大师,发送、处理消息,
Handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即:
通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。
handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。
andriod提供了Handler 和 Looper 来满足线程间的通信。
Handler先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。
1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
1.Handler创建消息
每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。
Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。
使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。
消息池提高了消息对象的复用,减少系统垃圾回收的次数。消息的创建流程如图1所示。
2.Handler发送消息
主要接受子线程发送的数据, 并用此数据配合主线程更新UI.
解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程,默认有一个消息队列) , 主线程为管理界面中的UI控件,进行事件分发,
比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。 如果此时需要一个耗时的操作,例如:
联网读取数据, 或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,,如果你放在主线程中的话,界面会
出现假死现象,如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭".
这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程是线程不安全的,也就是说,
更新UI只能在主线程中更新,子线程中操作是危险的.
这个时候,Handler就出现了.,来解决这个复杂的问题 , 由于Handler运行在主线程中(UI线程中), 它与子线程可以通过Message对象来传递数据,
这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,(里面包含数据) , 把这些消息放入主线程队列中,配合主线程进行更新UI。
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。
使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。
Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
Handler、Looper、MessageQueue的初始化流程如图2所示:
图1 图2 图3
一个最简单的异步任务例子:从网上下载一副图片(handler+message)
1 package com.example.android_03handler; 2 3 import org.apache.http.HttpResponse; 4 import org.apache.http.client.HttpClient; 5 import org.apache.http.client.methods.HttpGet; 6 import org.apache.http.impl.client.DefaultHttpClient; 7 import org.apache.http.util.EntityUtils; 8 9 import android.app.Activity; 10 import android.app.ProgressDialog; 11 import android.graphics.Bitmap; 12 import android.graphics.BitmapFactory; 13 import android.os.Bundle; 14 import android.os.Handler; 15 import android.os.Message; 16 import android.view.Menu; 17 import android.view.MenuItem; 18 import android.view.View; 19 import android.widget.Button; 20 import android.widget.ImageView; 21 22 public class MainActivity extends Activity { 23 private Button button; 24 private ImageView imageView; 25 private String image_Path = "http://pic14.nipic.com/20110522/7411759_164157418126_2.jpg"; 26 private final int IS_FINISHED=1; 27 private ProgressDialog progressDialog; 28 /** 29 * Handler必须开启一个子线程 30 * 该子线程必须通过Message传递数据给Handler 31 */ 32 private Handler handler=new Handler(){ 33 34 @Override 35 public void handleMessage(Message msg) { 36 // TODO Auto-generated method stub 37 super.handleMessage(msg); 38 byte[] data=(byte[]) msg.obj;//将message发送回来的数据强制转换成字节数组 39 Bitmap bitmap=BitmapFactory.decodeByteArray(data, 0, data.length); 40 imageView.setImageBitmap(bitmap); 41 if(msg.what==IS_FINISHED){ 42 progressDialog.dismiss(); 43 } 44 } 45 46 }; 47 48 public class myThread implements Runnable{ 49 50 //完成耗时任务操作 51 @Override 52 public void run() { 53 // TODO Auto-generated method stub 54 HttpClient httpClient = new DefaultHttpClient(); 55 HttpGet httpGet = new HttpGet(image_Path); 56 HttpResponse httpResponse =null; 57 try { 58 httpResponse = httpClient.execute(httpGet); 59 if (httpResponse.getStatusLine().getStatusCode() == 200) { 60 byte[] data=EntityUtils.toByteArray(httpResponse.getEntity()); 61 Message message=Message.obtain(); 62 message.obj=data; 63 message.what=IS_FINISHED; 64 handler.sendMessage(message); 65 66 } 67 } catch (Exception e) { 68 // TODO: handle exception 69 e.printStackTrace(); 70 } 71 } 72 73 } 74 @Override 75 protected void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 setContentView(R.layout.activity_main); 78 button = (Button) this.findViewById(R.id.button1); 79 imageView = (ImageView) this.findViewById(R.id.imageView1); 80 progressDialog=new ProgressDialog(this); 81 progressDialog.setTitle("提示"); 82 progressDialog.setMessage("downloading....."); 83 progressDialog.setCancelable(false); 84 button.setOnClickListener(new View.OnClickListener() { 85 86 @Override 87 public void onClick(View v) { 88 // TODO Auto-generated method stub 89 new Thread(new myThread()).start(); 90 progressDialog.show(); 91 } 92 }); 93 } 94 95 @Override 96 public boolean onCreateOptionsMenu(Menu menu) { 97 // Inflate the menu; this adds items to the action bar if it is present. 98 getMenuInflater().inflate(R.menu.main, menu); 99 return true; 100 } 101 102 @Override 103 public boolean onOptionsItemSelected(MenuItem item) { 104 // Handle action bar item clicks here. The action bar will 105 // automatically handle clicks on the Home/Up button, so long 106 // as you specify a parent activity in AndroidManifest.xml. 107 int id = item.getItemId(); 108 if (id == R.id.action_settings) { 109 return true; 110 } 111 return super.onOptionsItemSelected(item); 112 } 113 }
message简单代码(不建议使用new生成消息,建议使用message.obtain()或者Handler.obtainMessage(....))
1 package com.example.android_03message; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.os.Message; 7 import android.view.Menu; 8 import android.view.MenuItem; 9 import android.view.View; 10 import android.widget.Button; 11 12 public class MainActivity extends Activity { 13 private Button button; 14 private Handler handler=new Handler(){ 15 16 @Override 17 public void handleMessage(Message msg) { 18 // TODO Auto-generated method stub 19 super.handleMessage(msg); 20 int arg1=msg.arg1; 21 int arg2=msg.arg2; 22 int what=msg.what; 23 Object obj=msg.obj; 24 System.out.println("-->"+arg1+"-->"+arg2+"-->"+what+"-->"+obj); 25 Bundle bundle=msg.getData(); 26 System.out.println(bundle.getStringArray("str").length); 27 } 28 29 }; 30 public class myThread implements Runnable{ 31 32 @Override 33 public void run() { 34 // TODO Auto-generated method stub 35 //第一种方式 36 /*Message message=Message.obtain(); 37 message.arg1=1; 38 message.arg2=2; 39 message.what=3; 40 message.obj="mlj"; 41 handler.sendMessage(message);*/ 42 //第二种方式,源码中提示,默认执行了message.target=handler表明将消息交给某个handler去发送, 43 //因此与第一种发送消息方式不同 44 /* Message message=Message.obtain(handler); 45 message.arg1=1; 46 message.arg2=2; 47 message.what=3; 48 message.obj="mlj"; 49 message.sendToTarget();*/ 50 //第三种方式Message.obtain(handler,what); 51 /* Message message=Message.obtain(handler,3); 52 message.arg1=1; 53 message.arg2=2; 54 message.obj="mlj"; 55 message.sendToTarget();*/ 56 //第四种方式Message.obtain(handler,what,obj); 57 /*Message message=Message.obtain(handler,3,"mlj"); 58 message.arg1=1; 59 message.arg2=2; 60 message.sendToTarget();*/ 61 //第五种方式Message.obtain(handler,what,arg1,arg2,obj); 62 Message message=Message.obtain(handler,3,1,2,"mlj"); 63 //在传基本数据之外还可以通过setData传一些复杂数据 64 Bundle bundle=new Bundle(); 65 bundle.putStringArray("str", new String []{"mlj","mlj","mlj"}); 66 message.setData(bundle); 67 message.sendToTarget(); 68 69 } 70 71 } 72 @Override 73 protected void onCreate(Bundle savedInstanceState) { 74 super.onCreate(savedInstanceState); 75 setContentView(R.layout.activity_main); 76 button = (Button) this.findViewById(R.id.button1); 77 button.setOnClickListener(new View.OnClickListener() { 78 79 @Override 80 public void onClick(View v) { 81 // TODO Auto-generated method stub 82 new Thread(new myThread()).start(); 83 } 84 }); 85 } 86 87 @Override 88 public boolean onCreateOptionsMenu(Menu menu) { 89 // Inflate the menu; this adds items to the action bar if it is present. 90 getMenuInflater().inflate(R.menu.main, menu); 91 return true; 92 } 93 94 @Override 95 public boolean onOptionsItemSelected(MenuItem item) { 96 // Handle action bar item clicks here. The action bar will 97 // automatically handle clicks on the Home/Up button, so long 98 // as you specify a parent activity in AndroidManifest.xml. 99 int id = item.getItemId(); 100 if (id == R.id.action_settings) { 101 return true; 102 } 103 return super.onOptionsItemSelected(item); 104 } 105 }
handler的send(在线程内部发送消息方式)和post(new Runnable()...)两种发送消息方式的简单代码
1 package com.example.android_03handler_message; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.os.Message; 7 import android.view.Menu; 8 import android.view.MenuItem; 9 import android.view.View; 10 import android.view.View.OnClickListener; 11 import android.widget.Button; 12 13 public class MainActivity extends Activity implements OnClickListener{ 14 private Button button; 15 private Button button2; 16 private Handler handler=new Handler(){ 17 18 @Override 19 public void handleMessage(Message msg) { 20 // TODO Auto-generated method stub 21 super.handleMessage(msg); 22 System.out.println("-->"+msg.what); 23 } 24 25 }; 26 @Override 27 protected void onCreate(Bundle savedInstanceState) { 28 super.onCreate(savedInstanceState); 29 setContentView(R.layout.activity_main); 30 button = (Button) this.findViewById(R.id.button1); 31 button2 = (Button) this.findViewById(R.id.button2); 32 button.setOnClickListener(this); 33 button2.setOnClickListener(this); 34 } 35 36 @Override 37 public boolean onCreateOptionsMenu(Menu menu) { 38 // Inflate the menu; this adds items to the action bar if it is present. 39 getMenuInflater().inflate(R.menu.main, menu); 40 return true; 41 } 42 43 @Override 44 public boolean onOptionsItemSelected(MenuItem item) { 45 // Handle action bar item clicks here. The action bar will 46 // automatically handle clicks on the Home/Up button, so long 47 // as you specify a parent activity in AndroidManifest.xml. 48 int id = item.getItemId(); 49 if (id == R.id.action_settings) { 50 return true; 51 } 52 return super.onOptionsItemSelected(item); 53 } 54 55 @Override 56 public void onClick(View v) { 57 // TODO Auto-generated method stub 58 switch (v.getId()) { 59 case R.id.button1: 60 new Thread(new Runnable() { 61 62 @Override 63 public void run() { 64 // TODO Auto-generated method stub 65 //handler.sendEmptyMessage(3); 66 //handler.sendEmptyMessageAtTime(4, 1000); 67 handler.sendEmptyMessageDelayed(3, 3000); 68 } 69 }).start(); 70 break; 71 case R.id.button2: 72 handler.post(new Runnable() { 73 74 @Override 75 public void run() { 76 // TODO Auto-generated method stub 77 handler.sendEmptyMessageDelayed(3, 4000); 78 } 79 }); 80 break; 81 default: 82 break; 83 } 84 } 85 }
Looper:
字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。
在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。
使用Looper类创建Looper线程很简单:
public class LooperThread extends Thread { @Override public void run() { // 将当前线程初始化为Looper线程 Looper.prepare(); // ...其他处理,如实例化handler // 开始循环处理消息队列 Looper.loop(); } }
Looper.prepare()之后线程就升级为Looper线程了,线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,
prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。
除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如:
Looper.myLooper()得到当前线程looper对象:
1 public static final Looper myLooper() { 2 // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper 3 return (Looper)sThreadLocal.get(); 4 }
getThread()得到looper对象所属线程:
1 public Thread getThread() { 2 return mThread; 3 }
quit()方法结束looper循环:
1 public void quit() { 2 // 创建一个空的message,它的target为NULL,表示结束循环消息 3 Message msg = Message.obtain(); 4 // 发出消息 5 mQueue.enqueueMessage(msg, 0); 6 }
简单的looper代码:
1 package com.example.android_08looper; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.os.Looper; 7 import android.os.Message; 8 import android.view.Menu; 9 import android.view.MenuItem; 10 import android.view.View; 11 import android.widget.Button; 12 import android.widget.TextView; 13 14 public class MainActivity extends Activity { 15 16 private Button button1; 17 private TextView textView1; 18 private myHandler mHandler; 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 button1=(Button)this.findViewById(R.id.button1); 24 textView1=(TextView)this.findViewById(R.id.textView1); 25 //使用无参构造方法时handler并未与looper关联,但是为什么可以发送并接收消息呢? 26 //原因是Activity中默认有一个Looper对象来处理子线程发送的消息,其实相当于一下内容 27 //Looper looper=Looper.myLooper();//从UI主线程中得到looper 28 //mHandler=new myHandler(looper); 29 Looper looper=Looper.myLooper(); 30 mHandler=new myHandler(); 31 button1.setOnClickListener(new View.OnClickListener() { 32 33 @Override 34 public void onClick(View v) { 35 // TODO Auto-generated method stub 36 new Thread(new myThread()).start();//在主线程中开启一个子线程 37 } 38 }); 39 } 40 41 public class myThread implements Runnable{ 42 43 @Override 44 public void run() { 45 // TODO Auto-generated method stub 46 Message message=Message.obtain(); 47 message.obj="mlj"; 48 mHandler.sendMessage(message); 49 } 50 51 } 52 public class myHandler extends Handler{ 53 54 public myHandler(Looper looper) { 55 super(looper); 56 // TODO Auto-generated constructor stub 57 } 58 public myHandler() { 59 60 } 61 62 63 @Override 64 public void handleMessage(Message msg) { 65 // TODO Auto-generated method stub 66 super.handleMessage(msg); 67 textView1.setText("-接收消息->>"+msg.obj); 68 } 69 70 } 71 @Override 72 public boolean onCreateOptionsMenu(Menu menu) { 73 // Inflate the menu; this adds items to the action bar if it is present. 74 getMenuInflater().inflate(R.menu.main, menu); 75 return true; 76 } 77 78 @Override 79 public boolean onOptionsItemSelected(MenuItem item) { 80 // Handle action bar item clicks here. The action bar will 81 // automatically handle clicks on the Home/Up button, so long 82 // as you specify a parent activity in AndroidManifest.xml. 83 int id = item.getItemId(); 84 if (id == R.id.action_settings) { 85 return true; 86 } 87 return super.onOptionsItemSelected(item); 88 } 89 }
在以上例子中,如果handler的实例化脱离了UI主线程,而在子线程中实例化的话,就无法使用Activity中默认的looper对象,
就需要手工开启Looper,代码如下:
1 package com.example.android_08looper; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.os.Looper; 7 import android.os.Message; 8 import android.view.Menu; 9 import android.view.MenuItem; 10 import android.view.View; 11 import android.widget.Button; 12 import android.widget.TextView; 13 14 public class MainActivity extends Activity { 15 16 private Button button1; 17 private TextView textView1; 18 private Handler mHandler; 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 button1=(Button)this.findViewById(R.id.button1); 24 textView1=(TextView)this.findViewById(R.id.textView1); 25 new Thread(new myThread()).start(); 26 button1.setOnClickListener(new View.OnClickListener() { 27 28 @Override 29 public void onClick(View v) { 30 // TODO Auto-generated method stub 31 Message message=Message.obtain(); 32 message.obj="mlj"; 33 mHandler.sendMessage(message); 34 } 35 }); 36 } 37 38 public class myThread implements Runnable{ 39 40 @Override 41 public void run() { 42 // TODO Auto-generated method stub 43 Looper.prepare(); 44 mHandler=new Handler(){ 45 46 @Override 47 public void handleMessage(Message msg) { 48 // TODO Auto-generated method stub 49 super.handleMessage(msg); 50 //textView1.setText("--->>接收UI主线程的消息"+msg.obj);//在子线程中无法更新UI所以改成以下语句 51 System.out.println("--->>接收UI主线程的消息"+msg.obj); 52 } 53 54 }; 55 Looper.loop(); 56 } 57 58 } 59 @Override 60 public boolean onCreateOptionsMenu(Menu menu) { 61 // Inflate the menu; this adds items to the action bar if it is present. 62 getMenuInflater().inflate(R.menu.main, menu); 63 return true; 64 } 65 66 @Override 67 public boolean onOptionsItemSelected(MenuItem item) { 68 // Handle action bar item clicks here. The action bar will 69 // automatically handle clicks on the Home/Up button, so long 70 // as you specify a parent activity in AndroidManifest.xml. 71 int id = item.getItemId(); 72 if (id == R.id.action_settings) { 73 return true; 74 } 75 return super.onOptionsItemSelected(item); 76 } 77 }
总结几点:
1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal
2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
3.Looper使一个线程变成Looper线程。
---------------------
综合案例(图文混排):客户端(AsyncTask,Handler,Message,listView)+服务器端,未缓存
服务器端最终返回给客户端的是jsonString,内容是:
1 {"products":[{"proprice":"5","proaddress":"河南","proimage":"1.jpg","proname":"苹果","proid":"4a6c8e"},{"proprice":"2","proaddress":"北京","proimage":"1.jpg","proname":"猕猴桃","proid":"5fd850"},{"proprice":"4","proaddress":"广州","proimage":"1.jpg","proname":"香蕉","proid":"7c39b7"},{"proprice":"1","proaddress":"广州","proimage":"1.jpg","proname":"梨","proid":"850995"},{"proprice":"3","proaddress":"北京","proimage":"1.jpg","proname":"西瓜","proid":"be3314"},{"proprice":"7","proaddress":"上海","proimage":"1.jpg","proname":"桃子","proid":"c84833"}]}
客户端源文件目录如图:
步骤:
第一步:在布局文件中添加listView
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context="com.example.android_08handler_product.MainActivity" > 10 11 <ListView 12 android:id="@+id/listView1" 13 android:layout_width="match_parent" 14 android:layout_height="wrap_content" 15 android:layout_alignParentTop="true" 16 android:layout_centerHorizontal="true" > 17 </ListView> 18 19 </RelativeLayout>
第二步:在清单文件中添加网络授权
1 <uses-permission android:name="android.permission.INTERNET"/>
第三步:定义一个公共类CommonURL.java,存放客户端访问的网络地址
1 package com.example.android_08handler_product; 2 3 public class CommonURL { 4 5 //访问服务器产品url 6 static String Pro_URL="http://122.206.79.193:8080/xianfengProject/servlet/JsonAction?action_flag=more"; 7 //访问服务器产品图片url 8 static String Pro_ImgURL="http://122.206.79.193:8080/xianfengProject/upload/"; 9 }
第四步:编写主程序
注:图文混排时,不建议文字和图片在同一线程中出现或解决,一般是先处理文字再处理图片。
4.1 定义listView控件并取得
4.2为listView定义适配器(全局的)并在oncreate()方法中初始化,通常分以下几步
1.声明Context context,LayoutInflater layoutInflater两个变量并在构造方法中取值:this.**=**;
2.提供void setData()方法,由于取json数据时,数据类型是map类型,因此在适配器中需定义全局变量List<Map<String,Object>> list=null,
并在该方法中设置 this.list=list;
3. 完成适配器中除getView()方法以外的其他方法;
4.为适配器定义布局文件item.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" > 5 6 <ImageView 7 android:id="@+id/imageView1" 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content" 10 android:layout_alignParentLeft="true" 11 android:layout_alignParentTop="true" 12 android:layout_marginLeft="16dp" 13 android:src="@drawable/ic_launcher" /> 14 15 <TextView 16 android:id="@+id/textView1" 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:layout_alignParentTop="true" 20 android:layout_toRightOf="@+id/imageView1" 21 android:text="TextView" /> 22 23 <TextView 24 android:id="@+id/textView2" 25 android:layout_width="wrap_content" 26 android:layout_height="wrap_content" 27 android:layout_alignLeft="@+id/textView1" 28 android:layout_below="@+id/textView1" 29 android:text="TextView" /> 30 31 <TextView 32 android:id="@+id/textView3" 33 android:layout_width="wrap_content" 34 android:layout_height="wrap_content" 35 android:layout_alignLeft="@+id/textView2" 36 android:layout_below="@+id/textView2" 37 android:text="TextView" /> 38 39 </RelativeLayout>
5. 完成适配器中getView()方法;
1 @Override 2 public View getView(int position, View convertView, ViewGroup parent) { 3 // TODO Auto-generated method stub 4 View view=null; 5 if(convertView==null){ 6 view=layoutInflater.inflate(R.layout.item, null); 7 } 8 else 9 view=convertView; 10 TextView name=(TextView)view.findViewById(R.id.textView1); 11 TextView address=(TextView)view.findViewById(R.id.textView2); 12 TextView price=(TextView)view.findViewById(R.id.textView3); 13 final ImageView imageView=(ImageView)view.findViewById(R.id.imageView1); 14 //接下来需要给name address price赋值成从服务器上取下来的数据,这就需要定义异步任务 15 name.setText(list.get(position).get("proname").toString()); 16 address.setText(list.get(position).get("proaddress").toString()); 17 price.setText(list.get(position).get("proprice").toString()); 18 //图片待会儿再处理 19 return view; 20 }
由于在5中需要对name address price image赋值,这些值是从服务器取下来的json数据并解析后传回来的,这就需要定义异步任务AsyncTask了
4.3 定义异步任务
1.通常需要定义一个ProgressDialog并在oncreate方法中初始化
1 dialog=new ProgressDialog(this); 2 dialog.setTitle("提示"); 3 dialog.setMessage("正在下载,请稍后......");
2.定义异步任务,实现3个方法
1 public class myTask extends AsyncTask<String, Void, List<Map<String,Object>>>{ 2 3 @Override 4 protected void onPreExecute() { 5 // TODO Auto-generated method stub 6 dialog.show(); 7 super.onPreExecute(); 8 } 9 10 @Override 11 protected void onPostExecute(List<Map<String, Object>> result) { 12 // TODO Auto-generated method stub 13 super.onPostExecute(result); 14 adapter.setData(result); 15 listView.setAdapter(adapter); 16 adapter.notifyDataSetChanged(); 17 dialog.dismiss(); 18 19 } 20 21 @Override 22 protected List<Map<String, Object>> doInBackground(String... params) { 23 // TODO Auto-generated method stub 24 //list 服务器返回的最终经过解析后的json数据 25 List<Map<String, Object>> list=new ArrayList<Map<String,Object>>(); 26 //连接网络 获取json数据并解析 27 try { 28 HttpClient client=new DefaultHttpClient(); 29 //HttpGet httpGet=new HttpGet(params[0]);//不建议使用 因为大小受限制 30 HttpPost httpPost=new HttpPost(params[0]); 31 HttpResponse response=client.execute(httpPost); 32 if(response.getStatusLine().getStatusCode()==200){ 33 String jsonString=EntityUtils.toString(response.getEntity(), "utf-8"); 34 //以上获取数据 35 //以下解析数据 36 JSONObject jsonObject=new JSONObject(jsonString);//返回的数据key为products,Value为数组,数组中的每个元素又是jsonObject 37 JSONArray jsonArray=jsonObject.getJSONArray("products"); 38 for (int i = 0; i < jsonArray.length(); i++) { 39 JSONObject jsonObject2=jsonArray.getJSONObject(i); 40 Map<String,Object> map=new HashMap<String, Object>(); 41 Iterator<String> iterator=jsonObject2.keys(); 42 while(iterator.hasNext()){ 43 String key=iterator.next(); 44 Object val=jsonObject2.get(key); 45 map.put(key, val); 46 } 47 list.add(map); 48 } 49 } 50 51 } catch (Exception e) { 52 // TODO: handle exception 53 e.printStackTrace(); 54 } 55 return list; 56 } 57 58 } 59 @Override 60
3.在oncreate()方法中开启任务,传递的参数是CommonURL中的地址
1 new myTask().execute(CommonURL.Pro_URL);
在onpostExcute()方法执行时,通过适配器的setData方法填充适配器,并为listView绑定数据源
1 protected void onPostExecute(List<Map<String, Object>> result) { 2 // TODO Auto-generated method stub 3 super.onPostExecute(result); 4 adapter.setData(result); 5 listView.setAdapter(adapter); 6 adapter.notifyDataSetChanged(); 7 dialog.dismiss(); 8 9 }
之后 就得回到第四步4.2的最后一小步,即更新UI
1 name.setText(list.get(position).get("proname").toString()); 2 address.setText(list.get(position).get("proaddress").toString()); 3 price.setText(list.get(position).get("proprice").toString());
----------接下来得用接口回调来加载图片---------------
需要专门定义一个用来处理图片的类DownLoadImg.java,该类包括:
一个用来传递图片地址的构造方法,一个提供得到图片方法的接口,和一个下载图片、开启子线程返回消息给handler的方法(其参数是回调接口的一个对象)
该类接收activity传过来的图片路径并方法中开启一个子线程中下载图片作为消息返回给handler处理,handler的处理方式是通过回调对象获得该图片的引用,
然后在activity中调用该方法时,需要new一个接口对象作为参数,该参数是一个匿名内部类,在该类的实现接口方法中即可获得图片
因为该类需要开启一个子线程,得到图片
接口回调:定义一个接口,该接口提供了一个得到图片的方法getDrawable,方法参数为Drawable类型
1 public interface ImgCallBack{ 2 public void getDrawable(Drawable drawable); 3 }
最终代码:
MainActivity:
1 package com.example.android_08handler_product; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.Iterator; 6 import java.util.List; 7 import java.util.Map; 8 9 import org.apache.http.HttpResponse; 10 import org.apache.http.client.HttpClient; 11 import org.apache.http.client.methods.HttpPost; 12 import org.apache.http.impl.client.DefaultHttpClient; 13 import org.apache.http.util.EntityUtils; 14 import org.json.JSONArray; 15 import org.json.JSONObject; 16 17 import android.app.Activity; 18 import android.app.ProgressDialog; 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.os.AsyncTask; 22 import android.os.Bundle; 23 import android.view.LayoutInflater; 24 import android.view.Menu; 25 import android.view.MenuItem; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.BaseAdapter; 29 import android.widget.ImageView; 30 import android.widget.ListView; 31 import android.widget.TextView; 32 33 import com.example.android_08handler_product.DownLoadImg.ImgCallBack; 34 35 public class MainActivity extends Activity { 36 37 private ListView listView; 38 private ProgressDialog dialog; 39 private myAdapter adapter; 40 @Override 41 protected void onCreate(Bundle savedInstanceState) { 42 super.onCreate(savedInstanceState); 43 setContentView(R.layout.activity_main); 44 listView=(ListView)this.findViewById(R.id.listView1); 45 dialog=new ProgressDialog(this); 46 dialog.setTitle("提示"); 47 dialog.setMessage("正在下载,请稍后......"); 48 adapter=new myAdapter(this); 49 new myTask().execute(CommonURL.Pro_URL); 50 } 51 52 public class myAdapter extends BaseAdapter{ 53 54 private Context context; 55 private LayoutInflater layoutInflater; 56 private List<Map<String,Object>> list=null;//存放最终解析过的json数据 57 public myAdapter(){ 58 59 } 60 public myAdapter(Context context){ 61 this.context=context; 62 layoutInflater=layoutInflater.from(context); 63 } 64 public void setData(List<Map<String,Object>> list){ 65 this.list=list; 66 } 67 @Override 68 public int getCount() { 69 // TODO Auto-generated method stub 70 return list.size(); 71 } 72 73 @Override 74 public Object getItem(int position) { 75 // TODO Auto-generated method stub 76 return list.get(position); 77 } 78 79 @Override 80 public long getItemId(int position) { 81 // TODO Auto-generated method stub 82 return position; 83 } 84 85 @Override 86 public View getView(int position, View convertView, ViewGroup parent) { 87 // TODO Auto-generated method stub 88 View view=null; 89 if(convertView==null){ 90 view=layoutInflater.inflate(R.layout.item, null); 91 } 92 else 93 view=convertView; 94 TextView name=(TextView)view.findViewById(R.id.textView1); 95 TextView address=(TextView)view.findViewById(R.id.textView2); 96 TextView price=(TextView)view.findViewById(R.id.textView3); 97 final ImageView imageView=(ImageView)view.findViewById(R.id.imageView1); 98 //接下来需要给name address price赋值成从服务器上取下来的数据,这就需要定义异步任务 99 name.setText(list.get(position).get("proname").toString()); 100 address.setText(list.get(position).get("proaddress").toString()); 101 price.setText(list.get(position).get("proprice").toString()); 102 DownLoadImg downLoadImg=new DownLoadImg(CommonURL.Pro_ImgURL+list.get(position).get("proimage").toString()); 103 downLoadImg.loadImg(new ImgCallBack() { 104 105 @Override 106 public void getDrawable(Drawable drawable) { 107 // TODO Auto-generated method stub 108 imageView.setImageDrawable(drawable); 109 } 110 }); 111 return view; 112 } 113 114 } 115 public class myTask extends AsyncTask<String, Void, List<Map<String,Object>>>{ 116 117 @Override 118 protected void onPreExecute() { 119 // TODO Auto-generated method stub 120 dialog.show(); 121 super.onPreExecute(); 122 } 123 124 @Override 125 protected void onPostExecute(List<Map<String, Object>> result) { 126 // TODO Auto-generated method stub 127 super.onPostExecute(result); 128 adapter.setData(result); 129 listView.setAdapter(adapter); 130 adapter.notifyDataSetChanged(); 131 dialog.dismiss(); 132 133 } 134 135 @Override 136 protected List<Map<String, Object>> doInBackground(String... params) { 137 // TODO Auto-generated method stub 138 //list 服务器返回的最终经过解析后的json数据 139 List<Map<String, Object>> list=new ArrayList<Map<String,Object>>(); 140 //连接网络 获取json数据并解析 141 try { 142 HttpClient client=new DefaultHttpClient(); 143 //HttpGet httpGet=new HttpGet(params[0]);//不建议使用 因为大小受限制 144 HttpPost httpPost=new HttpPost(params[0]); 145 HttpResponse response=client.execute(httpPost); 146 if(response.getStatusLine().getStatusCode()==200){ 147 String jsonString=EntityUtils.toString(response.getEntity(), "utf-8"); 148 //以上获取数据 149 //以下解析数据 150 JSONObject jsonObject=new JSONObject(jsonString);//返回的数据key为products,Value为数组,数组中的每个元素又是jsonObject 151 JSONArray jsonArray=jsonObject.getJSONArray("products"); 152 for (int i = 0; i < jsonArray.length(); i++) { 153 JSONObject jsonObject2=jsonArray.getJSONObject(i); 154 Map<String,Object> map=new HashMap<String, Object>(); 155 Iterator<String> iterator=jsonObject2.keys(); 156 while(iterator.hasNext()){ 157 String key=iterator.next(); 158 Object val=jsonObject2.get(key); 159 map.put(key, val); 160 } 161 list.add(map); 162 } 163 } 164 165 } catch (Exception e) { 166 // TODO: handle exception 167 e.printStackTrace(); 168 } 169 return list; 170 } 171 172 } 173 @Override 174 public boolean onCreateOptionsMenu(Menu menu) { 175 // Inflate the menu; this adds items to the action bar if it is present. 176 getMenuInflater().inflate(R.menu.main, menu); 177 return true; 178 } 179 180 @Override 181 public boolean onOptionsItemSelected(MenuItem item) { 182 // Handle action bar item clicks here. The action bar will 183 // automatically handle clicks on the Home/Up button, so long 184 // as you specify a parent activity in AndroidManifest.xml. 185 int id = item.getItemId(); 186 if (id == R.id.action_settings) { 187 return true; 188 } 189 return super.onOptionsItemSelected(item); 190 } 191 }
DownLoadImg
1 package com.example.android_08handler_product; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.net.MalformedURLException; 6 import java.net.URL; 7 8 import android.graphics.drawable.Drawable; 9 import android.os.Handler; 10 import android.os.Message; 11 12 public class DownLoadImg { 13 14 private String img_path; 15 public DownLoadImg(String img_path) { 16 // TODO Auto-generated constructor stub 17 this.img_path=img_path; 18 } 19 20 public void loadImg(final ImgCallBack back){ 21 final Handler handler=new Handler(){ 22 23 @Override 24 public void handleMessage(Message msg) { 25 // TODO Auto-generated method stub 26 super.handleMessage(msg); 27 back.getDrawable((Drawable)msg.obj); 28 } 29 }; 30 new Thread(new Runnable() { 31 32 @Override 33 public void run() { 34 InputStream is; 35 try { 36 is = new URL(img_path).openStream(); 37 Drawable drawable=Drawable.createFromStream(is, ""); 38 Message message=Message.obtain(); 39 message.obj=drawable; 40 handler.sendMessage(message); 41 } catch (MalformedURLException e) { 42 // TODO Auto-generated catch block 43 e.printStackTrace(); 44 } catch (IOException e) { 45 // TODO Auto-generated catch block 46 e.printStackTrace(); 47 } 48 // TODO Auto-generated method stub 49 50 } 51 }).start(); 52 } 53 //接口的回调方式 54 public interface ImgCallBack{ 55 public void getDrawable(Drawable drawable); 56 } 57 }