Học source code từ open source, Retrofit.

Là lập trình viên, điều thú vị là bạn luôn có những thứ thực sự hay ho để tìm hiểu, những thứ hay ho có thể đến từ ngôn ngữ mà bạn chọn, từ người đồng nghiệp giỏi giang hoặc cũng có thể từ những người đang làm việc giống bạn, những chia sẻ, source code từ các expert, senior thực sự. Quả thật, trong thế giới IT, bạn có thể không có đủ thời gian, đủ yêu cầu để áp dụng tất thảy những opensource được chia sẻ. Những opensource chính là những công cụ đắt lực nhất cho các bạn trong công việc, bạn có thể phải làm cả tá thứ loằng ngoằng, mất cả tuần, cả tháng đôi khi lại không hiệu quả, nhưng chỉ mất một ngày đọc tutorial, hướng dẫn sử dụng lib, và thử áp dụng vào project. Amazing!

Tuy nhiên, nếu chỉ dừng lại ở mức sử dụng nó, bạn chỉ nắm được bề nổi, chỉ nắm được 1 phần hay ho của nó. Bạn có bao giờ suy nghĩ và tự đặt những câu hỏi như "Tại sao họ lại viết thư viện tuyệt như thế ? Họ đã viết nó như thế nào? Liệu rằng sau này mình có thể viết được như thế?...". Và rồi những câu hỏi như thế sẽ thôi thúc bạn đọc source code của họ, nếu bản thân chưa hiểu, có thể research thêm trên mạng về cách viết code như thế, mình tin rằng, về sau bạn sẽ nắm được bản chất source code của thư viện (hoặc của ai đó) mà bạn đang xài một cách cặn kẽ hơn. Và đó thực sự là điều thú vị.

Lang mang một tý, mong muốn của mình chỉ tóm gọn trong 1 vài câu "Hãy cố gắng hiểu cách thư viện đang xài hoạt động như thế nào ? Nếu thấy bất cứ có điều gì kì dị, thú vị lúc xài thư viện, đó là lúc đục sâu vào code của thư viện và bắt đầu tìm hiểu".

Mình sẽ list ra một vài thư viện hay xài, và những điều thú vị, kì lạ khi mới dùng bạn có thể cảm thấy được ngay:

  • Glide, Picasso,...: Các thư viện về xử lý load ảnh. Điều thú vị ở đây là tại sao chúng lại tối ưu hay như vậy, vì sao khi bạn tự làm load ảnh, GC lại chạy liên tục, còn dùng thư viện, chỉ vài dòng code nhưng performance đã tăng lên đáng kể như vậy ?
  • Dagger 2: Quá vi diệu, chỉ vài config files, toàn bộ các đối tượng của bạn được tự động sinh ra dưới một đống generated files từ Dagger 2inject vào class mà bạn mong muốn. Và nếu đã dùng Dagger 2, mình cũng muốn bạn tìm hiểu thêm thêm về Annotation (chính là mấy cái @ ... gì đó trên đầu mỗi function) và cả Java Reflection.
  • Retrofit: Có vài thứ kì dị ở đây, tạo một interface với một vài mô tả đơn giản API, và tất nhiên bạn chẳng thể tạo instance từ interface đó (trong Java), bạn cũng chả tạo một class khác để implement interface này, và cũng chả có class nào được tự generated (giống kiểu Dagger) từ Retrofit source code. Thế nhưng bạn vẫn có thể sử dụng instance của interface này, gọi được các method của interfacemột cách ngon lành. Brillian, right?
  • Android ViewModel, LiveData (Android Jetpack): Khá thú vị, Google đã giúp chúng ta giải quyết hàng tá thứ mệt mỏi về memory leak và giữ data sống sót sau cú rotation một cách nhẹ nhàng nhất từ trước giờ cho android dev.
  • ReactNative: trên cả tuyệt vời, dùng code JavaScript để tạo một native app, và performance cũng tốt gần như là native. ...

Chỉ là một số ít, nhưng hy vọng là nó cũng giống những gì bạn đang thấy, những điều đó có thể kích thích sự tò mò từ bản thân bạn. Hãy dùng nó để có thêm một chút năng lượng tìm hiểu.

OK. Mục đích chính của mình đã hết, chỉ là mong muốn tạo một chút gì đó giúp mọi người tích cực học và tìm hiểu sâu bên trong source code mà bạn đang dùng. Đó cũng là cách học giúp tiến bộ khá nhiều.

