Android-AsyncTask异步任务(获取手机联系人)
本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。
一、Android当中的多线程
在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。
二、Main Thread 和 Worker Thread
在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。
当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。
Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。
另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:
- 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
- 不能在UI Thread之外的线程当中操纵我们的UI元素
三、如何处理UI Thread 和 Worker Thread之间的通信
既然在Android当中有两条重要的原则要遵守,那么我们可能就有疑问了?我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了我们的主线程和工作线程之间的通信问题了。在Android当中,提供了两种方式来解决线程直接的通信问题,一种是通过Handler的机制(这种方式在后面的随笔中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。
四、AsyncTask
AsyncTask:异步任务,从字面上来说,就是在我们的UI主线程运行的时候,异步的完成一些操作。AsyncTask允许我们的执行一个异步的任务在后台。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给我们的UI线程来更新我们的UI控件。通过AsyncTask我们可以轻松的解决多线程之间的通信问题。
怎么来理解AsyncTask呢?通俗一点来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。
3个泛型指的是什么呢?我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数:
AsyncTask <Params, Progress, Result>
- Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型
- Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型
- Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型
我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如:
AsyncTask <Void, Void, Void>
4个步骤:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行
- onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog
- doInBackground(Params... params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作
(ps:需要注意的是Android为了安全考虑,不允许在主线程即UI线程进行耗时操作。例如HTTP请求等。
如果在UI中使用了耗时操作的话,Android Studio或者eclipse本身是不会报错的。只有在APP执行到相应的耗时操作位置时才会停止运行。手机或模拟器上会出现“很抱歉,XXX已停止运行”同时Android Studio logcat输出“
E/AndroidRuntime: FATAL EXCEPTION: main
Process:.....java.lang.RuntimeException.....
) - onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新
- onPostExecute(Result... result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上
为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。
下面举个获取手机联系人的栗子:
布局文件如下:
activity_main.xml:
1 <LinearLayout 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:orientation="vertical" 6 tools:context="com.example.contentproviderdemo.MainActivity" > 7 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:orientation="horizontal" 12 > 13 <TextView 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:id="@+id/tx_id1" 17 android:text="id" 18 /> 19 <TextView 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content" 22 android:id="@+id/tx_name1" 23 android:text="姓名" 24 android:layout_weight="1" 25 android:layout_marginLeft="5dp" 26 /> 27 <TextView 28 android:layout_width="wrap_content" 29 android:layout_height="wrap_content" 30 android:id="@+id/tx_ptype1" 31 android:text="号码类型" 32 android:layout_weight="1" 33 android:layout_marginLeft="5dp" 34 /> 35 36 37 <TextView 38 android:layout_width="wrap_content" 39 android:layout_height="wrap_content" 40 android:id="@+id/tx_phonenum1" 41 android:text="号码" 42 android:layout_weight="1" 43 android:layout_marginLeft="5dp" 44 /> 45 <TextView 46 android:layout_width="wrap_content" 47 android:layout_height="wrap_content" 48 android:id="@+id/tx_etype1" 49 android:text="邮箱类型" 50 android:layout_weight="1" 51 android:layout_marginLeft="5dp" 52 /> 53 <TextView 54 android:layout_width="wrap_content" 55 android:layout_height="wrap_content" 56 android:id="@+id/tx_email1" 57 android:text="邮箱" 58 android:layout_weight="1" 59 android:layout_marginLeft="5dp" 60 /> 61 62 </LinearLayout> 63 64 <ListView 65 android:id="@+id/listView1" 66 android:layout_width="match_parent" 67 android:layout_height="wrap_content" 68 android:layout_weight="1" 69 > 70 71 </ListView> 72 <Button 73 android:id="@+id/query_btn" 74 android:layout_width="wrap_content" 75 android:layout_height="wrap_content" 76 android:text="获取" 77 /> 78 79 80 </LinearLayout>
item_view.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="horizontal" > 6 <TextView 7 android:layout_width="wrap_content" 8 android:layout_height="match_parent" 9 android:id="@+id/tx_id" 10 android:text="id" 11 android:layout_weight="1" 12 android:layout_marginLeft="5dp" 13 /> 14 <TextView 15 android:layout_width="wrap_content" 16 android:layout_height="wrap_content" 17 android:id="@+id/tx_name" 18 android:text="姓名" 19 android:layout_weight="1" 20 android:layout_marginLeft="5dp" 21 /> 22 <TextView 23 android:layout_width="wrap_content" 24 android:layout_height="wrap_content" 25 android:id="@+id/tx_ptype" 26 android:text="号码类型" 27 android:layout_weight="1" 28 /> 29 <TextView 30 android:layout_width="wrap_content" 31 android:layout_height="wrap_content" 32 android:id="@+id/tx_phonenum" 33 android:text="号码" 34 android:layout_weight="1" 35 /> 36 <TextView 37 android:layout_width="wrap_content" 38 android:layout_height="wrap_content" 39 android:id="@+id/tx_etype" 40 android:text="邮箱类型" 41 android:layout_weight="1" 42 android:layout_marginLeft="5dp" 43 /> 44 <TextView 45 android:layout_width="wrap_content" 46 android:layout_height="wrap_content" 47 android:id="@+id/tx_email" 48 android:text="邮箱" 49 android:layout_weight="1" 50 android:layout_marginLeft="5dp" 51 /> 52 53 </LinearLayout>
新建一个实体类,保存联系人的id,姓名,号码类型和号码,邮箱类型和邮箱
1 package com.example.contentproviderdemo; 2 3 public class CallPerson { 4 private int id; 5 private String name; 6 private String type; 7 private String phoneNum; 8 private String emailtype; 9 private String email; 10 public int getId() { 11 return id; 12 } 13 public void setId(int id) { 14 this.id = id; 15 } 16 public String getName() { 17 return name; 18 } 19 public void setName(String name) { 20 this.name = name; 21 } 22 public String getType() { 23 return type; 24 } 25 public void setType(String type) { 26 this.type = type; 27 } 28 public String getPhoneNum() { 29 return phoneNum; 30 } 31 public void setPhoneNum(String phoneNum) { 32 this.phoneNum = phoneNum; 33 } 34 35 36 public String getEmailtype() { 37 return emailtype; 38 } 39 public void setEmailtype(String emailtype) { 40 this.emailtype = emailtype; 41 } 42 public String getEmail() { 43 return email; 44 } 45 public void setEmail(String email) { 46 this.email = email; 47 } 48 public CallPerson() { 49 super(); 50 // TODO Auto-generated constructor stub 51 } 52 53 }
新建一个MyTask类继承AsyncTask
1 package com.example.contentproviderdemo; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 7 import android.content.ContentResolver; 8 import android.database.Cursor; 9 import android.os.AsyncTask; 10 import android.provider.ContactsContract.Contacts; 11 import android.provider.ContactsContract.CommonDataKinds.Email; 12 import android.provider.ContactsContract.CommonDataKinds.Phone; 13 import android.util.Log; 14 import android.view.LayoutInflater; 15 import android.view.View; 16 import android.view.ViewGroup; 17 import android.widget.BaseAdapter; 18 import android.widget.ListView; 19 import android.widget.TextView; 20 import android.widget.Toast; 21 22 public class MyTask extends AsyncTask { 23 24 private BaseAdapter adapter; 25 private MainActivity activity; 26 private ContentResolver cr; 27 private List<CallPerson> list; 28 public MyTask(MainActivity activity){ 29 this.activity = activity; 30 } 31 32 //1.所有耗时的代码,写到这里来(数据库、蓝牙、网络服务) 33 //2.绝对不能碰UI 34 @Override 35 protected Object doInBackground(Object... params) { 36 cr = (ContentResolver) params[0]; 37 int i = 0; 38 //查询联系人的id和姓名 39 Cursor c = cr.query(Contacts.CONTENT_URI, new String[]{Contacts._ID,Contacts.DISPLAY_NAME}, null, null, null); 40 List<CallPerson> l = new ArrayList<CallPerson>(); 41 if(c != null){ 42 String log = ""; 43 while(c.moveToNext()){ 44 CallPerson cp = new CallPerson(); 45 int id = c.getInt(c.getColumnIndex(Contacts._ID)); 46 String name = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME)); 47 //根据联系人id查询电话号码 48 Cursor c1 = cr.query(Phone.CONTENT_URI, new String[]{Phone.NUMBER,Phone.TYPE}, Phone.CONTACT_ID + "=" + id, null, null); 49 if(c1!=null){ 50 while(c1.moveToNext()){ 51 int type = c1.getInt(c1.getColumnIndex(Phone.TYPE)); 52 String typestr = ""; 53 if(type == Phone.TYPE_HOME){ 54 typestr = "家庭电话:"; 55 }else if(type == Phone.TYPE_MOBILE){ 56 typestr = "手机:"; 57 } 58 String phoneNum = c1.getString(c1.getColumnIndex(Phone.NUMBER)); 59 log += "_id : "+id+" 姓名 :"+ name +" "+typestr+" "+phoneNum; 60 cp.setId(id); 61 cp.setName(name); 62 cp.setPhoneNum(phoneNum); 63 cp.setType(typestr); 64 } 65 c1.close(); 66 } 67 //根据联系人的ID去查询联系人的邮箱地址 68 Cursor c2 = cr.query(Email.CONTENT_URI, new String[]{Email.DATA,Email.TYPE}, Phone.CONTACT_ID + "=" + id, null, null); 69 if(c2!=null){ 70 while(c2.moveToNext()){ 71 String typestr = "邮箱:"; 72 int type = c2.getInt(c2.getColumnIndex(Email.TYPE)); 73 if(type == Email.TYPE_WORK){ 74 typestr = "工作邮箱:"; 75 } 76 String email = c2.getString(c2.getColumnIndex(Email.DATA)); 77 log += " "+typestr+" "+email; 78 cp.setEmailtype(typestr); 79 cp.setEmail(email); 80 } 81 82 c2.close(); 83 } 84 85 l.add(cp); 86 } 87 Log.i("info", log); 88 c.close(); 89 } 90 91 92 93 return l; 94 } 95 96 //开始前准备 97 @Override 98 protected void onPreExecute() { 99 100 Toast.makeText(activity, "准备读取...", Toast.LENGTH_SHORT).show(); 101 102 } 103 104 //做完后执行 105 @Override 106 protected void onPostExecute(Object result) { 107 list = (List<CallPerson>) result; 108 //获取listView 109 ListView lv = (ListView) activity.findViewById(R.id.listView1); 110 //配置适配器 111 adapter = new BaseAdapter(){ 112 113 @Override 114 public int getCount() { 115 // TODO Auto-generated method stub 116 return list.size(); 117 } 118 119 @Override 120 public View getView(int position, View convertView, ViewGroup parent) { 121 LayoutInflater inflater = activity.getLayoutInflater(); 122 View view; 123 if (convertView==null){ 124 view = inflater.inflate(R.layout.item_view, null); 125 } 126 else{ 127 view = convertView; 128 } 129 CallPerson c = list.get(position); 130 TextView tx_id = (TextView)view.findViewById(R.id.tx_id); 131 TextView tx_name = (TextView)view.findViewById(R.id.tx_name); 132 TextView tx_ptype = (TextView)view.findViewById(R.id.tx_ptype); 133 TextView tx_phonenum = (TextView)view.findViewById(R.id.tx_phonenum); 134 TextView tx_etype = (TextView)view.findViewById(R.id.tx_etype); 135 TextView tx_email = (TextView)view.findViewById(R.id.tx_email); 136 tx_id.setText(c.getId()+""); 137 tx_name.setText(c.getName()); 138 tx_ptype.setText(c.getType()); 139 tx_phonenum.setText(c.getPhoneNum()); 140 tx_etype.setText(c.getEmailtype()); 141 tx_email.setText(c.getEmail()); 142 return view; 143 } 144 145 @Override 146 public Object getItem(int position) { 147 // TODO Auto-generated method stub 148 return null; 149 } 150 151 @Override 152 public long getItemId(int position) { 153 // TODO Auto-generated method stub 154 return 0; 155 } 156 }; 157 //给ListView设置适配器 158 lv.setAdapter(adapter); 159 Toast.makeText(activity, "读取完成...", Toast.LENGTH_SHORT).show(); 160 161 } 162 163 164 }
在MainActivity中给button绑定点击事件,调用MyTask;
1 package com.example.contentproviderdemo; 2 3 import android.app.Activity; 4 import android.content.ContentResolver; 5 import android.view.View; 6 import android.view.ViewGroup; 7 import android.view.View.OnClickListener; 8 import android.widget.BaseAdapter; 9 import android.widget.Button; 10 11 12 public class MainActivity extends Activity { 13 private Button btn; 14 private ContentResolver cr; 15 private BaseAdapter adapter; 16 @Override 17 protected void onCreate(Bundle savedInstanceState) { 18 super.onCreate(savedInstanceState); 19 setContentView(R.layout.activity_main); 20 btn = (Button) findViewById(R.id.query_btn); 21 cr = getContentResolver(); 22 btn.setOnClickListener(new OnClickListener() { 23 24 @Override 25 public void onClick(View arg0) { 26 27 MyTask mt = new MyTask(MainActivity.this); 28 mt.execute(cr);//里面的参数是传给doInBackground 29 30 31 32 } 33 }); 34 35 }
效果图如下:
首先是界面
点击获取之后,会进去子线程读取数据
完成之后效果去下:
不要问我邮箱为什么空白,因为我手机的联系人没有一个存邮箱的
强制关闭的效果:
案例就到此为止,这个案例是我学习四大组件中ContentProvider的简单应用
那么为什么这个案例会拿异步来做呢,原因上面也说了,联网读取数据, 或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭"。 这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,,Android主线程是线程不安全的, 也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。
如果你的手机联系人(也就是你要取的数据)不是很多的话,那不用异步也不会强制关闭,但数据量过大的话,那么他就会进去假死状态。。。。