AsyncTask介绍
Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。
首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。
Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。
AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
- Params 启动任务执行的输入参数,比如HTTP请求的URL。
- Progress 后台任务执行的百分比,一般是整数型。
- Result 后台执行任务最终返回的结果,比如File。
使用过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只能被执行一次,否则多次调用时将会出现异常;
今天写了一段下载文件的AsyncTask类,以下是MainActivity类的启动代码:
package com.lanxin.testasynctask; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends AppCompatActivity { private static final String TAG = "AsyncTask"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; }else if(id == R.id.action_down){ //实例化一个AsyncTask类,并使用execute执行一个MP3下载任务; AsyncTaskEx ate = new AsyncTaskEx(this,"文件下载","下载中...",false,true); ate.execute("http://192.168.1.205/1.mp3"); return true; } return super.onOptionsItemSelected(item); } }
以下是AsyncTaskEx类的的源代码(代码中已经做了详细的解释,这里就不细说了):
package com.lanxin.testasynctask; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; /** * 后台异步执行类 * 请在执行的Acitivity的Manifest添加:android:configChanges="screenSize|orientation",否则当屏幕旋转时ProgressDialog会自动消失 * 权限申请: * <uses-permission android:name="android.permission.INTERNET"></uses-permission> * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> * <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission> * Created by Alan on 2016/6/3 0003. */ public class AsyncTaskEx extends AsyncTask<String,Integer,File> implements DialogInterface.OnCancelListener { private static final String TAG = "AsyncTaskEx"; private Context mContext; private ProgressDialog pd; private boolean Indeterminate;//是否模糊的 private boolean is_auto_file = false; private String PDTitle = "AsyncTask"; private String PDMsg = "请稍后..."; private static File _root; private static String _file; private static String filename; /** * 初始化 * @param ctx Activity上下文 * @param title ProgressDialog的标题 * @param msg ProgressDialog的显示信息 * @param isIndeterminate 是否模糊的,当为false时,会显示长条带百分比的进度条 * @param isopen 是否打开文件 */ public AsyncTaskEx(Context ctx,String title,String msg,boolean isIndeterminate,boolean isopen){ mContext = ctx; Indeterminate = isIndeterminate; if(title != null && !"".equals(title)) PDTitle = title; if(msg != null && !"".equals(msg)) PDMsg = msg; is_auto_file = isopen; _root = Environment.getExternalStorageDirectory(); _file = "/Download/"+ctx.getPackageName()+"/Download/"; } /** * 开始执行 * 这里初始化对话框 */ protected void onPreExecute() { pd = new ProgressDialog(mContext); pd.setIndeterminate(Indeterminate); pd.setCanceledOnTouchOutside(false); if(!Indeterminate) pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setCancelable(true); pd.setOnCancelListener(this); pd.setTitle(PDTitle); pd.setMessage(PDMsg); pd.setMax(100); pd.setProgress(0); pd.show(); Log.i(TAG, "AsyncTask Start"); } /** * 进度更新 * @param values 进度值 整数 */ protected void onProgressUpdate(Integer... values) { pd.setProgress(values[0]); if(Indeterminate) pd.setMessage(PDMsg+"..."+ values[0] + "%"); Log.i(TAG, "AsyncTasking..." + values[0]); } protected void onPostExecute(File result) { pd.setMessage("下载完毕。"); if(result != null && is_auto_file){ openTheFile(result); } pd.dismiss(); Log.i(TAG, "AsyncTask Complete:" + result.getAbsolutePath()); } /** * 执行后台任务 * @param params 传递过来的参数数组 * @return */ @Override protected File doInBackground(String... params) { Log.i(TAG,"doInBackgrounding"); return doDownFile(params[0]); } /** * 下载文件 * @param urls * @return */ private File doDownFile(String urls){ if(urls == null || "".equals(urls)) return null; filename = urls.substring(urls.lastIndexOf("/"));//example:DCS_0001.JPG filename = this.checkFileExists(getDownloadDir(),filename); try { URL url = new URL(urls); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.connect(); if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){ int length = conn.getContentLength(); int length_target = 0; int length_tmp = 0; InputStream in = conn.getInputStream(); FileOutputStream out = new FileOutputStream(new File(getDownloadDir() + filename)); byte[] b = new byte[1024*1024]; while ((length_tmp = in.read(b)) > 0){ length_target += length_tmp; int progress = (length_target * 100) / length ; publishProgress(progress);//更新进度 out.write(b, 0, length_tmp); } in.close(); out.flush(); out.close(); return new File(getDownloadDir()+filename); }else{ throw new MalformedURLException("Error Code " + conn.getResponseCode()); } } catch (MalformedURLException e) { e.printStackTrace(); Log.e(TAG,"MalformedURLException:" + e.getMessage()); }catch (IOException e) { e.printStackTrace(); Log.e(TAG,"IOException:" + e.getMessage()); } return null; } /** * 绑定取消,当用户点击返回键时,AsyncTask任务取消 * @param dialog */ @Override public void onCancel(DialogInterface dialog) { this.cancel(true); File file = new File(getDownloadDir()+filename); if(file.exists()) file.delete(); Log.i(TAG, "AsyncTask Cancel"); } /** * 取下载目录,默认目录是Download/PackageNmae(?)/Download目录 * @return */ public static String getDownloadDir(){ checkDownloadDir(_root + _file); return _root+_file; } /** * 检查下载目录是否存在,如果不存在则自动创建 * @param path * @return */ public static boolean checkDownloadDir(String path){ if("".equals(path)) return false; File file = new File(_root+_file); if(!file.exists()){ return file.mkdirs(); } return true; } /** * 检查一个文件是否存在,如果醋在,曾返回一个新的文件名 * @param path 路径 * @param filename 文件名 * @return */ public static String checkFileExists(String path,String filename){ if(!path.substring(path.length()-1).equals("/")){ path += "/"; } File file = new File(path + filename); if(!file.exists()){ return filename; }else{ String extend = getFileExtend(filename); String newName = filename.replace(extend, ""); int f = 1; File newFile = new File(path + newName + "(" + f + ")"+extend); while (newFile.exists()){ f++; newFile = new File(path + newName + "(" + f + ")"+extend); } return newName + "(" + f + ")"+extend; } } /** * 取文件的后缀名 * @param filename * @return 返回类似: .jpg 小写字符串 */ protected static String getFileExtend(String filename) { return filename.substring(filename.lastIndexOf(".")).toLowerCase(); } /** * 判断数组是否存在某个值 * @param arr 数组 * @param name 要判断的字符串 * @return */ protected static boolean inArray(String[] arr, String name) { return Arrays.asList(arr).contains(name); } /** * 打开文件 * @param file */ public void openTheFile(File file){ Intent intent = new Intent(); String fileName = file.getAbsolutePath(); String[] imgs = {"image/*",".png",".jpg",".jpeg",".gif",".bmp"}; String[] videos = {"video/*",".mp4",".3gp",".avi",".flv",".wmv",".rmvb",".asf",".mkv",".mpg"}; String[] audios = {"audio/*",".mp3",".ogg",".ape",".wav",".wma"}; if(inArray(imgs,getFileExtend(fileName))){ intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "image/*"); mContext.startActivity(intent); }else if(inArray(videos,getFileExtend(fileName))){ intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "video/*"); mContext.startActivity(intent); }else if(inArray(audios,getFileExtend(fileName))){ intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "audio/*"); mContext.startActivity(intent); }else if(fileName.endsWith(".apk")){ intent.setAction("android.intent.action.VIEW"); intent.addCategory("android.intent.category.DEFAULT"); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); mContext.startActivity(intent); } } }
最后就是申请权限了,如果没有执行这步,会报错的:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lanxin.testasynctask" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="screenSize|orientation" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission> </manifest>
运行的效果图:
写给有需要的人。