Trong bài viết này mình sẽ cùng mọi người lướt qua thư viện Retrofit, ý tưởng và cách mà nó hoạt động (Mình chỉ lướt ở mức flow cơ bản thôi 😄 ).

Retrofit retrofit = builder.client(httpClientBuilder.build())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
return retrofit.create(serviceClass); // Focus on this LOC

Hy vọng đoạn code trên bạn nhìn quen, đây là cách mà bạn bắt đầu làm cho interface của bạn đi vào hoạt động.

Trên Android Studio, Ctrl + Click vào create method, bạn sẽ được dẫn vào bên trong source code của Retrofit. Đại loại, hàm create sẽ như thế này (mình có comment thêm ý nghĩa một số dòng code):

public <T> T create(final Class<T> service) {
    ...
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          // The Platforms are android, Java 8, iOS
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            // Detect method is Java 8 default method or not
            // If yes, invoke by Java 8 specific platform
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            // The invoked reflect method will be converted to ServiceMethod
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

Đống code ở trên sẽ tạo một Proxy instance với invocation handler implement interface InvocationHandler (Thường sử dụng trong rộng rãi trong Java Spring)

Proxy cung cấp các hàm static cho việc tạo ra các dynamic proxy class và các instance. Nó cũng là superclass của tất cả các dynamic proxy class được tạo mởi những hàm này.

Một dynamic Proxy class là một classimplement một list các interface được chỉ định lúc Runtime khi proxy class được tạo. Mỗi proxy instance sẽ được liên kết với một đối tượng invocation hanlder.

Khi một method được gọi trong một proxy instance, lời gọi method (method invocation) sẽ được mã hóa (encoded) và dispatch đến ham invoke() của invocation handler của nó. Nôm na là phương thức invoke sẽ được gọi, trong đó sẽ trả về một đối tượng proxy, một java.lang.reflect.Method định nghĩa method đã được gọi, một array chứa các arguments của method.

Tại đây các reflect method được gọi sẽ được convert thành các ServiceMethod (lại Ctrl + Click tiếp nhé). ServiceMethod là một class trong Retrofit, nó có nhiệm vụ đọc tất cả dữ liệu của method từ arguments, annotations và cuối cùng convert thành một HTTP call hoàn chỉnh.

Đến đây có thể thấy được cách mà retrofit gọi được các method trong Network hay service interface của bạn. Mặc dù dùng proxy, reflectionannotation để đọc được các method, tạo request từ nó thực sự có ảnh hưởng lớn performance, tuy nhiên hãy cứ thử tưởng tượng, nó không thật sự không đáng kể so với việc hoàn thành một request. Đổi lại bên trong source code của Retrofit cũng có khá nhiều cách tối ưu performance, bằng cách load method 1 lần và cache chúng lại, ví dụ:

 ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  } 

Và cuối cùng chỉ với việc tạo một Proxy instance, toàn bộ các API method của bạn đều được xử lý giống nhau, cách tạo ra một Request là giống nhau, không cần quan tâm đến endpointarguments của API method. Thực sự đây là điều thú vị.

BONUS

Mình có viết 1 class Proxy example để xem cách Proxy instance hoạt động, nó in ra mọi thứ từ lời gọi interface method, các bạn có thể chạy trên IntelliJ IDEA, eclipse hoặc cả command line

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Date;

public class ProxyExample {
    public static void main(String[] args) {
        NonAuthInterface service = (NonAuthInterface) Proxy.newProxyInstance(
                AuthInterface.class.getClassLoader(),
                new Class[]{AuthInterface.class, NonAuthInterface.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] arguments) throws Throwable {
                        if (method.getDeclaringClass() == Object.class) {
                            return method.invoke(this, arguments);
                        }
                        System.out.println(o.getClass().getSimpleName());
                        System.out.println(method.getDeclaringClass().getSimpleName() + " class, invoking method "
                                + method.getName() + " with arguments " + Arrays.toString(arguments));

                        if (arguments.length > 0 && arguments[0] instanceof String) {
                            return arguments[0];
                        }
                        return null;
                    }
                }

        );
        System.out.println("-------------");
        System.out.println("returned: " + service.logout("UserName"));
        System.out.println("-------------");
        service.getScores(new Date());
        System.out.println("-------------");
        service.login("UserName", "Password");
    }

    interface AuthInterface {
        String logout(String userName);

        void getScores(Date date);
    }

    interface NonAuthInterface extends AuthInterface {
        void login(String userName, String password);
    }
}

HAPPY CODING !

References:

https://proandroiddev.com/how-does-retrofit-work-6ecad1bb683b https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/InvocationHandler.html https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html