Học source code từ open source, Retrofit.
Bài đăng này đã không được cập nhật trong 6 năm
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 2
và inject 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ộtinterface
với một vài mô tả đơn giản API, và tất nhiên bạn chẳng thể tạoinstance
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ụnginstance
củainterface
này, gọi được cácmethod
củainterface
mộ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ờ choandroid dev
.ReactNative
: trên cả tuyệt vời, dùng codeJavaScript
để 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 class mà implement 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, reflection và annotation để đọ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 endpoint
và arguments
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
All rights reserved