ANR是Android系统对程序无响应的异常响应,从Android系统的角度来看,发生ANR的原因主要有三个方面:
1. Activity/Fragment在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸);
2. BroadcastReceiver在10秒内没有执行完毕;
3. Service的生命周期方法中有20s以上耗时。
从开发编码的角度来看,主要可能发生的场景包括:
l 耗时的网络访问
l 在主线程中解析网络返回的数据
l 在主线程中进行加解密(编解码)运算
l 遍历资源文件Assert
l 大量的数据读写
l 繁重的数据库操作
l 硬件操作(比如camera)
l service binder的数量达到上限
l 其他线程持有锁,导致主线程等待超时
以上,对于锁资源的竞争需要从具体编码审查,binder资源的系统有限制不作为本章的讨论内容,而绝大部分的场景下,ANR的发生都可以归结为主线程的耗时操作。
从严格意义上来说,所有的耗时操作,即使不会导致ANR,也需要放到工作线程中执行,即采用异步任务的方式来实现。以下将对Android中的各种异步工作线程进行阐述。
最常见最原始的异步操作方式
示例:
private void test(){
new Thread(mRunnable).start();
}
Handler mHandler = newHandler(){
@Override
publicvoid handleMessage(Message msg){
if(msg.what == 0x123){
textView.setText("Task Done!!");
}
}
};
mRunnable = new Runnable() {
@Override
publicvoid run() {
SystemClock.sleep(5000); // 模拟耗时处理
mHandler.sendEmptyMessage(0x123);
}
};
优点:
l 操作简单,基本无学习成本
缺点:
l 代码规范性较差,不易维护
l 每次都开辟一个线程,开启新线程的以及调度多个线程的开销是非常大的,这往往会带来严重的性能问题,在实际开发中,该方式是很不被推荐的。
这其实是第一种异步方式的扩展,差别就是这种方法不需要去定义Handler,优缺点雷同。
示例:
只需要将上述的
mHandler.sendEmptyMessage(0x123);
改成:
runOnUiThread(new Runnable() {
@Override
publicvoid run() {
text.setText("Task Done!!");
}
});
text.post(new Runnable(){
@Override
publicvoid run() {
text.setText("Task Done!!");
}
});
备注:根据同学的实践反馈,使用view.post()方式进行线程调度时,在个别电视机型曾出现不执行的情况,换handler后表现正常,初步推测是底层框架对该接口进行了修改,在此标记为有风险的接口,不建议使用。
较为轻量级的异步类,封装了FutureTask的线程池、LinkedBlockingQueue和handler进行调度。
可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度,最后反馈执行的结果给UI主线程。
基本使用方法,doInBackground(Params…) 方法里执行耗时逻辑,然后在onPostExecute(Result)中将结果更新回UI组件。
示例:
/**
* 生成该类的对象,并调用execute方法之后
* 首先执行的是onProExecute方法
* 其次执行doInBackgroup方法
*
*/
public class ProgressBarAsyncTask extends AsyncTask{
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) {
If(!isCancel()){
int vlaue =values[0];
progressBar.setProgress(vlaue);
}
}
优点:
l 结构清晰,功能定义明确,尤其适用后台进度任务的交互
缺点:
l 在单个后台异步处理时,显得代码过多,结构过于复杂
l Task实例只能被执行一次,否则多次调用时将会出现异常
l AsyncTask在整个Android系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。
HandlerThread可以创建一个带有looper的线程,looper对象可以用于创建Handler类来进行来进行调度,其中创建looper时,start()方法必须先被调用。
创建出来的Handler对象,使用方法同主线程Handler使用方式一样,但此时handleMessage()方法执行在子线程中。
异步交互的一般方式是,在构造传入一个UIHandler对象,将数据返回主线程
示例:
handlerThread = newHandlerThread("MyWorkThread");//自定义线程名称
handlerThread.start();
mWorkHandler = new Handler(handlerThread.getLooper()){
@Override
publicvoid handleMessage(Message msg){
if (msg.what== 0x123){
try{
Log.d("HandlerThread",Thread.currentThread().getName());
Thread.sleep(5000);//模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
优点:
l 开销较少,仅一个Thread体量可以连续处理多个后台任务
l 兼有handler和Thread的优点,适合队列性中轻量的子线程任务
缺点:
l 没有提供返回主线程的接口,需要自行封装
l 通常需要交叉维护多个handler
内部封装了HandlerThread的实现,同时是Service的子类,用法跟Service也差不多,实现耗时逻辑应放在onHandleIntent(Intent intent){}的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,而且当任务执行完后会自动停止,无须手动去终止它。
示例:
实现onHandleIntent方法即可。
public class MyIntentService extends IntentService {
private static final String TAG = MyIntentService.class.getSimpleName();
private int count = 0;
public MyIntentService() {
super(TAG);
// TODO Auto-generated constructor stub
}
@Override
protected void onHandleIntent(Intent intent) {
//每一次通过Intent发送的命令将被顺序执行
count ++;
Log.d(TAG, "count::" + count);
}
}
优点:
l 使用方式熟悉
l 兼有HandlerThread和Service的优点,适合单个重度分叉性的后台任务
缺点:
l 没有提供返回主线程的接口,不适合异步交互
l 需要像Service一样在清单配置,开销体量较大
在一般的应用中我们可以使用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。