preface
To become an excellent Android Developer, you need a complete Knowledge system , here, let's grow up as we think~
As we all know, PhoneWindow is the most basic window system in the Android system, and each Activity will create one. At the same time, PhoneWindow is also an interface for interaction between Activity and View systems. DecorView is essentially a FrameLayout, which is the ancestor of all views in the Activity.
1, Start: DecorView is loaded into Window
Start with the startActivity of the Activity, and finally call the handleLaunchActivity method of the ActivityThread to create the Activity. The relevant core code is as follows:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { .... // When an Activity is created, the onCreate method of the Activity will be called // This completes the creation of DecorView Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed); } } final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { unscheduleGcIdler(); mSomeActivitiesChanged = true; // Call the onResume method of the Activity ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; ... if (r.window == null &&& !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); // Get DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // Get WindowManager, which is an interface // And inherits the interface ViewManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; // The implementation class of WindowManager is WindowManagerImpl, // Therefore, the addView method of WindowManagerImpl is actually called wm.addView(decor, l); } } } } public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ... @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } ... }
Before understanding the overall process of View drawing, we must first understand the concepts of ViewRoot and DecorView. ViewRoot corresponds to the ViewRootImpl class. It is the link between WindowManager and DecorView. The three processes of View are completed through ViewRoot. In the ActivityThread, after the Activity object is created, the DecorView will be added to the Window, the ViewRootImpl object will be created, and the ViewRootImpl object will be associated with the DecorView. The relevant source code is as follows:
// addView method of WindowManagerGlobal public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View pannelParentView = null; synchronized (mLock) { ... // Create ViewRootImpl instance root = new ViewRootImpl(view..getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } try { // Load DecorView into Window root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
2, Understand the overall process of drawing
Drawing starts from the performTraversals() method of the root ViewRoot, and traverses the entire View tree from top to bottom. Each View control is responsible for drawing itself, and the ViewGroup is also responsible for notifying its child views to draw. The core code of performTraversals() is as follows.
private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... //Execute measurement process performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... //Execute layout process performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... //Execute drawing process performDraw(); }
The approximate workflow of performTraversals is as follows:
Those that cannot be displayed can be viewed here
be careful:
-
The delivery process of preforlayout and performDraw is similar to that of performMeasure. The only difference is that the delivery process of performDraw is implemented through dispatchDraw in the draw method, but there is no essential difference.
-
Get content:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
-
Get set View:
content.getChildAt(0);
3, Understand MeasureSpec
1.MeasureSpec source code analysis
MeasureSpec represents a 32-bit integer value. Its high 2 bits represent the measurement mode SpecMode, and the low 30 bits represent the specification size SpecSize in a certain measurement mode. MeasureSpec is a static inner class of the View class, which is used to describe how to measure the View. The core code of MeasureSpec is as follows.
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0X3 << MODE_SHIFT; // Without specifying the measurement mode, the parent view does not limit the size of the child view, and the child view can be what you want // Any size of, usually used inside the system, is rarely used in application development. public static final int UNSPECIFIED = 0 << MODE_SHIFT; // In the precise measurement mode, the width and height of the view are specified as match_ Effective when parent or specific value, // It indicates that the parent View has determined the exact size of the child View. In this mode, the measurement of View // The value is the value of SpecSize. public static final int EXACTLY = 1 << MODE_SHIFT; // Maximum measurement mode, when the width and height of the view is specified as wrap_content takes effect at this time // The size of a child view can be any size that does not exceed the maximum size allowed by the parent view. public static final int AT_MOST = 2 << MODE_SHIFT; // Creates a MeasureSpec based on the specified size and pattern public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } // Fine tune the size of a MeasureSpec static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return make MeasureSpec(0, UNSPECIFIED); } int size = getSize(measureSpec) + delta; if (size < 0) { size = 0; } return makeMeasureSpec(size, mode); } }
MeasureSpec avoids excessive object memory allocation by packaging SpecMode and SpecSize into an int value. In order to facilitate operation, it provides packaging and unpacking methods. The packaging method is makeMeasureSpec in the above source code. The source code of unpacking method is as follows:
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
2. Creation process of MeasureSpec of decorview:
//desiredWindowWidth and desiredWindowHeight are the screen dimensions childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); private static int getRootMeaureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATRCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
3. Creation process of MeasureSpec of child element
// measureChildWithMargins method of ViewGroup protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // The MeasureSpec of the child element is created with the MeasureSpec of the parent container and the child element itself // It is also related to the margin and padding of View final int childWidthMeasureSpec = getChildMeasureSpec( parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec( parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child..measure(childWidthMeasureSpec, childHeightMeasureSpec); } public static int getChildMeasureSpec(int spec, int padding, int childDimesion) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // padding refers to the amount of space occupied in the parent container, so the child elements are available // The size is the size of the parent container minus padding int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (sepcMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimesion == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... // find out how big it should be resultSize = 0; resultMode == MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
Conclusion: for DecorView, its MeasureSpec is determined by the window size and its own LayoutParams; For an ordinary View, its MeasureSpec is determined by the MeasureSpec of the parent View and its own LayoutParams.
4, Measure of View drawing process
1. Basic process of measure
From the previous analysis, we can see that the measurement process of the page starts with the performMeasure method, and the relevant core code process is as follows.
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { ... // Specific measurement operations are distributed to the ViewGroup mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... } // Traverse all views in the measurement ViewGroup in the measureChildren() method in the ViewGroup protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; // When the visibility of the View is in the go state, it is not measured if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } // Measure a specified View protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); // It is calculated according to the MeasureSpec of the parent container and the LayoutParams of the child View // MeasureSpec of subview final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } // measure method of View public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // ViewGroup does not define the specific process of measurement because ViewGroup is a // Abstract class. The onMeasure method of its measurement process needs each subclass to implement onMeasure(widthMeasureSpec, heightMeasureSpec); ... } // Different ViewGroup subclasses have different layout characteristics, resulting in different measurement details. If you need to customize the measurement process, the subclass can override this method protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // The setMeasureDimension method is used to set the measured width and height of the View setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // If the View does not override the onMeasure method, getDefaultSize will be called by default to obtain the width and height of the View public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = sepcSize; break; } return result; }
2. Analysis of getSuggestMinimumWidth
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; }
If the View has no background, the value specified by the android:minWidth property is returned, which can be 0; If the View has a background set, the maximum of android:minWidth and the minimum width of the background is returned.
3. Manually handle wrap when customizing the View_ Content
Controls that directly inherit View need to override the onMeasure method and set wrap_content, otherwise wrap is used in the layout_ Content is equivalent to using match_parent. The solution is as follows:
protected void onMeasure(int widthMeasureSpec, int height MeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); // In wrap_ Specify the internal width / height (mWidth and mHeight) in the case of content int heightSpecSize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(widthSpecSize, mHeight); } }
4. Implementation and analysis of onMeasure method of LinearLayout
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } // measureVertical core source code // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); ... // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if need) measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }
The system will traverse the child elements and execute the measureChildBeforeLayout method for each child element. This method will internally call the measure method of the child elements, so that each child element will start to enter the measure process in turn, and the system will store the preliminary height of LinearLayout in the vertical direction through the mTotalLength variable. Each time a child element is measured, mTotalLength will increase. The increased part mainly includes the height of the child element and the margin of the child element in the vertical direction.
// LinearLayout is the core source code for measuring your size // Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; ... setMeasuredDimension(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState), heightSizeAndState); public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: // The height cannot exceed the remaining space of the parent container if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
5. Get the width and height of a View in the Activity
Because the measure process of View and the life cycle method of Activity are not executed synchronously, if the View has not been measured, the obtained width / height is 0. Therefore, the width and height information of a View cannot be obtained correctly in onCreate, onStart and onResume. The solution is as follows:
-
Activity/View#onWindowFocusChanged
//At this point, the View has been initialized. / / the Activity window will be called once when it gets the focus and loses the focus. / / if onResume and onPause are performed frequently, Then onWindowFocusChanged will also be frequently called public void onWindowFocusChanged (Boolean hasfocus) {super.onWindowFocusChanged (hasfocus); if (hasfocus) {int width = View. Getmeasurewidth(); int height = View. Getmeasuredheight();}}
-
view.post(runnable)
//You can post a runnable to the end of the message queue. / / and then wait for Looper to call runnable for the second time. The View has been initialized / / protected void onstart() {super. Onstart(); View. Post (New runnable()){
@Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } });
}
-
ViewTreeObserver
//When the state of the View tree changes or the visibility / / of the View inside the View tree changes, the onGlobalLayout method will be callback protected void onstart() {super. Onstart();
ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } });
}
-
View.measure(int widthMeasureSpec, int heightMeasureSpec)
5, Layout of the drawing process of View
1. Basic process of layout
// ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... } // View.java public void layout(int l, int t, int r, int b) { ... // Use the setFrame method to set the position of the four vertices of the View, that is, the position of the View in the parent container boolean changed = isLayoutModeOptical(mParent) ? set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b); ... onLayout(changed, l, t, r, b); ... } // Empty method. If the subclass is of ViewGroup type, override this method to implement ViewGroup // Layout process of all View controls in protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
2. Implementation and analysis of onLayout method of LinearLayout
protected void onlayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l,) } } // layoutVertical core source code void layoutVertical(int left, int top, int right, int bottom) { ... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasureWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); ... if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; // Determine the corresponding position for the child element setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); // childTop will gradually increase, which means that the following child elements will be deleted // Place in the lower position childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child,i) } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
Note: in the default implementation of View, the measured width / height and the final width / height of View are equal, except that the measured width / height is formed in the measure process of View, and the final width / height is formed in the layout process of View, that is, the assignment timing of the two is different, and the assignment timing of measured width / height is slightly earlier. In some special cases, the two are not equal:
-
Rewrite the layout method of View so that the final width is always 100px wider / taller than the measured width
public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }
-
How long does it take for View to determine its own measured width / height? In the process of previous measurements, the measured width / height may be inconsistent with the final width / height, but in the end, the measured width / height is still the same as the final width / height
6, Draw ing of View's drawing process
1. Basic flow of draw
private void performDraw() { ... draw(fullRefrawNeeded); ... } private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, scalingRequired, dirty)) { return; } ... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scallingRequired, Rect dirty) { ... mView.draw(canvas); ... } // Drawing can be basically divided into six steps public void draw(Canvas canvas) { ... // Step 1: draw the background of View drawBackground(canvas); ... // Step 2: if necessary, keep the canvas layer and prepare for fading saveCount = canvas.getSaveCount(); ... canvas.saveLayer(left, top, right, top + length, null, flags); ... // Step 3: draw the contents of the View onDraw(canvas); ... // Step 4: draw sub View of View dispatchDraw(canvas); ... // Step 5: if necessary, draw the fading edge of the View and restore the layer canvas.drawRect(left, top, right, top + length, p); ... canvas.restoreToCount(saveCount); ... // Step 6: draw the decoration of View (such as scroll bar, etc.) onDrawForeground(canvas) }
2. Function of setwillnotdraw
// If a View does not need to draw any content, after setting this flag bit to true, // The system will be optimized accordingly. public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
-
By default, View does not enable this optimization flag bit, but ViewGroup will enable this optimization flag bit by default.
-
When our custom control inherits from ViewGroup and does not have drawing function, you can turn on this flag bit to facilitate subsequent optimization of the system.
-
When we clearly know that a ViewGroup needs to draw content through onDraw, we need to close will explicitly_ NOT_ Draw this flag bit.
7, Summary
View's drawing process and event distribution mechanism are the core knowledge points in Android development, as well as the internal mental skills of custom view experts. For an excellent Android Developer, mainstream third-party source code analysis and Android core source code analysis can be said to be a required course. The next article will lead you to further explore Android.
Reference link:
1. Exploration of Android development Art
2. Android advanced light
3. Advanced Android
4,Android application layer View drawing process and source code analysis
5,Analysis of View drawing process in Android
Contanct Me
● wechat:
Welcome to my wechat: bcce5360. The number of people in the group has exceeded 200. You can't scan the code into the group. Add my wechat to pull you into the group.