【导读】在Android应用开发的过程当中,咱们须要时刻注意保证应用程序的稳定和UI操做响应及时,由于不稳定或响应缓慢的应用将给应用带来很差的印象,严重的用户卸载你的APP,这样你的努力就没有体现的价值了。本文试图从AsnycTask的做用提及,进一步的讲解一下内部的实现机制。若是有一些开发经验的人,读完以后应该对使用AsnycTask过程当中的一些问题豁然开朗,开发经验不丰富的也能够从中找到使用过程当中的注意点。
在Android程序开始运行的时候会单独启动一个进程,默认状况下全部这个程序操做都在这个进程中进行。一个Android程序默认状况下只有一个进程,可是一个进程倒是能够有许线程的。 html
在这些线程中,有一个线程叫作UI线程,也叫作Main Thread,除了Main Thread以外的线程均可称为Worker Thread。Main Thread主要负责控制UI页面的显示、更新、交互等。所以全部在UI线程中的操做要求越短越好,只有这样用户才会以为操做比较流畅。一个比较好的作法是把一些比较耗时的操做,例如网络请求、数据库操做、复杂计算等逻辑都封装到单独的线程,这样就能够避免阻塞主线程。为此,有人写了以下的代码: java
private TextView textView; public void onCreate(Bundle bundle){ super.onCreate(bundle); setContentView(R.layout.thread_on_ui); textView = (TextView) findViewById(R.id.tvTest); new Thread(new Runnable() { @Override public void run() { try { HttpGet httpGet = new HttpGet("http://www.baidu.com"); HttpClient httpClient = new DefaultHttpClient(); HttpResponse httpResp = httpClient.execute(httpGet); if (httpResp.getStatusLine().getStatusCode() == 200) { String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8"); textView.setText("请求返回正常,结果是:" + result); } else { textView.setText("请求返回异常!"); } }catch (IOException e){ e.printStackTrace(); } } }).start(); }
运行,不出所料,异常信息以下: android
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
怎么破?能够在主线程建立Handler对象,把textView.setText地方替换为用handler把返回值发回到handler所在的线程处理,也就是主线程。这个处理方法稍显复杂,Android为咱们考虑到了这个状况,给咱们提供了一个轻量级的异步类能够直接继承AsyncTask,在类中实现异步操做,并提供接口反馈当前异步执行的结果以及执行进度,这些接口中有直接运行在主线程中的,例如onPostExecute,onPreExecute等方法。 git
也就是说,Android的程序运行时是多线程的,为了更方便的处理子线程和UI线程的交互,引入了AsyncTask。 github
AsyncTask内部逻辑主要有二个部分: 数据库
一、与主线的交互,它内部实例化了一个静态的自定义类InternalHandler,这个类是继承自 Handler的,在这个自定义类中绑定了一个叫作AsyncTaskResult的对象,每次子线程须要通知主线程,就调用sendToTarget发送消息给handler。而后在handler的handleMessage中AsyncTaskResult根据消息的类型不一样(例如MESSAGEPOSTPROGRESS会更新进度条,MESSAGEPOSTCANCEL取消任务)而作不一样的操做,值得一提的是,这些操做都是在UI线程进行的,意味着,从子线程一旦须要和UI线程交互,内部自动调用了handler对象把消息放在了主线程了。源码地址缓存
mFuture = new FutureTask<Result>(mWorker) { @Override protected void More ...done() { Message message; Result result = null; try { result = get(); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null)); message.sendToTarget(); return; } catch (Throwable t) { throw new RuntimeException("An error occured while executing " + "doInBackground()", t); } message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(AsyncTask.this, result)); message.sendToTarget(); } };
private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void More ...handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; case MESSAGE_POST_CANCEL: result.mTask.onCancelled(); break; } } }
二、AsyncTask内部调度,虽然能够新建多个AsyncTask的子类的实例,可是AsyncTask的内部Handler和ThreadPoolExecutor都是static的,这么定义的变量属于类的,是进程范围内共享的,因此AsyncTask控制着进程范围内全部的子类实例,并且该类的全部实例都共用一个线程池和Handler。代码以下: 网络
public abstract class AsyncTask<Params, Progress, Result> { private static final String LOG_TAG = "AsyncTask"; private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 1; private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>(10); private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread More ...newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); private static final int MESSAGE_POST_RESULT = 0x1; private static final int MESSAGE_POST_PROGRESS = 0x2; private static final int MESSAGE_POST_CANCEL = 0x3;
从代码还能够看出,默认核心线程池的大小是5,缓存任务队列是10。意味着,若是线程池的线程数量小于5,这个时候新添加一个异步任务则会新建一个线程;若是线程池的数量大于等于5,这个时候新建一个异步任务这个任务会被放入缓存队列中等待执行。限制一个APP内AsyncTask并发的线程的数量看似是有必要的,但也带来了一个问题,假若有人就是须要同时运行10个而不是5个,或者不对线程的多少作限制,例若有些APP的瀑布流页面中的N多图片的加载。 多线程
另外一方面,同时运行的任务多,线程也就多,若是这些任务是去访问网络的,会致使短期内手机那可怜的带宽被占完了,这样整体的表现是谁都很难很快加载彻底,由于他们是竞争关系。因此,把选择权交给开发者吧。 并发
事实上,大概从Android从3.0开始,每次新建异步任务的时候AsnycTask内部默认规则是按提交的前后顺序每次只运行一个异步任务。固然了你也能够本身指定本身的线程池。
能够看出,AsyncTask使用过程当中须要注意的地方很多
上面提到了那么多的注意点,还有其余须要注意的吗?固然有!咱们开发App过程当中使用AsyncTask请求网络数据的时候,通常都是习惯在onPreExecute显示进度条,在数据请求完成以后的onPostExecute关闭进度条。这样作看似完美,可是若是您的App没有明确指定屏幕方向和configChanges时,当用户旋转屏幕的时候Activity就会从新启动,而这个时候您的异步加载数据的线程可能正在请求网络。当一个新的Activity被从新建立以后,可能由从新启动了一个新的任务去请求网络,这样以前的一个异步任务不经意间就泄露了,假设你还在onPostExecute写了一些其余逻辑,这个时候就会发生意想不到异常。
通常简单的数据类型的,对付configChanges咱们很好处理,咱们直接能够经过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。 Android会在销毁你的Activity以前调用onSaveInstanceState()方法,因而,你能够在此方法中存储关于应用状态的数据。而后你能够在onCreate()或onRestoreInstanceState()方法中恢复。
可是,对于AsyncTask怎么办?问题产生的根源在于Activity销毁从新建立的过程当中AsyncTask和以前的Activity失联,最终致使一些问题。那么解决问题的思路也能够朝着这个方向发展。Android官方文档 也有一些解决问题的线索。
这里介绍另一种使用事件总线的解决方案,是国外一个安卓大牛写的。中间用到了Square开源的EventBus类库http://square.github.io/otto/。首先自定义一个AsyncTask的子类,在onPostExecute方法中,把返回结果抛给事件总线,代码以下:
@Override protected String doInBackground(Void... params) { Random random = new Random(); final long sleep = random.nextInt(10); try { Thread.sleep(10 * 6000); } catch (InterruptedException e) { e.printStackTrace(); } return "Slept for " + sleep + " seconds"; } @Override protected void onPostExecute(String result) { MyBus.getInstance().post(new AsyncTaskResultEvent(result)); }
在Activity的onCreate中注册这个事件总线,这样异步线程的消息就会被otta分发到当前注册的activity,这个时候返回结果就在当前activity的onAsyncTaskResult中了,代码以下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.otto_layout); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new MyAsyncTask().execute(); } }); MyBus.getInstance().register(this); } @Override protected void onDestroy() { MyBus.getInstance().unregister(this); super.onDestroy(); } @Subscribe public void onAsyncTaskResult(AsyncTaskResultEvent event) { Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show(); }
我的觉的这个方法至关好,固然更简单的你也能够不用otta这个库,本身单独的用接口回调的方式估计也能实现,你们能够试试。