一,背景
正确的掌握Android异步编码能有效提高APP的性能和体验,但同时也带来了更多的编码工作,而如果编码不当,不仅使得代码难于维护,还可能引入内存开销等问题。
二,为什么需要异步机制
1, 基于Android系统机制要求
l 不要阻塞UI线程(即主线程),因此非UI任务尽可能的走子线程;
l 不要在UI线程之外访问UI组件,因此子线程的执行结果需要抛转到UI线程。
2, 基于性能响应和用户体验
l 通过多线程提升业务处理和响应能力
l 避免UI线程的高负荷带来的卡顿甚至阻塞,提升交互体验
三,什么时候需要到异步加载
理论上,只要有耗时的任务,必须要抛到工作线程中去执行;然后把在工作线程中执行的任务结果返回到UI线程中去更新组件。
比较密集的场景包括APP的启动,组件的初始化,用户交互业务中有耗时请求,特别是有网络、文件、DB等访问场景时。
四,JDK、SDK典型异步机制
1,Thread、Runnable结合主Looper的handler
最常见最原始的异步操作方式
示例:
private void test(){
new Thread(mRunnable).start();
}
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
if(msg.what == 0x123){
textView.setText("Task Done!!");
}
}
};
mRunnable = new Runnable() {
@Override
public void run() {
SystemClock.sleep(5000); //模拟耗时处理
mHandler.sendEmptyMessage(0x123);
}
};
优点:
l 操作简单,基本无学习成本
缺点:
l 代码规范性较差,不易维护
l 每次都开辟一个线程,开启新线程的以及调度多个线程的开销是非常大的,这往往会带来严重的性能问题,在实际开发中,该方式是很不被推荐的。
2,Runnable结合View.post、Activity.runOnUIThread
这其实是第一种异步方式的扩展,差别就是这种方法不需要去定义Handler,优缺点雷同。
示例:
只需要将上述的
mHandler.sendEmptyMessage(0x123);
改成:
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
text.post(new Runnable() {
@Override
public void run() {
text.setText("Task Done!!");
}
});
3,AsyncTask
较为轻量级的异步类,封装了FutureTask的线程池、LinkedBlockingQueue和handler进行调度。
可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度,最后反馈执行的结果给UI主线程。
基本使用方法,doInBackground(Params…)方法里执行耗时逻辑,然后在onPostExecute(Result) 中将结果更新回UI组件。
示例:
/**
* 生成该类的对象,并调用execute方法之后
* 首先执行的是onProExecute方法
* 其次执行doInBackgroup方法
*
*/
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {
private TextViewtextView;
private ProgressBar progressBar;
public ProgressBarAsyncTask(TextView textView, ProgressBarprogressBar) {
super();
this.textView= textView;
this.progressBar= progressBar;
}
/**
* 这里的Integer参数对应AsyncTask中的第一个参数
* 这里的String返回值对应AsyncTask的第三个参数
* 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
* 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
*/
@Override
protected StringdoInBackground(Integer... params) {
NetOperator netOperator = new NetOperator();
int i = 0;
for (i = 10; i <= 100; i+=10) {
netOperator.operator();
publishProgress(i);
}
return i +params[0].intValue() + "";
}
/**
* 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
* 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
*/
@Override
protected void onPostExecute(String result){
textView.setText("异步操作执行结束" +result);
}
//该方法运行在UI线程当中,并且运行在UI线程当中可以对UI空间进行设置
@Override
protected void onPreExecute() {
textView.setText("开始执行异步线程");
}
/**
* 这里的Intege参数对应AsyncTask中的第二个参数
* 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
* onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
*/
@Override
protected void onProgressUpdate(Integer...values) {
int vlaue= values[0];
progressBar.setProgress(vlaue);
}
}
优点:
l 结构清晰,功能定义明确,尤其适用后台进度任务的交互
缺点:
l 在单个后台异步处理时,显得代码过多,结构过于复杂
l Task实例只能被执行一次,否则多次调用时将会出现异常
4,HandlerThread
HandlerThread可以创建一个带有looper的线程,looper对象可以用于创建Handler类来进行来进行调度,其中创建looper时,start()方法必须先被调用。
创建出来的Handler对象,使用方法同主线程Handler使用方式一样,但此时handleMessage()方法执行在子线程中。
异步交互的一般方式是,在构造传入一个UIHandler对象,将数据返回主线程
示例:
handlerThread = new HandlerThread("MyWorkThread");//自定义线程名称
handlerThread.start();
mWorkHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
if (msg.what== 0x123){
try {
Log.d("HandlerThread", Thread.currentThread().getName());
Thread.sleep(5000);//模拟耗时任务
} catch (InterruptedExceptione) {
e.printStackTrace();
}
}
}
};
优点:
l 开销较少,仅一个Thread体量可以连续处理多个后台任务
l 兼有handler和Thread的优点,适合队列性中轻量的子线程任务
缺点:
l 没有提供返回主线程的接口,需要自行封装
l 通常需要交叉维护多个handler
5,IntentService
内部封装了HandlerThread的实现,同时Service的子类,用法跟Service也差不多,实现耗时逻辑应放在onHandleIntent(Intent intent){}的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,而且当任务执行完后会自动停止,无须手动去终止它。
示例:
实现onHandleIntent方法即可,具体略。
优点:
l 使用方式熟悉
l 兼有HandlerThread和Service的优点,适合单个重度分叉性的后台任务
缺点:
l 没有提供返回主线程的接口,不适合异步交互
l 需要像Service一样在清单配置,开销体量较大
6,AsyncQueryHandler
在一般的应用中我们可以使用ContentProvider去操作其它进程的数据库,为了避免ANR,还需要配合上述的异步任务机制,但Android已经为此封装了异步查询框架AsyncQueryHandler。
AsyncQueryHandler类封装了调用者线程与工作线程的交互过程。交互的主体是两个Handler,一个运行在调用者线程中,一个运行在工作者线程中。通过提供onXXXComplete的回调接口,返回主线程实现事件的完成处理。
示例:以查询为例:
定义自己的AsyncQueryHandler,同时定义一个监听器进行设置。
/** * 异步的查询操作帮助类,可以处理增删改(ContentProvider提供的数据) */ public class CommonQueryHandler extends AsyncQueryHandler { public CommonQueryHandler(ContentResolver cr) { super(cr); } /** * 当查询完成后,回调的方法 */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { //触发监听事件 if(cursorChangedListener!=null){ cursorChangedListener.onCursorChanged(token, cookie, cursor); } } public OnCursorChangedListener getCursorChangedListener() { return cursorChangedListener; } public void setOnCursorChangedListener(OnCursorChangedListener cursorChangedListener) { this.cursorChangedListener = cursorChangedListener; } private OnCursorChangedListener cursorChangedListener; /** * 定义cursor改变时的事件监听 * @author leo * */ public interface OnCursorChangedListener{ void onCursorChanged(int token, Object cookie, Cursor cursor); } }
调用:
使用时直接调用startXXX方法即可。
new CommonQueryHandler(getContentResolver()).startQuery(token, cookie, uri, projections, selection selectinArgs, orderBy);
传入的通用参数如下:
l Int token,一个令牌,主要用来标识查询,保证唯一即可。
l Object cookie,你想传给onXXXComplete方法使用的一个对象。(没有的话传递null即可)
l Uri uri(进行查询的通用资源标志符):
l String[] projections 查询的列
l String selection 限制条件
l String[] selectionArgs 查询参数
l String orderBy 排序条件
优点:
l 实现了异步调度的封装
l 接口简单易用
缺点:
l 封装了ContentResolver,应用场景定向,只适用于数据库操作
五,异步线程的抉择
根据上述,我们需要根据不同异步方式优缺点和具体的使用场景进行抉择,一般来说:
1, 大量分散性的轻量化异步任务,可以考虑对Runnable进行线程池的统一管理使用;
2, 频率不高,比较重度,且有进度交互的,可以考虑使用AsyncTask,同时注意执行时,调用其并发性执行接口;
3, 中轻度队列性子任务,考虑使用HandlerThread;
4, 分叉性的重度后台任务,考虑使用IntentService;
5, 涉及ContentProvider的数据库操作,考虑使用AsyncQueryHandler。
六,封装与扩展
对于上述策略和原有的异步类,往往不足以使用,我们一般还需要进一步的封装,其中最常见的包括对HandlerThread执行接口的封装,Runnable和全局线程池的封装。
1,HandlerThread使用进阶
思路:
l HandlerThread最简单的使用方法如普通主线程handler一般,但为了维护代码风格的一致性,不建议在一个组件中显性的操作多个handler。
l 为了维护编码结构,作为一个逻辑处理类,应该转化为一般性的业务接口供外部组件进行调用。
示例:
protected void initWorkerThread() {
mDBHandlerThread = new DBHandlerThread("Handler Thread");
mDBHandlerThread.setUIHandler(mUIHandler);
mDBHandlerThread.setWorkCallBack(mThreadCallback);
//start()后会执行Thread的run()方法,回调onLooperPrepared()
mDBHandlerThread.start();}
public class DBHandlerThread extends HandlerThreadimplements Handler.Callback {
public static final int MSG_QUERY_FRIENDS = 100;
private Handler mWorkerHandler; //与工作线程相关联的Handler
private WorkCallbackmWorkback; //提供回调接口
private HandlermUIHandler; //与UI线程相关联的Handler
public DBHandlerThread(String name) {
super(name);
}
public DBHandlerThread(String name, int priority){
super(name, priority);
}
public void setUIHandler(Handlerhandler) {
this.mUIHandler = handler;
}
public void setWorkCallBack(WorkCallbackcb) {
this.mWorkback = cb;
}
public HandlergetWorkerHandler() {
return mWorkerHandler;
}
@Override
protected void onLooperPrepared() {
mWorkerHandler = new Handler(getLooper(), this);
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_QUERY_FRIENDS:
//...查询数据库操作...
if (mUIHandler!= null) {
//回调到主线程
mUIHandler.post(new Runnable() {
@Override
public void run() {
if (mWorkback!= null) {
mWorkback.onQueryFriendsCallback("Friends");
}
}
});
}
break;
}
return true;
}
/**
* 最终提供给外部组件的接口
*/
public void queryFriends() {
Message msg = Message.obtain(null, MSG_QUERY_FRIENDS);
mWorkerHandler.sendMessage(msg);
}
/**
* 释放资源的接口
*/
public void release(){
mUIHandler = null;
this.quit();
}
/**
* 自定义的回调
*/
interface WorkCallback{
void onQueryFriendsCallback(Object object);
void onUpdateFriendesCallback();
}
}
2,Runnable轻量级异步调度类的封装
思路:
l 维持Runnable的易用性
l 对线程开辟使用线程池统一管理
l 构建类似于AsyncTask类的交互接口,方便调用
l 对可能存在的线程强持外部实例的问题,使用弱引用关系进行维护
示例:
封装扩展之后的工具类如下:
单后台任务示例:
private staticclass MyBackTask extendsBackTask<MainMvpActivity, Void> {
MyBackTask() {
}
MyBackTask(MainMvpActivityactivity) {
super(activity);
}
@Override
protected VoiddoInTheBack() {
// 子线程
return null;
}
@Override
protected void doneInTheBack(MainMvpActivityoutInstance, Void doneValue) {
// 子线程
}
}
异步交互示例:
private static class AsyncImageTaskextends BackForeTask<MainMvpActivity, ImageEntity>{
AsyncImageTask(MainMvpActivity activity) {
super(activity);
}
@Override
protected ImageEntitydoInBack() {
// 子线程
return null;
}
@Override
protected void postedToFore(MainMvpActivityoutInstance, ImageEntity postedValue) {
// 主线程
}
}
}