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框架