项目中有一些操作比较耗时,例如AES解密,在第一次解密初始化时会用时1~2秒,这时候如果不把耗时操作放到异步任务里面,app就会出现屏幕假死、卡顿。放到异步任务一般人的做法是在每一个需要创建异步任务的地方创建一个类继承AsyncTask,把耗时操作放到doInBackground方法里面,处理完毕则在onPostExecute方法更新UI。这样的做法太繁琐,且创建出来的AsyncTask线程池不好管理,所以就考虑将异步任务封装出来,队列化管理。
首先创建ExecuteTaskManager,它是管理线程池,实际执行异步任务操作的类:
public class ExecuteTaskManager implements Runnable {
/**
* 线程执行完事儿后默认的回调类型
*/
private static final int COMMON_EXCUTE_TASK_TYPE = 0;
/**
* 线程开关
*/
public volatile boolean isRunning = false;
/**
* 是否初始化完成的开关
*/
private boolean isHasInit = false;
/**
* 默认线程池的线程数量
*/
private static final int DEFAULT_THREAD_NUM = 5;
/**
* 初始化时的线程数量
*/
private int threadNum = DEFAULT_THREAD_NUM;
/**
* 定义一个单线程的线程池,专门用来执行耗时且不需要回调的操作
*/
private static ScheduledExecutorService singlePool = null/*Executors.newSingleThreadScheduledExecutor()*/;
/**
* 定义一个大小为5的线程池(这个我们比较适合多个图片下载时使用)
*/
private static ExecutorService threadPool = null/*Executors.newFixedThreadPool(threadNum)*/;
/**
* 任务执行队列
*/
private static ConcurrentLinkedQueue allExecuteTask = null/*new ConcurrentLinkedQueue()*/;
/**
* 回调接口列表
*/
private static ConcurrentHashMap uniqueListenerList = null/*new ConcurrentHashMap()*/;
public Handler getHandler() {
return handler;
}
public int getThreadNum() {
return threadNum;
}
public boolean isHasInit() {
return isHasInit;
}
public boolean isRunning() {
return isRunning;
}
/**
* @author Rocky
* @desc 得到普通的 ExcuteTask 对象,
* 对外界开放的回调接口
*/
public interface GetExcuteTaskCallback {
void onDataLoaded(ExecuteTask task);
}
/**
* 直接把数据发送到主线程
*/
private final static Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case COMMON_EXCUTE_TASK_TYPE:
if (msg.obj != null
&& msg.obj instanceof ExecuteTask) {
ExecuteTaskManager.getInstance().doCommonHandler((ExecuteTask) msg.obj);
}
break;
}
}
};
private static ExecuteTaskManager instance = null;
private ExecuteTaskManager() {
}
public static ExecuteTaskManager getInstance() {
if (instance == null) {
synchronized (ExecuteTaskManager.class) {
if (instance == null) {
instance = new ExecuteTaskManager();
}
}
}
return instance;
}
/**
* 初始化操作,这个主要是初始化需要执行异步
* 回调任务的线程池,默认开启5个线程
*/
public void init() {
init(threadNum);
}
/**
* 初始化操作,这个主要是初始化需要执行异步
* 回调任务的线程池,可以传入线程的个数
*/
public synchronized void init(int initNum) {
if (!isHasInit) {
/**
* 初始化之后就相当于开始了线程次的运行
* 只不过如果没有任务处于等待状态
*/
isRunning = true;
if (initNum > 0) {
threadNum = initNum;
}
threadPool = Executors.newFixedThreadPool(threadNum);
singlePool = Executors.newSingleThreadScheduledExecutor();
allExecuteTask = new ConcurrentLinkedQueue<>();
uniqueListenerList = new ConcurrentHashMap<>();
/**
* 初始化需要用到的线程
*/
for (int i = 0; i < threadNum; i++) {
threadPool.execute(this);
}
isHasInit = true;
}
}
/**
* 当应用被销毁时,执行清理操作
*/
public void doDestory() {
/**
* 关闭线程开关
*/
isRunning = false;
isHasInit = false;
if (allExecuteTask != null) {
allExecuteTask.clear();
allExecuteTask = null;
}
if (uniqueListenerList != null) {
uniqueListenerList.clear();
uniqueListenerList = null;
}
if (threadPool != null) {
threadPool.shutdown();
threadPool = null;
}
if (singlePool != null) {
singlePool.shutdown();
singlePool = null;
}
}
/**
* 向任务队列中添加任务对象,添加成功后,
* 任务会自动执行,执行完事儿后,不进行任何回调操作
*
* @param task 可执行的任务对象
*/
public void newExcuteTask(ExecuteTask task) {
if (task != null) {
allExecuteTask.offer(task);
synchronized (allExecuteTask) {
allExecuteTask.notifyAll();
}
}
}
/**
* 这个方法主要是获取普通的回调数据,
* 获取成功后会把加入的 ExcuteTask 对象回调到用户界面
*
* @param task 加入的任务Task
* @param callback 任务的回调接口GetDataCallback
*/
public void getData(ExecuteTask task, GetExcuteTaskCallback callback) {
/**
* 把CallBack 接口加入列表中,用完之后移除
*/
try {
if (task != null && callback != null) {
if (task.getUniqueID() == 0) {
task.setUniqueID(task.hashCode());
}
uniqueListenerList.put(task.getUniqueID(), callback);
/**
* 开始加入任务,执行任务
*/
newExcuteTask(task);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从任务队列中移除任务对象,使其不再执行(如果任务已经执行,则此方法无效)
*
* @param task 添加的任务对象
*/
public void removeExcuteTask(ExecuteTask task) {
if (task != null) {
uniqueListenerList.remove(task.getUniqueID());
allExecuteTask.remove(task);
}
}
public void log() {
}
/**
* 清除所有的任务
*/
public void removeAllExcuteTask() {
allExecuteTask.clear();
uniqueListenerList.clear();
}
//=================================任务执行、回调、分发start============================================
/**
* 所有的异步任务都在此执行
*/
@Override
public void run() {
// TODO Auto-generated method stub
while (isRunning) {
/**
* 从allExcuteTask取任务
*/
ExecuteTask lastExecuteTask = allExecuteTask.poll();
if (lastExecuteTask != null) {
try {
/**
* 真正开始执行任务,
* 所有的耗时任务都是在子线程中执行
*/
doExcuteTask(lastExecuteTask);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
/**
* 处理异常的回调
*/
lastExecuteTask.setStatus(ExecuteTask.EXCUTE_TASK_ERROR);
if (lastExecuteTask.isMainThread()) {
Message msg = Message.obtain();
msg.what = COMMON_EXCUTE_TASK_TYPE;
msg.obj = lastExecuteTask;
handler.sendMessage(msg);
} else {
doCommonHandler(lastExecuteTask);
}
uniqueListenerList.remove(lastExecuteTask.getUniqueID());
}
} else {
try {
synchronized (allExecuteTask) {
allExecuteTask.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 根据不同的ExcuteTask,执行相应的任务
*
* 这个是真正开始执行异步任务的地方,
* 即调用需要在子线程执行的代码==>task.doTask()
*
* @param task ExcuteTask对象
*/
private void doExcuteTask(ExecuteTask task) {
ExecuteTask result = task.doTask();
/**
*
* 开始执行的Task和最后得到的Task是同一个的时候,才会进行回调,
* 否则不进行回调(保证在回调得到数据的时候知道是哪一个Task,以便进行强转)
*
*
* 没有UniqueID相当于不需要回调
*
*/
if (result != null && task == result && result.getUniqueID() != 0) {
/**
* 发送当前消息,更新UI(把数据回调到界面),
* 下面不用做任何的发送消息,
* 只在这一个地方发送就行,否者会发生错误!
*/
if (result.isMainThread()) {
Message msg = Message.obtain();
msg.what = COMMON_EXCUTE_TASK_TYPE;
msg.obj = result;
handler.sendMessage(msg);
} else {
doCommonHandler(task);
}
} else {
uniqueListenerList.remove(task.getUniqueID());
}
}
/**
* 真正的回调操作,所有的任务在这里
* 把数据回调到主界面
*
* @param task ExcuteTask对象
*/
private void doCommonHandler(ExecuteTask task) {
if (task != null && uniqueListenerList.containsKey(task.getUniqueID())) {
try {
/**
* 回调整个Task数据
* 然后可以回调方法中去直接更新UI
*/
((GetExcuteTaskCallback) uniqueListenerList.get(task.getUniqueID())).onDataLoaded(task);
/**
* 回调完成移除CallBack对象
*/
uniqueListenerList.remove(task.getUniqueID());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/**=================================任务执行、回调、分发end============================================*/
/**=================================单线程池,可以顺序,延迟执行一些任务start============================================*/
/**
* 顺序执行耗时的操作
*
* @param runnable 对象
*/
public void excute(Runnable runnable) {
singlePool.execute(runnable);
}
/**
* 顺序执行耗时的操作
*
* @param runnable 对象
* @param delay 延迟执行的时间,单位毫秒
*/
public void excute(Runnable runnable, long delay) {
singlePool.schedule(runnable, delay, TimeUnit.MILLISECONDS);
}
/**
* 顺序执行耗时的操作
*
* @param runnable 对象
* @param delay 延迟执行的时间
* @param timeUnit 时间单位
*/
public void excute(Runnable runnable, long delay, TimeUnit timeUnit) {
singlePool.schedule(runnable, delay, timeUnit);
}
public void scheduleAtFixedRate(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
singlePool.scheduleAtFixedRate(runnable, delay, period, timeUnit);
}
public void scheduleAtFixedRate(Runnable runnable, long delay, long period) {
singlePool.scheduleAtFixedRate(runnable, delay, period, TimeUnit.MILLISECONDS);
}
public void scheduleAtFixedRate(Runnable runnable, long period) {
singlePool.scheduleAtFixedRate(runnable, 0, period, TimeUnit.MILLISECONDS);
}
/**=================================单线程池,可以顺序,延迟执行一些任务end============================================*/
}
还有异步任务类,负责存储单个任务的数据,任务处理的结果可以从该类中读取。
public abstract class ExecuteTask implements Runnable, Serializable {
public static final int EXCUTE_TASK_ERROR = -1;
/**
* 这个会自动生成,不用自己设置
*/
protected int uniqueID;
/**
* 主要是用来初始化的时候传入参数,
* 然后根据不用的参数来进行异步操作
*/
@SuppressWarnings("rawtypes")
protected Map taskParam;// 内容参数
/**
* 异步操作完成之后的状态,失败、成功 or 其他
*/
protected int status;
/**
* 如果是网络请求,并且获取的数据是Json,
* 则直接可以给此字段赋值,然后在回调中get Json数据
*/
protected String json;
/**
* 这个是异步操作后,如果想把异步数据传到UI线程,
* 则可以通过此字段赋值,然后再强转得到所要的数据
*/
protected Object result;
private boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
public ExecuteTask() {
}
public int getUniqueID() {
return uniqueID;
}
public void setUniqueID(int uniqueID) {
this.uniqueID = uniqueID;
}
@SuppressWarnings("rawtypes")
public Map getTaskParam() {
return taskParam;
}
@SuppressWarnings("rawtypes")
public void setTaskParam(Map taskParam) {
this.taskParam = taskParam;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public boolean isMainThread() {
return isMainThread;
}
public void setIsMainThread(boolean isMainThread) {
this.isMainThread = isMainThread;
}
@Override
public void run() {
doTask();
}
/**
* 专门用来执行耗时的操作,
* 子类只需要继承此类,实现此方法,
* 在这个方法中执行所有耗时的操作
* 用ExcuteTaskManager进行执行,
* 可以回调, 也可以不回调
*
* 在继承此类的时候 doTask
* return null(不再回调) or return this(会回调)
*
* @return
*/
public abstract ExecuteTask doTask();
}
然后将自己的业务逻辑放到自定义的异步任务中,
public class AESDecryptTask extends ExecuteTask {
private String forDecryptStr;
public String getForDecryptStr() {
return forDecryptStr;
}
public void setForDecryptStr(String forDecryptStr) {
this.forDecryptStr = forDecryptStr;
}
@Override
public ExecuteTask doTask() {
try {
String str = AESUtil.decrypt(forDecryptStr); //这是比较耗时的AES解密方法,主要是初始化的步骤比较耗时
setResult(str);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return this;
}
}
为了方便使用,把创建异步任务的逻辑放到工具类里面,
public static AESDecryptTask decryptAsync(String forDecryptStr, ExecuteTaskManager.GetExcuteTaskCallback callback){
AESDecryptTask task = new AESDecryptTask();
task.setForDecryptStr(forDecryptStr);
ExecuteTaskManager.getInstance().getData(task, callback);
return task;
}
需要调用的时候直接调用工具类方法,它会返回异步任务示例本身,方便做一些销毁操作,在callback里面写更新UI的逻辑:
decryptTask = AESUtil.decryptAsync(user.phone, new ExecuteTaskManager.GetExcuteTaskCallback() {
@Override
public void onDataLoaded(ExecuteTask task) {
try {
String result = (String) task.getResult();
if (TextUtils.isEmpty(result)){
result = "";
}
selectedUserPhone = result;
String userName = StringUtil.hideSeveralNumber(selectedUserPhone);
et_userName.setText(userName);
et_userName.setSelection(userName.length());
et_userName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (isAutoComplete && !isSelectedFromHint) {
//如果当前用户名为从下拉列表选择,用户对用户名进行编辑,则清空输入框
s.clear();
iv_clearUserName.setVisibility(View.INVISIBLE);
iv_userPhoto.setImageResource(R.mipmap.avatar);
selectedUserPhone = "";
isAutoComplete = false;
} else {
//如果当前用户名为正常输入,则进行是否显示清空按钮的处理
String text = s.toString();
if (!TextUtils.isEmpty(text)) {
iv_clearUserName.setVisibility(View.VISIBLE);
} else {
iv_clearUserName.setVisibility(View.INVISIBLE);
}
}
isSelectedFromHint = false;
//每当输入内容发生变化,都调用此方法,判断登录按钮是否可点击
checkLoginButtonEnable();
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
enableUsernameEditText();
}
}
});
为了避免退出Activity后异步任务还在执行,而重新进入Activity异步任务又会重复执行,在onDetroy方法中销毁当前的异步任务:
@Override
protected void onDestroy() {
......
ExecuteTaskManager.getInstance().removeExcuteTask(decryptTask);
super.onDestroy();
}
这样写的好处是异步任务进一步抽象,代码比较简洁,由统一的线程池集中管理,在销毁时可以很容易地取消正在执行的异步任务。