This article was originally published on the wechat official account — Interesting Things in the world, handling, reprint please note the source, otherwise will be held liable for copyright. Wechat id: A1018998632, QQ group: 859640274

Serial articles

  • 1. Copy a Douyin app from scratch — Start
  • 4. Copy a Douyin App from scratch — log and burial points and the initial architecture of the back end
  • 6. Copy a Douyin App from scratch — start with audio and video
  • 7. Copy a Douyin App from scratch — a minimalist video player based on FFmpeg
  • 8. Copy a Douyin App from scratch — build a cross-platform video editing SDK project

The github address for this project is MyTikTok

National Day is coming to an end, there are six days in the National Day to write articles to see the code than I helpless pain (buy a sad, ha ha). Five modules have been added to the project in the last few days, so I took a look at Kotlin’s syntax and put it into practice in the project. Two of these modules will be covered in this article, and the rest will be covered in the next article to be published shortly.

  • 1. Discussion — Summarize the meaningful discussion in the comments of the last two weeks and give me an answer
  • 2. App Architecture Update — As development progressed, I found some problems with the architecture in the second article, so here is an update
  • 3. Network layer customization – Customize a network request layer based on Retrofit and okHttp3, with some principles in between

A, discuss

Discussion 1: ZSH support for bash is not complete, and if running pure bash sometimes causes problems, it is recommended not to use it on the server.

  • 1. This reader’s suggestion is very good, in the last article I wrote a unbunt environment initialization script, it seems that this script can only be used when developing under Linux

Discussion 2: I thought AOP was implemented through AspectJRT, which is similar to Butterknife

  • 1. Aop in my mind can be summarized as simply adding code before and after certain methods with annotated information.
  • 2. So AspectJ can also implement the AOP logging I talked about in the previous article.
  • 3. If you know how AspectJ works, you will find that it also uses the Gradle plugin to insert code into and after annotations, but this part does not need to be implemented by the developer.
  • 4. One such thing is implemented in the project, one is for customizability, the other is for understanding the principle of some technology rather than just using it.

Discussion 3: It is suggested to create different tags for the completed version of a function module or article, which is good for eating. (Github issue)

  • 1. This reader’s suggestion is also very good. I have added a tag to the commit of each update, so you can see the code in combination with this.

Ii. Update the APP architecture

I think those of you who have seen the second article in this series have seen the module architecture diagram for this project. It has been almost three months since I wrote the first line of code for this project. During this process, many modules have been added to the project, and MY grasp of the big project has deepened a lot. Therefore, in this section, I will update the architecture of the APP.

I’ll start with figure 1, where the small modules in red represent the ones that have been developed

  • 1. Start from the bottom, here are some two-party libraries (self-developed SDK) and three-party libraries (open source SDK). All other modules can depend on this library, but of course they are unidirectional (A depends on B, but B cannot depend on A).
  • 2. One level up, there are two large modules,generate-codeinternal-base.
    • 1. Generate-code: This is where several modules of code generation are placed, such as the annotation-progress of code generated with APT and the Invoker of code generated with Gradle’s Transform in conjunction with Javassist.
    • 2. Internal-base: This is where all the underlying modules of the app are placed, such as the HTTP module responsible for network requests, the Image module responsible for image loading, the Database module responsible for replication, and so on.
    • 3. Generate code and internal-base can be used to generate and internal-base each other, as HTTP and image can be used to generate and internal-base each other.
    • 4. Both of these large modules can be relied on by larger modules at higher levels. Note that this is a one-way dependency, a convention that must be followed because there are no code level constraints
  • 3. If you look up, there’s one on the leftexternal-baseBig module and onecoreSmall modules
    • 1. External – base: There is no small module added to this large module, but it will be added in the future. This module contains the encapsulation of external intrusive code. For example, Bugly needs to add some additional code in addition to adding library dependencies, and some adaptation code is needed after integrating push solutions from some Android vendors
    • 2. Core: This is a small module. It is left out because it serves as a link between the preceding and the following
      • 1. This contains the common code of the upper module, such as the initialization code when the app enters.
      • 2. Solve the problems that some low-level modules need to reference each other, for example, HTTP needs to reference each other with image, which will cause circular reference errors. In this case, put these codes into core for processing. For the time being, I noticed that this feature may cause the module to rely on too much. I will continue to split the module later
      • 3. Communicate the bottom and upper modules
    • 3. The two modules here can be dependent on the big module of app-plugins on the right of the same layer, and this is also a one-way dependency
  • 4. On the right side of the same layer, a large module is named app-plugins, and each small module in it can be compiled into an app. It can then be relied on by the top-level app-variants to build apps with different features.
  • Its top layer is the app-plugins, which will lead to the development of gradle configurations that will eventually create apps with different functions.

Third, network layer customization

