Android实战----Android Retrofit是怎么将回调函数放到UI线程(主线程)中的(源码分析)

Posted Herman-Hong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android实战----Android Retrofit是怎么将回调函数放到UI线程(主线程)中的(源码分析)相关的知识,希望对你有一定的参考价值。

一、简介

集成过Retrofit的猿们都知道,callback是运行在主线程中的,不用再通过异步机制处理。那么是怎么实现的呢,下面从源码角度进行分析,其中涉及到android异步机制(Handler、Message、Looper、MessageQueue),猿们自行脑补。


二、Retrofit的创建

下面从Retrofit的创建开始,其中的秘密也在其中

Retrofit mRetrofit = new Retrofit.Builder()
                .baseUrl(UrlConfig.ROOT_URL)
                .client(genericClient())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

.Builder()

public Builder() 
      this(Platform.get());
    

private static final Platform PLATFORM = findPlatform();

  static Platform get() 
    return PLATFORM;
  

private static Platform findPlatform() 
    try 
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) 
        return new Android();
      
     catch (ClassNotFoundException ignored) 
    
    try 
      Class.forName("java.util.Optional");
      return new Java8();
     catch (ClassNotFoundException ignored) 
    
    try 
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new ios();
     catch (ClassNotFoundException ignored) 
    
    return new Platform();
  

static class Android extends Platform 
    @Override public Executor defaultCallbackExecutor() 
      return new MainThreadExecutor();
    

    @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) 
      return new ExecutorCallAdapterFactory(callbackExecutor);
    

    static class MainThreadExecutor implements Executor 
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override public void execute(Runnable r) 
        handler.post(r);
      
    
  

从上到下依次执行,最终到Android中,秘密就在defaultCallbackExecutor()中,从MainThreadExecutor命名就可以看出其作用,下面代码更是进行了验证

private final Handler handler = new Handler(Looper.getMainLooper());


不是别的就是基于主线程中的Looper创建了Handler,那么利用本Handler post进的Runnable就执行在主线程(不懂的猿们可自行脑补)中了。

题外话:还有更常见的一种是异步消息处理,在主线程中采用下列方式创建Handler

private final Handler handler = new Handler()

那么其中的Looper也是主线程中的Looper,Looper是从线程本地变量中获取的。

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() 
        return sThreadLocal.get();
    

这种使用默认new Handler()构造的只能在主线程中创建,使用new Handler(Looper.getMainLooper())创建跟在哪儿创建无关,Looper都是主线程中的Looper

/**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() 
        synchronized (Looper.class) 
            return sMainLooper;
        
    

sMainLooper是应用的主线程looper,是由Android系统为应用创建的,不需要自己创建。

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: @link #prepare()
     */
    public static void prepareMainLooper() 
        prepare(false);
        synchronized (Looper.class) 
            if (sMainLooper != null) 
                throw new IllegalStateException("The main Looper has already been prepared.");
            
            sMainLooper = myLooper();
        
    


言归正传,看Retrofit的build

/**
     * Create the @link Retrofit instance using the configured values.
     * <p>
     * Note: If neither @link #client nor @link #callFactory is called a default @link
     * OkHttpClient will be created and used.
     */
    public Retrofit build() 
      if (baseUrl == null) 
        throw new IllegalStateException("Base URL required.");
      

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) 
        callFactory = new OkHttpClient();
      

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) 
        callbackExecutor = platform.defaultCallbackExecutor();
      

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    
其中callbackExecutor,就是上面创建的MainThreadExecutor,这里要记住,后面callback调用的时候要用

三、callBack的回调

先看使用

/**
     * 将call加入队列并实现回调
     *
     * @param context          应用上下文
     * @param call             调入的call
     * @param retrofitCallBack 回调
     * @param method           调用方法标志,回调用
     * @param <T>              泛型参数
     */
    public static <T> void addToEnqueue(final Context context, Call<T> call,
                                        final RetrofitCallBack retrofitCallBack, final int method) 
        call.enqueue(new Callback<T>() 
            @Override
            public void onResponse(Call<T> call, Response<T> response) 
                LogUtil.d("retrofit back code ====" + response.code());
                if (null != response.body()) 
                    if (response.code() == 200) 
                        LogUtil.d("retrofit back body ====" + new Gson().toJson(response.body()));
                        retrofitCallBack.onResponse(response, method);
                     else 
                        LogUtil.d("toEnqueue, onResponse Fail:" + response.code());
                        ToastUtil.makeShortText(context, "网络连接错误" + response.code());
                        retrofitCallBack.onFailure(response, method);
                    
                 else 
                    LogUtil.d("toEnqueue, onResponse Fail m:" + response.message());
                    ToastUtil.makeShortText(context, "网络连接错误" + response.message());
                    retrofitCallBack.onFailure(response, method);
                
            

            @Override
            public void onFailure(Call<T> call, Throwable t) 
                LogUtil.d("toEnqueue, onResponse Fail unKnown:" + t.getMessage());
                t.printStackTrace();
                ToastUtil.makeShortText(context, "网络连接错误" + t.getMessage());
                retrofitCallBack.onFailure(null, method);
            
        );
    

将callback加入到call的enqueue中,看下enqueue中都做了什么

@Override public void enqueue(final Callback<T> callback) 
      if (callback == null) throw new NullPointerException("callback == null");

      delegate.enqueue(new Callback<T>() 
        @Override public void onResponse(Call<T> call, final Response<T> response) 
          callbackExecutor.execute(new Runnable() 
            @Override public void run() 
              if (delegate.isCanceled()) 
                // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
               else 
                callback.onResponse(ExecutorCallbackCall.this, response);
              
            
          );
        

        @Override public void onFailure(Call<T> call, final Throwable t) 
          callbackExecutor.execute(new Runnable() 
            @Override public void run() 
              callback.onFailure(ExecutorCallbackCall.this, t);
            
          );
        
      );
    


其他不管,那么callback的方法就是在callbackExecutor被调用的,而这里的callbackExecutor就是MainThreadExecutor,因此这里的callback方法就运行在主线程中了。


总结:在这方面Retrofit对okhttp的封装,就好比Volley(最新的)对HttpUrlConnection的封装一样,都是采用这种方式将回调方法运行在主线程中的,其中都用到了Android最基本的异步机制(Handler、Looper、Message、MessageQueue)。通过基于主线程中的Looper创建Handler,从而实现通过此Handler post的消息能够被主线程中的Looper获取并运行在主线程中。



以上是关于Android实战----Android Retrofit是怎么将回调函数放到UI线程(主线程)中的(源码分析)的主要内容,如果未能解决你的问题,请参考以下文章

android实战网址

Android项目实战Android开发进阶学习

okhttp文件上传失败,居然是Android Studio背锅?

Android实战技巧:ViewStub的应用

Android实战Android中处理崩溃异常

Android开发实战(二十一):浅谈android:clipChildren属性