Android异步加载之AsyncTask
AsyncTask是什么?谷歌为什么要发明AsyncTask?
Android有一个原则—单线程模型的原则:UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
所以谷歌就制造AsyncTask,AsyncTask扩展Thread增强了与主线程的交互的能力。如果你的应用没有与主线程交互,那么就直接使用Thread就好了。
单线程模型中谨记:
1. 不要阻塞UI线程
2. 确保只在UI线程中访问Android UI工具包
简单来说:AsyncTask是线程与handler的结合体。 除了 doInBackground 之外,所有的回调都是在主线程里
AsyncTask的四个方法的运行环境:
- doInBackground(); //运行在后台线程中
- onPreExecute(); //运行在UI线程中
- onProgressUpdate(); //运行在UI线程中
- onPostExecute() //运行在UI线程中
方法的调用顺序:
- onPreExecute 在调用doInBackground 调用
- doInBackground 子线程调用
- onProgressUpdate 如果doInBackground 里调用了publishProgress会在主线程里被调用
- onPostExecute doInBackground执行完毕之后,会在主线程里被调用
示例:
static class MyAsyncTask extends AsyncTask{ /** * doInBackground 执行之前会被回调 */ @Override protected void onPreExecute() { super.onPreExecute(); Log.e("MyAsyncTask","onPreExecute" + Thread.currentThread().getName()); } /** * 进度更新的回调 * @param values */ @Override protected void onProgressUpdate(Object[] values) { Log.e("MyAsyncTask",String.format("onProgressUpdate total=%d, progress=%d",values[0],values[1]) + Thread.currentThread().getName()); super.onProgressUpdate(values); } /** * 任务执行之后 * @param o */ @Override protected void onPostExecute(Object o) { Log.e("MyAsyncTask","onPostExecute " + Thread.currentThread().getName() +" " +o); Log.e("MyAsyncTask","onPostExecute " + Thread.currentThread().getName()); } /** * 在子线程里执行 * @param params * @return */ @Override protected Object doInBackground(Object[] params) { Log.e("MyAsyncTask","doInBackground" + Thread.currentThread().getName()); String str = "hehe"; int count = 10; while(count -- > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(10,count); } return str; }}
使用AsyncTask需遵守的四条规则:
- Task的实例必须在UI thread中创建
- execute方法必须在UI thread中调用
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
- 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground 接 受的参数,第二个为显示进度的参数,第三个为doInBackground返回和onPostExecute传入的参数
关于第四点,其实我个人存在异议:即使经过多次调用,也不应该会出现异常 ——>AsyncTask有多外提供公开的接口:cancel(true);isCancelled(); 这两个方法主要配合AsyncTask可以手动退出(参考AsyncTask.java源码):
AsyncTask must be subclassed to be used. The subclass will override at least * one method ({@link #doInBackground}), and most often will override a * second one ({@link #onPostExecute}.) * * Here is an example of subclassing:
* class="prettyprint"> * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { * protected Long doInBackground(URL... urls) { * int count = urls.length; * long totalSize = 0; * for (int i = 0; i < count; i++) { * totalSize += Downloader.downloadFile(urls[i]); * publishProgress((int) ((i / (float) count) * 100)); * // Escape early if cancel() is called * if (isCancelled()) break; * } * return totalSize; * } * * protected void onProgressUpdate(Integer... progress) { * setProgressPercent(progress[0]); * } * * protected void onPostExecute(Long result) { * showDialog("Downloaded " + result + " bytes"); * } * }
这是AsyncTask给出的demo:
if (isCancelled()) break;
这个就是判断是否退出后台线程,如果设置了cancel为true,就back跳出循环,但注意:
终止线程最好不要用打断线程来做,这样的方式太粗暴了,而且不能保证代码的完整性,最好的处理方式就是在for循环,while循环中加入自己的判断标志位,就像AsyncTask这种方法来处理是最好的,这也是谷歌来指导我们怎么来处理终止线程的办法
但有时我们的代码并没有循环,怎么加入判断标志位?其实这个一般来说后台处理,大部分都是处理循环的逻辑,很少说一行代码或者十几行代码很耗时的,(当然网络相关的另说了,还有下载相关的,这个有其他方法来解决的)。即使有的话,比如调用jni,so库,返回就是慢。那就在几个耗时的方法的后面都加上标志位的判断;通过上述方法就可以做出完整的方案设计,就能设计,当下次再次执行AsyncTask,先判断自己是否正在运行,如果在运行,就不执行或取消任务重新执行,这个要看具体的需求是什么了
示例:
private void stopAyncTaskRunning() { if (mContactsListLoader != null && mContactsListLoader.getStatus() == AsyncTask.Status.RUNNING) { //if task is still running, stop it; mContactsListLoader.cancel(true); } }
private void getContactsList() { stopAyncTaskRunning(); mContactsListLoader = new ContactsListLoader(); mContactsListLoader.executeOnExecutor(AsyncTask.THEAD_POOL_EXECUTOR); }
当然,我的这个需求是下次进来的时候,就取消上次的任务,然后重新刷新数据。另外也不要忘记在doInBackground()中的循环语句中加入
@Override protected Integer doInBackground(Object... arg0){ List contacts = new ArrayList(); ContentResolver cr = mContext.getContentResolver(); Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, PROJECTION_CONTACT, null, null, null); if (cursor != null) { cursor.moveToPosition(-1); while (cursor.moveToNext()) if (isCancelled()) break; } }
这样,逻辑按照需求来写,需求是什么样子的,逻辑就相应的怎么处理
AsyncTask一点令人无语之处,在Android3.0之后的版本,AsyncTask的执行方法分成了两个:
- execute()
- executeOnExecutor()
如果用AsyncTask调用execute的时候,就表示串行执行线程,如果这个Activity中有4个fragment,而且每个fragment都有一个AsyncTask,这样的话用execute的话,就必须顺序执行,等一个执行完,第二个才执行。如果用方法executeOnExecutor,则可以串行执行,这个UI效果就很好了。线程池可以用系统的,也可以用我们自定义的线程池
对系统默认线程池中执行的线程数的说明:
下面的5代表corePoolSize,10代表阻塞队列的长度,128代表maximumPoolSize
- 如果线程池的数量小于5,则创建新的线程并执行
- 如果线程数大于5且小于5+10(阻塞队列大小),则将第6~15的线程加入阻塞队列,待线程池中的5个正在运行的线程有某个结束后,取出阻塞队列的线程执行。
- 如果线程数为16~128,则运行的线程数为num-10
- 如果线程数大于128,则舍弃。
数据要加载一部分就刷新UI,给用户一个好的用户体验;eg:
private static final int DISPLAY_NUM = 10; private List mContacts = new ArrayList(); private class ContactsListLoader extends AsyncTask<Object, Integer, Integer> { int count = 0; List mTempContacts = new ArrayList(DISPLAY_NUM); @Override protected void onPreExecute() { super.onPreExecute(); mTempContacts.clear(); mContacts.clear(); } @Override protected Integer doInBackground(Object... arg0) { List contacts = new ArrayList(); if (c != null) { c.moveToPosition(-1); { if (isCancelled()) { break; } contacts.add(contact); mTempContacts.add(contact); if (++count >= DISPLAY_NUM) { publishProgress(); count = 0; } } } return contacts.size(); } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); mContacts.addAll(mTempContacts); mTempContacts.clear(); mAdapter.notifyDataSetChanged(); } @Override protected void onPostExecute(Integer size) { if (isCancelled()) return; { if (mTempContacts.size() > 0 && mTempContacts.size() != DISPLAY_NUM) { mContacts.addAll(mTempContacts); } } else { if (mTempContacts.size() > 0) { mContacts.addAll(mTempContacts); } } mAdapter.notifyDataSetChanged(); } }
更多相关文章
- Android中库项目、jar包等的使用方法
- 深入理解Android自定义View
- Android下从网络上获取图片的方法 学习笔记
- android集成支付宝sdk
- AsyncTask使用详解及源码分析
- Android实现圆形、圆角和椭圆自定义图片View(使用BitmapShader图
- Android(安卓)Studio常用配置及使用技巧(二)
- Android(安卓)Studio导入第三方jar包或依赖工程的方法
- android下关联源码的最简单的方法