Guide
It has been a while since Penguin E-sports was connected to Weex in June 2017. During this time, in response to the problems encountered, the Penguin E-sports terminal mainly made the following optimizations:
image component
Preload
Pre-rendered
Image component
Weex’s list component and image component are very prone to problems. Penguin e-sports itself has many weex pages with infinite lists. The combination of list and image has caused memory problems. The memory problems caused by the app’s memory problems after accessing weex have always been high. No less.
List component problem
First of all, let's talk about list. The corresponding implementation of list is WXListComponent, and the corresponding view is BounceRecyclerView. RecyclerView should be familiar to everyone. The high-performance replacement ListView control provided in the android support library exists for the reuse of elements in the list. Originally, weex used RecyclerView as the implementation of the list, which is a happy thing to everyone, but there is an improper use of RecyclerView, which will cause the view to be unreusable.
The following figure describes the reuse process of RecyclerView: The problem of software quality degradation caused by RecylerView now needs to be displayed in the list and solved
[RecyclerView reuse]
The RecyclerView in weex does not set stableId, so all the reuse of RecyclerView depends on the ViewType of ViewHolder. The ViewType of Weex is generated as shown in the figure below:
Without setting the scope, the ref of the component of the viewHolder is the viewType, that is, all ViewHolders are different and non-reusable. At this time, the RecyclerView also degenerates into a slightly more complicated ScrollView.
If the scope attribute is set, but you absolutely can't think of it, the scope itself is also a pit. Directly on the code below:
In the above code, you can see that when the scope is used, when the Holder is reused, the data of the component that needs to be displayed will be bound to the reused component. So the question is, what if I don't just want to modify some properties, but need to change the hierarchical relationship of the component? For example, if you change from a->b->c to a->c->b, can you only use a different viewType or the following structure: a->b a->c b->b1 b ->c1 c->c2 c->b2 such a structure, but there are more instances of view, which will inevitably lead to various problems such as memory. The most fatal problem is that when creatingViewHolder, the component instance passed to ViewHolder is the original, not a copy. When bindData is executed, it will wait for the data of the component you reused to be modified, and then slide it back. At the time, GG.
So the scope attribute is basically unavailable, and only the list equivalent to scrollView is left for us.
Fortunately, in order to solve the performance of the list, the recyclerList supports the reuse of templates from the syntax layer of vue. But the pitfall is that the 0.17 and 0.18 versions of the recyclerList have all kinds of problems. Refactoring students feel that it is inefficient to use. After the 0.19 version of the weex team fixed these problems, the front-end students of Penguin e-sports are also trying to switch to the recyclerList.
image component problem
I believe that the android developers know that the problem of pictures is always a big problem. Performance problems such as OOM and GC are often accompanied by image manipulation.
Before version 0.17, the release of the bitmap in WXImageView was performed in the recycle of the component. After version 0.17, the recycle was also performed during detach, but the recycle of WXImageView just set the drawable of the ImageView to null, and did not actually call the bitmap. recycle.
And Penguin E-sports discovered during the version running that only set bitmapDrawable to null instead of calling bitmap's recycle. The oom problem on some models is very prominent (I have never wanted to understand here, why this part of the models will appear this Problem, after replacing it with fresco to manage it, there will be no such problem). Of course, if you recycle the bitmap directly without setting the bitmapDrawable, it will directly cause a crash.
Back to Penguin e-sports itself, the picture management in Penguin e-sports uses fresco. Before connecting to weex, we have made a series of optimizations for fresco to load pictures, and fresco itself already includes functions such as three-level caching.
After connecting to weex, the first thing that comes to mind is to use fresco's pipeline to load the bitmap and then use it for WXImage. In this process, I first encountered the improper management of the CloseableReference, which caused the bitmap to be recycled while it was still in use, and then encountered the pit that the bitmap could not be released because the recycle was not executed. In the long list, the problem that the picture cannot be released is zoomed infinitely, and the problem of oom when swiping a few screens often occurs. And with the development of business, the inability to play gif and webp pictures using WXImage has also become a bottleneck.
In subsequent versions, Penguin Gaming directly rewrites the image and img tags, replacing ImageView with Fresco's SimpleDraweeView. The benefit of this solution is that bitmap no longer needs to be managed by itself, that is, crash problems caused by oom problems and bitmap recycle will be greatly reduced, and fresco supports gif and webp pictures by default. However, this scheme also has a fatal problem: rounded corners.
The problem of rounding corners must start with the respective rounding schemes of fresco and weex.
weex rounded corners (box model-border): https://weex.apache.org/cn/wiki/common-styles.html#shi-li
fresco rounded corners: https://www.fresco-cn.org/docs/rounded-corners-and-circles.html
The fresco rounding scheme can be seen specifically in the three classes RoundedBitmapDrawable, RoundedColorDrawable, and RoundedCornersDrawable. The change of the fresco rounded corner property is ultimately only the modification of the properties of these three classes. The rounded corners are also implemented based on the modification of the canvas content when drawing, the cut of the BtimapDrawable and the border. The drawing is all drawn when drawing.
The weex rounded corner scheme can be seen in ImageDrawable. The realization scheme is to use Android's PaintDrawable to realize the reduction of bitmapDrawable by setting the shader, but the drawing of the border depends on the backgroundDrawable.
Moreover, in fresco, multi-layer drawable is encapsulated, it is difficult to modify the logic of drawabl's draw, and the setting of frame parameters is not as diverse as weex.
In view of the differences between the two, Penguin Gaming’s solution is to abandon fresco’s rounded corners, cut the bitmap through fresco’s post-processor to achieve the rounded corners, and reuse the weex background for the frame. The only problem with this solution is that a new bitmap must be created in the post-processor, but by reusing fresco's bitmapPool, it will not cause excessive memory problems.
Here is the key code for post-processor processing rounded corners:
When the list and image are combined, because the image of weex does not recycle the bitmap, and there is no use of bitmapPool, the memory of the long list weex page will be extremely high. After replacing it with the bitmap memory management mode of fresco, the percentage of memory crashes caused by weex dropped significantly from 2% in the initial version to 0.1%-0.2%.
Preloading
After stepping on large and small pits and alleviating the memory and crash problems, Penguin Gaming encountered two major problems in the use of weex:
1. Difficulty in debugging
2. Slow page loading
Difficult to debug
Weex's page does not give front-end developers a silky debugging experience. At first, front-end students used terminal logs or pop-ups to debug (distressed front-end students learned how to read android logs). Later, through repeated communication with the weex team, we finally determined the corresponding versions of weex and weex_debuger. Front-end students can check Debug the weex page on chrome.
However, weex_deubgger is not a perfect solution. Weex itself is the jscore kernel, and weex_debugger only opens a service through the chrome debugging protocol, which is equivalent to using the chrome kernel. The inconsistency of the kernel cannot guarantee the accuracy of debugging. Even weex's development classmates said that they would encounter inconsistent results between the debug environment and the formal environment.
The solution is also very simple, that is, it can be debugged on mac's xcode and safari. At that time, because the success of replacing the mac was too high, we will use the weex_debugger solution. I believe everyone knows how to solve it later.
Slow page loading
With the development of the Penguin e-sports business, the front-end classmates soon reported how the weex page opened so slowly, and this chrysanthemum has been around for so long. At that time, my heart was broken. When I accessed it, I was fine. A page was loaded back easily within 500-600ms. What could be the problem?
The speed of business development is always beyond your imagination. In less than two versions, the weex page in Penguin e-sports easily breaks from single digits to double digits, and the bundle size easily goes from dozens of kb. It broke through to hundreds of kb. The problem caused by this is that after opening the weex page, you can clearly see the chrysanthemum turning, and even the opening speed is not as good as the straight web page.
First of all, I found from the data report that in the page opening speed, 300-400ms in 1s is the time for the bundle to be downloaded from the network. Does that save this time and the page easily returns to the millisecond opening speed?
The following figure shows the overall process of preloading.
[Pre-loading process]
After the pre-loading scheme went online, the page successfully saved nearly 200ms of time-consuming. The LRUCache size of 20M also refers to the default size of the http cache, and the preload rate of page opening is 75%-80%.
Pre-render
After doing the pre-loading, I soon discovered that even if there is no network request, it takes more than 1 second to open the page. In this case, the existing solutions can no longer continue to optimize the page. At this time, I suddenly had an idea. Weex itself transforms the virtual dom of the front end into various view controls of the terminal. So why does the opening of the weex page slow so many terminal pages open?
Define the problem
Before solving the problem, let's define what the problem is. In view of the slow rendering speed, Penguin Gaming’s time-consuming for weex rendering is defined as follows:
· RenderStart = the point in time when WXSdkInstance.render() is called
· HttpFinish = the time point when WXSdkInstance.onHttpFinish() is called after the httpAdapter request comes back
· RenderFinish = callback IWXRenderListener.onRenderSuccess() time point
· Time to open the page = renderFinish-renderStart
· Network time = httpFinish-renderStart
· Rendering time = renderFinish-httpFinish
Therefore, the previous preloading has optimized the network time consumption, but after the rendering time is large, there will still be big performance problems.
In order to uncover the essence of this problem, let's take a look at the overall framework of weex:
[weex frame picture:]
JSFrameWork
The sdk provided to the front end encapsulates the dom operation of vue, and JSFrameWork is separately packaged into the apk package.
JavaScriptCore
Using the JavaScript engine of safari, a virtual machine that specializes in processing JavaScript, corresponds to the v8 of chrome, and the function can be roughly associated with the jvm of java.
JSS
The server side of weex core encapsulates the call to JavaScripteCore and encapsulates the instance sandbox. In multi-process implementation, JSS and JavaScriptCore are executed in another process to prevent the main process from crashing due to abnormal JS execution.
JSC
The client side of weex core serves as the bridge layer between WeexFrameWork and JSS. In addition, starting from version 0.18, cssLayout has also sunk to this layer.
WeexFrameWork
Provides java invocation of various sdk interfaces, conversion of virtual dom and Android control tree, control management, etc.
After understanding the weex framework, then shift the focus to the jsBundle generated after js build. Careful students will definitely find that the generated jsBundle is essentially a js method, so the process of rendering the weex page is essentially executing a js method. The current Js methodology is no longer suitable for most situations
For the homepage of the game that Penguin E-sports pays attention to, a complete management of the entire weex framework has been added. You can see that on nexus 6, the corresponding time-consuming and overall process are as follows:
[Weex execution process and time-consuming]
It can be seen that the performance hot spots are mainly in the two key steps of executing the js method and the execution of the virtual dom. According to the management point, the execution of a single js method and a single virtual dom takes very low time. Penguin E-sports has caught many times and saw that the slowest execution of js at startup is only 3ms, and most of the executions are in the range of 0.1ms-0 ms. However, no matter how fast the execution is time-consuming, it can't stand up to a large amount. Also take the homepage of the Penguin e-sports game as an example. How many js methods are executed on the page when it starts, and the execution of these 2000+ methods plus method scheduling It's time-consuming and it's no surprise that it can become a performance hotspot. The same is true for the execution of virtual dom. A single execution is optimized by the weex team, and the execution time is basically between 1ms-3ms, but the same problem is that it can't stand the amount of time and thread scheduling.
Pre-rendering scheme
Students who know RN should also know that the execution of the js method and the execution of the virtual dom are the core of this framework. If you want to leverage the entire core, the difficulty is basically the same as rewriting one. Then there is only one remaining solution: rendering in advance.
[Pre-rendering]
The pre-rendering scheme modifies the conversion between WeexFrameWork virtual dom and Android control tree. During pre-rendering, no real component and view structures are generated. The abstracted ComponentNode is used to store virtual dom operations, and the node is converted in RealRender. Into a component and a View.
The basic principle of this solution is that the space consumed in advance is typically exchanged for time, and the real component and view are not converted. The reason is that the view is not reusable in different contexts and the view itself will take up most of the memory.
Pre-render optimized data
Memory consumption
Pre-rendering will inevitably lead to the early consumption of class memory. Tested on Huawei nove3, the peak memory during pre-rendering the game homepage will go to 10M, which is below allure, but after the final pre-rendering is completed, the GC will release this part of memory, and it will eventually stay resident. The memory is 0.3M. The memory peak of the real rendering game homepage will go to 20M, and the final resident memory is 5.6M.
It can be seen that pre-rendering consumes very little resident memory, but due to the execution of virtual dom, the peak memory is high. In some memory-sensitive scenarios, there is still a certain risk.
Time-consuming page opening
The normal loading data of the game homepage in the lab is 900ms (pre-loaded, no network time-consuming), after pre-rendering, the page only needs 150ms to open.
Live network data:
[The pre-rendered page opens and reports]
Finally, here are two comparison diagrams before and after optimization:
[Pre-rendering:]
[Non-pre-rendering:]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。