别再傻傻得认为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以下系统的手机无法安装。

参考:关于build.gradle四个版本号的含义

虽然我设置了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方法的主要内容,如果未能解决你的问题,请参考以下文章

小伙伴们,线程生命周期线程池生命周期别再傻傻分不清楚了!!!

在主线程中等待 AsyncTask

为啥我的 AsyncTask 在主线程上运行?

httphttps 傻傻分不清楚?别再上当受骗了!

从 url 下载 pdf 文件在主线程上工作,但是当我使用 asynctask 时出错

在服务中使用 AsyncTask