别再傻傻得认为AsyncTask只可以在主线程中创建实例和调用execute方法
Posted 潇潇凤儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了别再傻傻得认为AsyncTask只可以在主线程中创建实例和调用execute方法相关的知识,希望对你有一定的参考价值。
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
大家都知道在android中可以AsyncTask来进行异步请求,AsyncTask其实就是把Thread进行了封装,内部实现原理是Thread+Handler,使得线程简单易用。我们在查看AsyncTask源码的时候,会看到它的构造方法上边有这样的提示“Creates a new asynchronous task. This constructor must be invoked on the UI thread.”即AsyncTask()的构造方法只能在UI线程中(主线程)中调用。
但实际真的是这样么?如果在子线程中创建AsyncTask实例,会报错么?在子线程中执行execute()方法,会抛异常么?
1、创建测试demo
今天我自己实验了一把,发现在子线程中创建AsyncTask实例,调用execute()方法,完全没问题。编译正常,运行也正常,但是在onPreExecute和onPostExecute方法中更新UI线程中组件(TextView)会报异常,这也正常。可是为什么能在非UI线程中创建AsyncTask实例呢,明明官方注释是不能在非UI线程创建AsyncTask实例的呀?发生了神马?下面是测试代码:
//Activity的onCreate方法,肯定运行在主线程中
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvShow = findViewById(R.id.text);
new MyThread().start();//启动一个新线程
public class MyThread extends Thread
@Override
public void run()
//在子线程中实例化AsyncTask,调用execute()方法
MyTask myTask = new MyTask();
myTask.execute();
public class MyTask extends AsyncTask<Void, Void, Void>
@Override
protected void onPreExecute()
Log.e("xwf", "onPreExecute");
//tvShow.setText("onPreExecute");//因为在子线程中实例化MyTask,此方法运行在实例化AsyncTask的线程中, 进行tvShow.setText会报错
super.onPreExecute();
@Override
protected void onPostExecute(Void aVoid)
Log.e("xwf", "onPostExecute");
//tvShow.setText("onPostExecute");//和onPreExecute类似
super.onPostExecute(aVoid);
@Override
protected Void doInBackground(Void... voids) //运行在AsyncTask新建线程中
try
Log.e("xwf", "doInBackground before");
Thread.sleep(4000);
Log.e("xwf", "doInBackground after");
catch (InterruptedException e)
e.printStackTrace();
return null;
下面是打印的log,发现在4S前打印出onPreExecute, doInBackground before, 4S后打印出doInBackground after, onPost
赶紧打开AsyncTask源码看一看,里边的Handler是怎么实例化的,Handler实例化需要用到线程的Looper,而我自己新建的Thread并没有调用Looper.prepare(),所以新开的线程Looper肯定为空,而AsyncTask是要用到Handler来和主线程交互的,我在子线程中实例化AsyncTask,按道理运行时应该报错啊,怎么会运行正常,而且也不符合AsyncTask()构造方法上的提示啊。
2、 android26+ AsyncTask部分源码
楼主打开AsyncTask源码查看,发现AsyncTask有三个构造方法,其中有两个是@hide隐藏的,开发者是调用不到的。但开发者能调用到无参构造方法,无参构造函数又调用了第三个带Looper参数的构造方法。
private static InternalHandler sHandler;
private final Handler mHandler;
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
* 其实可以在子线程中调用此构造器方法,即使子线程中没有调用Looper.prepare()方法创建Looper
* android版本明明改成支持在子线程中实例化AsyncTask,但是上面的注释没有跟着改
*/
public AsyncTask()
this((Looper) null);
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide
*/
public AsyncTask(@Nullable Handler handler) //这个方法是隐藏方法,普通用户是无法调用的
this(handler != null ? handler.getLooper() : null);
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*
* @hide 这个方法是隐藏方法,普通用户是无法调用的
*/
public AsyncTask(@Nullable Looper callbackLooper)
//因为我们调用的是无参构造方法,所以callbackLooper是空的,所以会走getMainHandler()
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
//后面是实例化mWorker不是咱们关注的重点,暂时省略,不贴上来
//用mWorker实例来创建FutureTask实例
mFuture = new FutureTask<Result>(mWorker)
@Override
protected void done()
try
postResultIfNotInvoked(get());//将运行结果返回
catch (InterruptedException e)
android.util.Log.w(LOG_TAG, e);
catch (ExecutionException e)
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
catch (CancellationException e)
postResultIfNotInvoked(null);
;
//getMainHandler()方法
private static Handler getMainHandler()
synchronized (AsyncTask.class) //线程安全,多个AsyncTask实例只会创建一个Handler对象
//sHandler是静态变量
if (sHandler == null)
//会把主线程的Looper传进去构造Handler实例,所以sHandler持有的主线程的Looper
sHandler = new InternalHandler(Looper.getMainLooper());
return sHandler;
//InternalHandler类,只查看它带参数的构造方法
private static class InternalHandler extends Handler
public InternalHandler(Looper looper)
super(looper);//调用Handler的构造方法创建Handler实例
在第三个带Looper的构造方法AsyncTask(@Nullable Looper callbackLooper)中,由于子线程中没有调用Looper.prepare()方法创建Looper,所以callbackLooper为null,会调用getMainHandler()来创建Handler对象,该方法为AsyncTask.class线程安全做了同步,保证一个进程中所有AsyncTask实例只有一个Handler实例,且只有应用在第一次实例化AsyncTask时才会创建Handler对象。getMainHandler()会调用new InternalHandler(Looper.getMainLooper())方法,这里传入了主线程的Looper对象,即无论我们在哪个线程实例化AsyncTask实例,AsyncTask中的Handler都是用主线程的Looper来实例化的,但AsyncTask中的onPreExecute和onPostExecute方法还是会运行在新建AsyncTask实例的线程中。我们还发现在AsyncTask(Looper callbackLooper)构造方法中,会将sHandler对象赋给mHandler对象,为什么需要两个Handler对象呢?发消息的时候用的是哪个Handler呢?
我们根据上面新建FutureTask对象时,跟进postResultIfNotInvoked方法:
private void postResultIfNotInvoked(Result result)
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked)
postResult(result);
private Result postResult(Result result)
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
private Handler getHandler()
return mHandler;
可以看到最终是调用mHandler来进行消息发送的,至于为什么需要用两个Handler呢,我猜是为了兼容老版本吧,或许以后AsyncTask支持传入其他线程的Looper来构建实例,Handler就可以用非主线程的Looper来创建实例了,这样sHandler是用主线程Looper创建实例的,而mHandler是用其他非UI线程Looper创建的实例。
所以我们可以得出以下结论:
结论(android 26+)
通过上面的分析,可以得出以下结论:
1)可以在子线程中调用AsyncTask()构造器方法创建AsyncTask实例。
即使子线程中没有调用Looper.prepare()方法创建Looper对象,且AsyncTask也不支持传子线程的Looper对象来构造Handler,不过它提供了带Looper参数的构造方法,只是暂时是@hide(对开发者隐藏),或许以后会支持可以传子线程中Looper对象进行创建AsyncTask实例。
2)可以在子线程中调用execute()方法执行耗时操作。
3)在子线程中创建AsyncTask实例,执行execute()方法,在哪个线程执行AsyncTask的execute(),onPreExecute会运行在执行在那个线程中,onPostExecute方法还是会执行在主线程中,doInBackground方法会执行在AsyncTask创建的线程中。因为AsyncTask中的Handler还是用主线程中的Looper对象来实例化的,所以handler的handleMessage消息还是会发到主线程中。
见右图:
4)android 26+改成支持在子线程中实例化AsyncTask,但是构造器上面的注释没有跟着改。
楼主以上分析是基于android-28中的AsyncTask进行分析的,其实从android 26起就改成这样了,那以前的版本呢,是不是以前版本不支持AsyncTask在子线程中实例化呢,楼主接着打开android20〜android25版本的AsyncTask进行查看。
3、android 21及之前AsyncTask部分源码
下面是android21及之前版本的AsyncTask的InternalHandler源码:
private static final InternalHandler sHandler = new InternalHandler();//注释1
//AsyncTask内部Handler类
private static class InternalHandler extends Handler
@SuppressWarnings(unchecked, RawUseOfParameterizedType)
@Override
public void handleMessage(Message msg)
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
//根据返回结果处理
/** Creates a new asynchronous task. This constructor must be invoked on the UI thread.
创建一个异步任务,该构造器只能在UI线程中调用*/
public AsyncTask()
//创建WorkerRunnable对象,即Callable对象,跟Runnable对象比,有返回值
mWorker = new WorkerRunnable<Params, Result>()
public Result call() throws Exception
//...
;
//用WorkerRunnable对象去创建FutureTask对象
mFuture = new FutureTask<Result>(mWorker)
@Override
protected void done()
//...
;
在注释1处,定义InternalHandler变量时,就直接new InternalHandler(),类似于单例里的饿汉式,类一加载就对Handler进行了实例化,这样其实相对来说耗资源的。且android 21及之前,只提供了一种无参构造方法来创建AsyncTask实例。我们看到InternalHandler没有重写Handler构造函数,直接调用的是Handler的无参构造方法。
我们再来看看Handler的无参构造方法。
/**默认构造器,调用该构造器的线程需要有自己的Looper,没有Looper的线程调用Handler()方法会抛异常*/
public Handler()
this(null, false);
public Handler(Callback callback, boolean async)
//...
mLooper = Looper.myLooper();//获取当前线程的Looper对象,如果为空会抛异常
if (mLooper == null)
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
可以看到如果在子线程中如果没有调用Looper.prepare()方法创建looper,进行实例化Handler是会抛出异常的。
结论:在android-21及之前,是无法子线程中创建AsyncTask实例,因为InternalHandler没有重写构造方法,它调用是父类Handler的构造方法,而父类Handler()方法调用了 Handler(Callback callback, boolean async)方法,由于子线程中没有创建Looper对象,Looper.myLooper()是为空的,所以会抛出异常。且Handler实例在AsyncTask类加载的时候就被创建出来了。
但是还有个诡异的地方,我在demo中把build.gradle中版本改成compileSdkVersion 和targetSdkVersion都改成21,发现还是能在子线程中创建AsyncTask实例,也可以调用execute()方法。这时为什么呢?
android
compileSdkVersion 21
defaultConfig
applicationId "com.example.myapplication2"
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildTypes
release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
这其实涉及到compileSdkVersion、buildToolsVersion、minSdkVersion、targetSdkVersion之间的区别了:
compileSdkVersion:应用编译版本号,用到了sdk目录下的platforms目录里下载好的android版本,如果填写的版本没有出现在platorms目录中,需要先下载。如填写的是 compileSdkVersion 21,代表是将android-21目录下得android.jar参与apk的编译。如果你的compileSdkVersion设置成了21,但是想要使用一个API23才出现的方法,这是不可行的,必须把compileSdkVersion设置成23或以上。
buildeToolVersion:构建工具的版本号,用到了sdk目录下的build-tools目录里下载好的版本,其中包括了打包工具aapt、dx等等。Android系统在不断升级,每次添加新特性,就需要新的工具来进行build,所以老的build工具不能用来build新的版本。
targetSdkVersion:API目标版本,它是Android里提供的一种向下兼容的方案。如果手机平台的API Level高于你的应用程序中的targetSdkVersion属性指定的值,系统会开启兼容行为来确保你的应用程序继续以期望的形式来运行。你可以通过指定targetSdkVersion来匹配运行程序的平台的 API level来禁用这种兼容性行为。例如如果将targetSdkVersion设置成<23,那么在6.0及以上的设备上就不会以6.0的新特性来运行了,当然也不需要动态申请了。
minSdkVersion:最小sdk版本,他代表的意思是你的App最低支持的手机版本。如果你的minSdkVersion设置成了16(Android4.0),那么Apk在16以下系统的手机无法安装。
虽然我设置了compleSdkVersion 21编译我的程序,AsyncTask源码是不支持在子线程中创建实例的,但我的手机平台sdk是23的,所以会用android sdk 23中的AsyncTask中的源码来运行,所以会出现我编译时用21的版本,还能在子线程中创建AsyncTask实例。
4、android22~android25版本AsyncTask源码
接下来,我又查看了android sdk22、23、24、25版本AsyncTask的InternalHandler代码,发现这几个版本在创建AsyncTask实例和Handler实例是一样的。
private static class InternalHandler extends Handler
public InternalHandler()
super(Looper.getMainLooper());
@SuppressWarnings("unchecked", "RawUseOfParameterizedType")
@Override
public void 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;
可以看到这几个版本的InternalHandler重写了Handler()构造方法,直接把主线程中的Looper对象传过去了。且这几个版本的AsyncTask也只提供了一种无参构造方法,和android-21是一样的。与android-21还有一点不同的是,android-21 Handler对象实例化是在类加载的时候就进行实例化,而这几个版本是在用到Handler的时候才进行实例化的,只是在调用getHandler()的时候才会去创建Handler的实例,也进行了线程安全处理,同一进程中所有AsyncTask实例也只会有一个Handler实例。
private static Handler getHandler()
synchronized (AsyncTask.class)
if (sHandler == null)
sHandler = new InternalHandler();
return sHandler;
5、在子线程创建Looper
我们再来测试下,如果在子线程中创建Looper,则其实无论哪个android版本的AsyncTask应该都可以在主线程中创建实例且运行execute()方法,我们来测试一把,在子线程中调用Looper.prepare()创建Looper。
new Thread()
@Override
public void run()
Looper.prepare();
new MyTask().execute();
Looper.loop();
super.run();
.start();
如上所示,如果在子线程中创建Looper,则在MyTask类中可以创建Handler实例,因为无参的Handler()构造方法会调用Looper.myLooper()来获取当前线程的Looper对象,下面是模拟android 21及之前版本中AsyncTask直接创建Handler实例的代码,并且用Handler来发送消息。
public class MyTask extends AsyncTask<Void, Void, Void>
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler
@SuppressWarnings("unchecked", "RawUseOfParameterizedType")
@Override
public void handleMessage(Message msg)
Log.e("xwf", "handleMessage = " + msg.what);
@Override
protected void onPreExecute()
Log.e("xwf", "onPreExecute");
sHandler.sendEmptyMessage(0);
super.onPreExecute();
@Override
protected void onPostExecute(Void aVoid)
sHandler.sendEmptyMessage(1);
Log.e("xwf", "onPostExecute");
super.onPostExecute(aVoid);
@Override
protected Void doInBackground(Void... voids)
try
Log.e("xwf", "doInBackground before");
Thread.sleep(4000);
Log.e("xwf", "doInBackground after");
catch (InterruptedException e)
e.printStackTrace();
return null;
下面是运行结果,可以看到程序正常运行,Handler正常创建了实例。
总结:
android随着版本的升高,也在不断优化android.jar包中的类,随着android第三方库的强大,AsyncTask类在开发过程中用得越来越少,在高并发情况下都选择用线程池,但是提供android.jar包团队并没有放弃对它的优化。但在优化的同时,发现团队把代码修改了,可是注释部分没有跟着变化。
同时通过上面的探索,也可以给我们一个警钟,很多东西不能人云亦云,毕竟纸上得来终觉浅,觉知此事还须行。网上几乎千篇一律地说AsyncTask不能在子线程中实例化,只能在UI线程中实例化,只能在UI线程中调用execute()方法,楼主也天真的以为如此。其实只要AsyncTask中的Handler可以获取到Looper就可以在相应的线程创建AsyncTask实例。
现在才发现,Android SDK开发团队早已对AsyncTask代码进行了修改,当然在子线程中进行实例化AsyncTask和调用execute方法,应用场景可能不多。但也有需要用到的场景,比如在跨进程通信时,AIDL中连接都用的Binder线程,如果我们不想阻塞Binder线程,可以在Binder线程中用AsyncTask来处理一些轻量级耗时操作。
1、android-21及之前只能在UI线程创建AsyncTask实例和调用execute方法。android22+可以在非UI线程中创建AsyncTask实例和调用execute方法。因为从android22+起Handler实例化时调用了Looper.getMainLooper()(主线程的Looper)。因为用的是主线程的Looper,所以建议是在主线程中进行实例化AsyncTask,但其实Handler只要有Looper就能实例化,并不会抛异常。AsyncTask的onPreExecute是执行在调用AsyncTask.execute()方法的线程中,onPostExecute方法是执行在主线程中的(用了主线程looper实例化handler),doInBackground方法是执行在AsyncTask新开的线程中的。
2、对于AsyncTask的构造方法,android25及之前版本AsyncTask只提供了一个无参构造函数来创建AsyncTask实例,android26+AsyncTask提供了三个构造方法,但是对外公开调用的只有一个无参构造方法,或许以后可能会公开带Looper参数的构造方法。
3、Handler是静态变量,属于类属性,同一进程所有AsyncTask实例共享一个Handler对象。android-21及之前Handler的实例化在类一加载的时候就创建了,android22~25 handler实例是在需要用到Handler发送消息的时候,才会进行Handler的实例化。android26+则是在创建AsyncTask实例时进行了Handler的实例化。这样做的好处是不会像android21之前那样类一加载就实例化耗费资源,而在需要用到Handler发消息的时候才实例化,如果在多线程并发发消息时,会有延迟。
4、android26+ AsyncTask中包含了两个Handler对象mHandler(常量)和sHandler(静态变量),如果用AsyncTask有参构造方法中传入了Looper创建AsyncTask实例,mHandler会用传入的Looper来创建实例;而现在系统只支持AsyncTask无参构造方法创建AsyncTask实例,所以mHandler=sHandler.
以上是关于别再傻傻得认为AsyncTask只可以在主线程中创建实例和调用execute方法的主要内容,如果未能解决你的问题,请参考以下文章
小伙伴们,线程生命周期线程池生命周期别再傻傻分不清楚了!!!