android开发:AsyncTask深入解析

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类的启动代码:

QQ截图20160604151242

 

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类的的源代码(代码中已经做了详细的解释,这里就不细说了):
QQ截图20160604151228

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>

运行的效果图:

QQ截图20160604151303

 

写给有需要的人。

Leave a Comment