Now okHTTP + Retrofit may be a standard for new projects, but many people only use the basic functionality of these two libraries, not knowing that they can be customized to achieve more functionality. In this section, I’ll show you how to customize the network request layer for a large project based on these two libraries. It’s going to be interspersed with some of the principles.

1. Network layer request process

I’ll start with the okHTTP + Retrofit request process as shown in Figure 2, and wait for the reader to understand the process before moving on to the custom code, which will be more efficient.

  • 1. The red box in the picture is the beginning part. Let’s start from here. By default, everyone will use these two frameworks, so I won’t go into more detail.
  • 2. First, when we need to request an interface, we will use the Retrofit object to call its create method to create an XXXService. Let’s look at the code in Figure 3:
    • 1. You can see that the dynamic proxy method is simply used to assign each interface of XXXService to a specific ServiceMethod for implementation.
    • 2. Where does the ServiceMethod come from? Look at the loadServiceMethod method in line 36. This first goes to the serviceMethodCache to see if there is a ServiceMethod for an XXXService interface. If not, create one using Builder mode.

  • 3. Looking back at Figure 2, after creating an implementation class for XXXService, we typically call an interface in conjunction with Rxjava to return an Observable. Adapt (OkhttpCall) is an Observable that returns a call to servicemethod.adapt (OkhttpCall).You can see line 21 in Figure 3), we enter this method.
    • 1. This method will give calls to calladapter.adapt (OkhttpCall)
    • 2. Some of you may know that this CallAdapter is created by the CallAdapterFactory added to retrofit.Builder () when you initialize Retrofit. Several implementations of this are shown in Figure 2.
    • 3. So which one to choose here? Choose specific logic in ServiceMethod CallAdapter. Build inside. He will call ServiceMethod createCallAdapter here will eventually to Retrofit. CallAdapter to find the right CallAdapter.
    • 4.So what is the specific lookup logic in 3? Here’s a summary:
      • 1. The CallAdapterFactory will be looped and used once it returns a CallAdapter that is not null.
      • 2. Assign the null logic to the specific CallAdapterFactory to implement.
      • 3. Because the search is sequential, if there are multiple matching items in the list, only the first one is selected here.
  • 4. Without looking at Figure 2,In general on the matching RxJava2CallAdapterFactory CallAdapterFactory would be. Let’s first examine how he generates an Observable.
    • 1. Take a look at Figure 4, let’s go straight to line 20, Here explains why general will match to RxJava2CallAdapterFactory because our XXXService when defining interfaces were generally choose the return value of the observables or relevant Rxjava return values. Then we look directly at line 55, which returns an RxJava2CallAdapter, which is the object that generates the Observable.
    • 2. Then let’s look at Figure 5, remember the one above3.1What did we say? An Observable is generated by callAdapter.adapt (OkhttpCall). Here’s the implementation.
      • 1. You can see that line 18 generates two different Observables depending on whether the interface call is synchronous or asynchronous.
      • 2. Then add operators to Observable based on the flags.

  • 5. Going back to Figure 2, we now have An Observable. Let’s skip a few steps in Figure 2 and go straight to the yellow box where we can get the resulting Observable running. Those of you familiar with Rxjava should know that an Observable runs from the very top of the operator stream. So this starts with the first subscribe of the Observable defined in the rxJava2CallAdapter.adapt we talked about earlier. We’ll just default this interface call tosynchronousSo it goes into CallExecuteObservable first.
    • 1. Look at Figure 6. In line 1, when we construct this object, we pass in a Call object.
    • 2. In Line 5 of Figure 6, this is the first method that An Observable calls when it starts running (for those interested, see Rxjava source parsing). Here we can see line 13, which gives the call to okhttp.execute.
    • 3. We can look at line 20 in Figure 7, where createRawCall is called to create an okHttp3.Call whose implementation is a RealCall(the same request network we use directly with okHTTP).
    • 4. Back in Figure 2, when the realCall. execute is invoked, the okHTTP request chain is entered. Okhttp uses a chain of responsibility pattern, passing requests through the interceptors shown in Figure 2, each responsible for a function. Developers can insert their own interceptors at the beginning of the interceptor chain to achieve some customization.
    • 5. Going back to Figure 7, okHTTP will return an okHttp3.response after the data request is completed. At this time, parseResponse will be called in line 32 and line 43 to parse the Response.
    • 6. Some of the later code in Figure 7 is missing, but in fact the final Response is resolved by Servicemethod. toResponse. Which in turn will be handed over to Converter. Coverter. This interface implementation class is very much also, one of the most common should be GsonConverterFactory GsonResponseBodyConverter the offers. As shown in Figure 2, we typically add some Converters when creating a Retrofit for use here as well. Similar to CallAdapter, the selection of Converter is the same strategy
    • 7. After the above call, we have a Retrofit2. Respons with a parsed body object inside.

  • 6. After the CallExecuteObservable is called, the process of calling the BodyObservable usually passes the parsed body from Retrofit2.Respons to the next Observable operator. The flow of operators will eventually lead to the return value of the interface we defined in XXXService and the Observable generic object will be passed into the SUBSCRIBE for external callers to use. The pink box in Figure 2.

