AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。这里Android已经帮我们封装好了AsyncTask(AsyncTask是对Thread+Handlerde 的封装),即使你对异步消息处理机制完全不了解,也可以简单的从子线程切换到主线程。
AsyncTask是一个抽象类。那么要使用AsyncTask,我们需要写一个类去继承它;在继承时我们可以为AsyncTask类指定3个泛型参数,分别为Params(开始时),Progress(执行时)和Result(结束后)。
Params:启动任务执行的输入参数,如HTTP请求的URL。
Progress:后台任务执行的百分比。后台任务执行时,如果需要对在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
Result:后台执行任务最终返回的结果类型。
例如:如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替
class MyAsyncTask extends AsyncTask<Params, Progress, Result>{...}
我们来一个完整的简单例子:
//创建一个类继承AsyncTask并设置好泛型参数 class demoAsyncTask extends AsyncTask<Void,Intent,Boolean>{ /** * 该方法在主线程中执行,将在execute(Params… params)被调用后 * 执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。 */ @Override protected void onPreExecute() { progressDialog.show();//显示进度条对话框 } /** * 抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务, * 将在onPreExecute方法执行后执行。其参数是一个可变类型, * 表示异步任务的输入参数,在该方法中还可通过 * publishProgress(Progress… values)来更新实时的任务进度, * 而publishProgress方法则会调用onProgressUpdate方法。 * 此外doInBackground方法会将计算的返回结果传递给 * onPostExecute方法。 * */ @Override protected Boolean doInBackground(Void... params) { try { while (true){ int downloadPercent=doDownload(); publishProgress(downloadPercent); if (downloadPercent>=100){ break; } } }catch (Exception e){ return false; } return null; } /** *在主线程中执行,该方法在publishProgress(Progress… values) *方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。 * @param values */ @Override protected void onProgressUpdate(Intent... values) { //在这里更新下载进度 progressDialog.setMessage("Download"+values[0]+"%"); } /** *在doInBackground 执行完成后,onPostExecute 方法将被UI线程 *调用,doInBackground 方法的返回值将作为此方法的参数传递到 *UI线程中,并执行一些UI相关的操作,如提醒任务执行的结果, *以及关闭进度条对话框等等。 * @param aBoolean */ @Override protected void onPostExecute(Boolean aBoolean) { progressDialog.dismiss();//关闭进度对话框 //在这里提示下载结果 if (aBoolean){ Toast.makeText(MyAsyncTask.this, "aBoolean", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(MyAsyncTask.this,"failed", Toast.LENGTH_SHORT).show(); } } }
启动任务只需编写:new demoAsyncTask().execute();
//使用AsyncTask的诀窍:在doInBackground方法中执行具体的耗时操作, //在onProgressUpdate方法中进行UI操作, //在onPostExecute方法中执行一些任务的收尾工作
注意:老司机们提醒。为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI thread中创建
2) execute方法必须在UI thread中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground 接受的参数,第二个为显示进度的参数,第三个为doInBackground返回和onPostExecute传入的参数。
现在,我们来思考一些问题,Android异步线程AsyncTask我们学得差不多了,想想,AsyncTask+Okhttp(当然,AsyncTask还可以和其他网络技术合作,比如AsyncTask+HttpURLConnection等等,这里强烈推荐使用AsyncTask+OkHttp)实现异步下载图片。首先要考虑AsyncTask的三个泛型参数是什么数据类型?AsyncTask单独使用的时候需要传入url,Okhttp单独使用也是需要传入url,那么它俩合作,url作为参数传给谁去完成任务呢?还有具体的实现细节,它俩之间的数据通过什么方式方法来传递?先来个简单的例子:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="AsyncTask+OkHttp网络下载图片" android:id="@+id/button" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/imageView" /> </LinearLayout>
Java代码:
public class MainActivity extends AppCompatActivity { private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg"; private Button mButton; private ImageView mImageView; private Bitmap bitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView=(ImageView) findViewById(R.id.imageView); mButton=(Button) findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在UI Thread当中实例化AsyncTask对象,并调用execute方法 new MyAsyncTask().execute(url); } }); } public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{ @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Bitmap doInBackground(String... params) { try { OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() .url(url).build(); Response response=client.newCall(request).execute(); InputStream is=response.body().byteStream(); bitmap= BitmapFactory.decodeStream(is); is.close(); }catch (Exception e){ e.printStackTrace(); } return bitmap; } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); mImageView.setImageBitmap(bitmap); } } }
效果图:
差点忘了说:记得申请权限
<!-- 授权手机能够访问网络 --> <uses-permission android:name="android.permission.INTERNET" />
还有使用OkHttp需要下载它的两个依赖包:在build.gradle(Module:app) 中的dependencies添加{
compile 'com.squareup.okhttp3:okhttp:3.4.1'
}
栗子中我们只用到了两个方法,doInBackground和onPostExecute,还有两个方法没有用到,还得继续优化栗子,增加一个下载提示框提示用户图片正在下载中。布局不变,下面贴出Java代码:
public class MainActivity extends AppCompatActivity { private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg"; private Button mButton; private ImageView mImageView; private Bitmap bitmap; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView=(ImageView) findViewById(R.id.imageView); //弹出要给ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("提示信息"); progressDialog.setMessage("正在下载中,请稍后......"); //设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失 progressDialog.setCancelable(false); //设置ProgressDialog样式为圆圈的形式 progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); mButton=(Button) findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在UI Thread当中实例化AsyncTask对象,并调用execute方法 new MyAsyncTask().execute(url); } }); } public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{ @Override protected void onPreExecute() { super.onPreExecute(); //在onPreExecute()中我们让ProgressDialog显示出来 progressDialog.show(); } @Override protected Bitmap doInBackground(String... params) { try { OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() .url(url).build(); Response response=client.newCall(request).execute(); InputStream is=response.body().byteStream(); bitmap= BitmapFactory.decodeStream(is); is.close(); }catch (Exception e){ e.printStackTrace(); } return bitmap; } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); //更新我们的ImageView控件 mImageView.setImageBitmap(bitmap); //使ProgressDialog框消失 progressDialog.dismiss(); } } }
效果图:
再继续优化栗子:这次我们用到更新UI的功能,一样的布局没变,下面贴Java代码:
public class MainActivity extends AppCompatActivity { private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg"; private Button mButton; private ImageView mImageView; private Bitmap bitmap; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView=(ImageView) findViewById(R.id.imageView); //弹出要给ProgressDialog progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setTitle("提示信息"); progressDialog.setMessage("正在下载中,请稍后......"); //设置setCancelable(false);表示我们不能取消这个弹出框, // 等下载完成之后再让弹出框消失 progressDialog.setCancelable(false); // //设置ProgressDialog样式为圆圈的形式 // progressDialog.setProgressStyle(ProgressDialog // .STYLE_SPINNER); //设置ProgressDialog样式为水平的样式 progressDialog.setProgressStyle(ProgressDialog .STYLE_HORIZONTAL); mButton=(Button) findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在UI Thread当中实例化AsyncTask对象,并调用execute方法 new MyAsyncTask().execute(url); } }); }/** * 定义一个类,让其继承AsyncTask这个类 * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径 * Progress: Integer类型,进度条的单位通常都是Integer类型 * Result:Bitmap类型,表示我们下载好的图片以Bitmap返回 * @author xiaoluo * */ public class MyAsyncTask extends AsyncTask<String,Integer ,Bitmap>{ @Override protected void onPreExecute() { super.onPreExecute(); //在onPreExecute()中我们让ProgressDialog显示出来 progressDialog.show(); } @Override protected Bitmap doInBackground(String... params) { try { OkHttpClient client=new OkHttpClient(); Request request=new Request.Builder() .url(url).build(); Response response=client.newCall(request).execute(); InputStream is=response.body().byteStream(); bitmap= BitmapFactory.decodeStream(is); //每次读取后累加的长度 long file_length = 0; int length = 0; //每次读取1024个字节 byte[] data = new byte[1024]; while (-1!=(length=is.read(data))){ //每读一次,就将file_length累加起来 file_length += length; //得到当前图片下载的进度 int mData=((int) (file_length/is.read())*100); publishProgress(mData); } is.close(); }catch (Exception e){ e.printStackTrace(); } return bitmap; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //更新ProgressDialog的进度条 progressDialog.setProgress(values[0]); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); //更新我们的ImageView控件 mImageView.setImageBitmap(bitmap); //使ProgressDialog框消失 progressDialog.dismiss(); } } }
效果图:
在写这个例子的时候,还是学到了很多基础的知识,说实话,有时候你不动手打代码,你都不知道自己有些知识点依然懵懂。例子涉及的知识点,本人也去好好巩固,下面分享一些知识:
/** * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。 * 所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件 * 的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件 *(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列 */
/** * 把Bitmap转Byte */
public static byte[] Bitmap2Bytes(Bitmap bm){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); }
//android将图片内容解析成字节数组,将字节数组转换为 // ImageView可调用的Bitmap对象, //图片缩放,把字节数组保存为一个文件,把Bitmap转Byte
/** * @param 图片缩放 * @param bitmap 对象 * @param w 要缩放的宽度 * @param h 要缩放的高度 * @return newBmp 新 Bitmap对象 */
public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h){ int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); float scaleWidth = ((float) w / width); float scaleHeight = ((float) h / height); matrix.postScale(scaleWidth, scaleHeight); Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); return newBmp; }
/** * 将字节数组转换为ImageView可调用的Bitmap对象 */ public static Bitmap getPicFromBytes(byte[] bytes,BitmapFactory.Options opts) { if (bytes != null) if (opts != null) return BitmapFactory.decodeByteArray(bytes, 0, bytes.length,opts); else return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return null; }
/** * @param 将图片内容解析成字节数组 * @param inStream * @return byte[] * @throws Exception */ public static byte[] readStream(InputStream inStream) throws Exception { byte[] buffer = new byte[1024]; int len = -1; ByteArrayOutputStream outStream = new ByteArrayOutputStream(); while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } byte[] data = outStream.toByteArray(); outStream.close(); inStream.close(); return data; }
从资源中获取Bitmap Java代码 Resources res=getResources(); Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic); Bitmap → byte[] Java代码 private byte[] Bitmap2Bytes(Bitmap bm){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); } byte[] → Bitmap Java代码 private Bitmap Bytes2Bimap(byte[] b){ if(b.length!=0){ return BitmapFactory.decodeByteArray(b, 0, b.length); } else { return null; } } */
希望这些知识点对大家有帮助!
如果大家对OkHttp还陌生的话,请看另一篇博客:Android网络技术之OkHttp框架