2. Network layer custom code

Customization is the addition of code implementations to each major node of the network request process to meet specific requirements. After the previous explanation, I think the reader should have a general understanding of the overall network layer request flow. At this point we can look at Figure 2 again, and you can see that there are several places in my green boxes where we can add custom code. I’ll show you how the custom code is implemented at these points in sequence.

(1) retrofit2. The adornment of the Call

The first thing we see in Figure 2 in order of request is the newCall. execute box, and I’ll show you how this can be customized.

  • 1. As we explained earlier, you should know that NewCall here is OkhttpCall, which returns a Retrofit.Response without any customization. Finally, a data structure (called ContentData) is returned in the developer’s subscribe after parsing the body. Sometimes we need more information in the subscribe, such as the head information lost during data conversion.
  • 2. We can wrap OkhttpCall by defining our own DataContainer object, which encapsulates the ContentData. It can also contain the data lost during the data transformation. As shown in figure 10.

  • Observable

    > (Observable

    >) (Observable

    >)
  • The DataContainer is deserialized by Gson, and the okHttp3.Response object is not known to the server.
  • 5. The answer is shown in Figures 8 and 9. You can see line 7 in Figure 8 where I’ve added a custom CallAdapterFactory.
  • 6. In lines 44, 48, and 49 of Figure 8, the CallAdapter in line 44 is used to generate an Observable based on the request flow described earlier. If you look at line 48, the call here is OkhttpCall. We pass that into our buildCall and we return a NewCall, and here’s the key.
  • 7. The buildCall implementation code is shown in Figure 9 at line 38. The implementation here is very simple and straightforward by wrapping OkhttpCall and returning a ContainerCall, as shown in Figure 11.

  • 8.DataContainerCall is a DataContainer with an okHttp3. Response object.
  • 9. Do you think a little thing like this is easy? In fact, I think it is very simple, but as long as you can use this little thing, then more practical functions can be achieved this way.

(2) OkhttpClient customization

The second place to customize, in order, is where OkhttpCall calls okhttp.realCall.

  • 1. Look at line 21 of Figure 8, where an OkhttpClient is added to Retrofit. All subsequent requests are sent through it.
  • In line 3, you can see that RetrofitConfig is passed here. It is actually an interface. DefaultRetrofitConfig in Figure 9 is an implementation ofit. Of course we can have different implementations to achieve different ways of customization.
  • 3. Again, looking at line 6 of Figure 9, you can see that the return value Builder for this method has added a series of Intercepts. As we saw earlier, these are interceptors that then intercept requests and responses in the order they are added.
  • 4. You can see here that I implemented a variety of different functions: printing network request logs (not implemented in the last article, but now implemented), filtering requests that are too frequent (preventing ddos attacks), SSL authentication (no backend yet), timeout interception, adding custom parameters, and so on.
  • 5. The customization here is relatively simple, you can look at the implementation of the interceptors.

(3) the Converter customization

  • 1. In fact, this is also very simple, you may have used, is the figure 8 5, 6 two lines, add data converter.
  • 2. You just need to understand the implementation strategy of Converter as I explained earlier.

(4) CallAdapter customization

  • 1. As you can see in (1) Retrofit2.Call decorating section, we added a CustomAdapterFactory.
  • 2. Because CustomAdapterFactory than RxJava2CallAdapterFactory add first, so the higher priority. See figure 8 40 line again, here for a delegate, RxJava2CallAdapterFactory namely actually. So we can add some uniform operators to the Observable returned by RxJava2CallAdapter.
  • 3. The concrete code is at line 49 in Figure 8, then go to line 42 in Figure 9. As you can see, I just added a few simple operators: count the number of successful and failed requests, and filter frequent requests with ThrottlingInterceptor.

(5) Network layer custom code summary

This is how you customize the four main nodes of your network request. The bottom line is simple: 1 is extending the results returned by Retrofit, 2 is extending the OKHTTP request and return, 3 is parsing the results returned by OKHTTP to Retrofit, and 4 is enhancing the processing of the results returned by Retrofit.

Four,

I’ve already written so much. I thought I could do a section on Customizations for Fresco, but I’ll have to do it in the next article. Here’s a preview: There will probably be one or two more Articles in this series on the Android level, to be released in the next week or so.

After the end of this stage, my article and learning focus will shift to audio and video. Over the past few months, I have sometimes delayed writing, but in the end I have kept my promise. In the end, I hope you’ll stay tuned for this series because I’ve been working so hard.

No anxiety peddling, no headlines. Share something interesting about the world. Topics include but are not limited to: science fiction, science, technology, Internet, Programmer, computer programming. Below is my wechat public number: the world’s interesting things, do a lot of goods waiting for you to see.