资讯专栏INFORMATION COLUMN

Android 复盘——你真的了解 setContentView 吗?

Alan / 3491人阅读

摘要:之前的也被取代为。但的内部回调是由来实现的。一个包含这一个纵向的,内部是一个,是一个包含了内容部分和标题部分的容器。是的子类,我们编写的布局就是被添加到它的内部。至此,的流程就走完了。

1. AppCompatDelegate 的 setContentView()

分析 Android 中的 View,我们先从进入应用的看到的的一个 View 入手,第一个 View 就是 通过 setContentView() 这个方法进行加载的。我们来看 setContentView() 的源码:

</>复制代码

  1. public void setContentView(@LayoutRes int layoutResID) {
  2. this.getDelegate().setContentView(layoutResID);
  3. }

AppCompatActivity 中的 setContentView() 又调用了 AppCompatDelegate 中的 setContentView() 方法,那 AppCompatDelegate 是做什么的呢?

</>复制代码

  1. AppCompat 出现在 v7 包,它的作用是让 API 等级在 7 之上的设备也能使用 ActionBar,在 v7:21 之后,AppCompat 可以让 API 在 7 之上的设备使用 MD、ToolBar 等效果。之前的 ActionBarActivity 也被取代为 AppCompatActivity。但 AppCompatActivity 的内部回调是由 AppCompatDelegate 来实现的。

AppCompatDelegate 是一个抽象类,它的实现类是 AppCompatDelegateImpl,现在看 AppCompatDelegateImpl 中的 setContentView() 方法:

</>复制代码

  1. public void setContentView(int resId) {
  2. // 创建 DecorView,DecorView 是视图中的顶级 View
  3. this.ensureSubDecor();
  4. // 获取 DecorView 中的 content 部分
  5. ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
  6. contentParent.removeAllViews();
  7. // 将我们编写的界面填充到 content 中
  8. LayoutInflater.from(this.mContext).inflate(resId, contentParent);
  9. this.mOriginalWindowCallback.onContentChanged();
  10. }
2. DecorView

在 AppCompatDelegateImpl 的 setContentView() 中,通过 ensureSubDecor() 方法为视图创建 DecorView,

</>复制代码

  1. private void ensureSubDecor() {
  2. if (!this.mSubDecorInstalled) {
  3. // DecorView 不存在,调用 createSubDecor() 创建 DecorView
  4. this.mSubDecor = this.createSubDecor();
  5. CharSequence title = this.getTitle();
  6. if (!TextUtils.isEmpty(title)) {
  7. if (this.mDecorContentParent != null) {
  8. this.mDecorContentParent.setWindowTitle(title);
  9. } else if (this.peekSupportActionBar() != null) {
  10. this.peekSupportActionBar().setWindowTitle(title);
  11. } else if (this.mTitleView != null) {
  12. this.mTitleView.setText(title);
  13. }
  14. }
  15. this.applyFixedSizeWindow();
  16. this.onSubDecorInstalled(this.mSubDecor);
  17. this.mSubDecorInstalled = true;
  18. AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
  19. if (!this.mIsDestroyed && (st == null || st.menu == null)) {
  20. this.invalidatePanelMenu(108);
  21. }
  22. }
  23. }
  24. private ViewGroup createSubDecor() {
  25. // 获取设置的主题属性
  26. TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
  27. // 如果使用的主题不是 Theme.AppCompat,或者没又继承自 Theme.AppCompat,抛出异常。
  28. if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
  29. a.recycle();
  30. throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
  31. } else {
  32. // 根据主题的属性进行设置
  33. if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
  34. // 在 requestWindowFeature() 方法中
  35. // 设置 this.mWindowNoTitle = true
  36. this.requestWindowFeature(1);
  37. } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, false)) {
  38. this.requestWindowFeature(108);
  39. }
  40. if (a.getBoolean(styleable.AppCompatTheme_windowActionBarOverlay, false)) {
  41. this.requestWindowFeature(109);
  42. }
  43. if (a.getBoolean(styleable.AppCompatTheme_windowActionModeOverlay, false)) {
  44. this.requestWindowFeature(10);
  45. }
  46. // 记录是否为浮动的主题
  47. this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
  48. a.recycle();
  49. this.mWindow.getDecorView();
  50. LayoutInflater inflater = LayoutInflater.from(this.mContext);
  51. ViewGroup subDecor = null;
  52. // 根据不同的设置,给 subDecor 填充内容
  53. if (!this.mWindowNoTitle) {
  54. if (this.mIsFloating) {
  55. // Dialog 的主题
  56. subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
  57. this.mHasActionBar = this.mOverlayActionBar = false;
  58. } else if (this.mHasActionBar) {
  59. // 添加 ActionBar
  60. TypedValue outValue = new TypedValue();
  61. this.mContext.getTheme().resolveAttribute(attr.actionBarTheme, outValue, true);
  62. Object themedContext;
  63. if (outValue.resourceId != 0) {
  64. themedContext = new ContextThemeWrapper(this.mContext, outValue.resourceId);
  65. } else {
  66. themedContext = this.mContext;
  67. }
  68. subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
  69. this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
  70. this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
  71. if (this.mOverlayActionBar) {
  72. this.mDecorContentParent.initFeature(109);
  73. }
  74. if (this.mFeatureProgress) {
  75. this.mDecorContentParent.initFeature(2);
  76. }
  77. if (this.mFeatureIndeterminateProgress) {
  78. this.mDecorContentParent.initFeature(5);
  79. }
  80. }
  81. } else {
  82. if (this.mOverlayActionMode) {
  83. subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
  84. } else {
  85. subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
  86. }
  87. if (VERSION.SDK_INT >= 21) {
  88. ViewCompat.setOnApplyWindowInsetsListener(subDecor, new OnApplyWindowInsetsListener() {
  89. public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
  90. int top = insets.getSystemWindowInsetTop();
  91. int newTop = AppCompatDelegateImpl.this.updateStatusGuard(top);
  92. if (top != newTop) {
  93. insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
  94. }
  95. return ViewCompat.onApplyWindowInsets(v, insets);
  96. }
  97. });
  98. } else {
  99. ((FitWindowsViewGroup)subDecor).setOnFitSystemWindowsListener(new OnFitSystemWindowsListener() {
  100. public void onFitSystemWindows(Rect insets) {
  101. insets.top = AppCompatDelegateImpl.this.updateStatusGuard(insets.top);
  102. }
  103. });
  104. }
  105. }
  106. // 把 DecorView 添加到 Window 上 并且返回 DecorView
  107. if (subDecor == null) {
  108. throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
  109. } else {
  110. if (this.mDecorContentParent == null) {
  111. this.mTitleView = (TextView)subDecor.findViewById(id.title);
  112. }
  113. ViewUtils.makeOptionalFitsSystemWindows(subDecor);
  114. ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
  115. ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
  116. if (windowContentView != null) {
  117. while(windowContentView.getChildCount() > 0) {
  118. View child = windowContentView.getChildAt(0);
  119. windowContentView.removeViewAt(0);
  120. contentView.addView(child);
  121. }
  122. windowContentView.setId(-1);
  123. contentView.setId(16908290);
  124. if (windowContentView instanceof FrameLayout) {
  125. ((FrameLayout)windowContentView).setForeground((Drawable)null);
  126. }
  127. }
  128. // 把 DecorView 添加到 Window 上
  129. this.mWindow.setContentView(subDecor);
  130. contentView.setAttachListener(new OnAttachListener() {
  131. public void onAttachedFromWindow() {
  132. }
  133. public void onDetachedFromWindow() {
  134. AppCompatDelegateImpl.this.dismissPopups();
  135. }
  136. });
  137. return subDecor;
  138. }
  139. }
  140. }

创建好 DecorView 之后,DecorView 会被添加到 Windows(实现类是 PhoneWindow) 中,然后返回 DecorView。
并且 DecorView 是视图的顶级容器,我们可以通过 Android Studio 的 Layout Inspector 来查看一个界面的 View Tree。

一个 DecorView 包含这一个纵向的 LinearLayout,LinearLayout 内部是一个 FrameLayout,FrameLayout 是一个包含了内容部分和标题部分的容器。

在 AppCompatDelegateImpl 中的 setContentView() 方法中还有一句:

</>复制代码

  1. ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);

这句代码得到的 contentParent 就是刚刚创建的 DecorView 中的 内容根部局(id/content (ContentFrameLayout))。

ContentFrameLayout 是 FrameLayout 的子类,我们编写的 xml 布局就是被添加到它的内部。

然后查看为内容根布局添加视图的过程。

3. LayoutInflater 的 inflate()

inflate() 的代码如下:

</>复制代码

  1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
  2. final Resources res = getContext().getResources();
  3. if (DEBUG) {
  4. Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
  5. + Integer.toHexString(resource) + ")");
  6. }
  7. // 获取解析当前布局 xml 文件的 parser 对象
  8. final XmlResourceParser parser = res.getLayout(resource);
  9. try {
  10. // 调用 inflate() 方法,开始解析 xml 文件,并返回得到界面
  11. return inflate(parser, root, attachToRoot);
  12. } finally {
  13. parser.close();
  14. }
  15. }

在上述方法中,会先获取解析 xml 文件的 parser 对象,然后调用另一个 infalte() 方法进行解析。

</>复制代码

  1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  4. final Context inflaterContext = mContext;
  5. final AttributeSet attrs = Xml.asAttributeSet(parser);
  6. Context lastContext = (Context) mConstructorArgs[0];
  7. mConstructorArgs[0] = inflaterContext;
  8. View result = root;
  9. try {
  10. // 通过 while 循环遍历 xml 中的节点,直到找到 root
  11. int type;
  12. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  13. type != XmlPullParser.END_DOCUMENT) {
  14. // Empty
  15. }
  16. if (type != XmlPullParser.START_TAG) {
  17. throw new InflateException(parser.getPositionDescription()
  18. + ": No start tag found!");
  19. }
  20. final String name = parser.getName();
  21. // 如果是 merge 节点,执行 rInflate() 方法,按照层次递归的去实例化 xml 文件的子项
  22. if (TAG_MERGE.equals(name)) {
  23. if (root == null || !attachToRoot) {
  24. throw new InflateException(" can be used only with a valid "
  25. + "ViewGroup root and attachToRoot=true");
  26. }
  27. rInflate(parser, root, inflaterContext, attrs, false);
  28. } else {
  29. // Temp is the root view that was found in the xml
  30. // 不是 merge 节点,就通过 tag 标签创建一个 view
  31. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
  32. ViewGroup.LayoutParams params = null;
  33. if (root != null) {
  34. // Create layout params that match root, if supplied
  35. params = root.generateLayoutParams(attrs);
  36. if (!attachToRoot) {
  37. // Set the layout params for temp if we are not
  38. // attaching. (If we are, we use addView, below)
  39. temp.setLayoutParams(params);
  40. }
  41. }
  42. // Inflate all children under temp against its context.
  43. rInflateChildren(parser, temp, attrs, true);
  44. // We are supposed to attach all the views we found (int temp)
  45. // to root. Do that now.
  46. // 将创建的 View 添加到 root 视图中
  47. if (root != null && attachToRoot) {
  48. root.addView(temp, params);
  49. }
  50. // Decide whether to return the root that was passed in or the
  51. // top view found in xml.
  52. if (root == null || !attachToRoot) {
  53. result = temp;
  54. }
  55. }
  56. } catch (XmlPullParserException e) {
  57. final InflateException ie = new InflateException(e.getMessage(), e);
  58. ie.setStackTrace(EMPTY_STACK_TRACE);
  59. throw ie;
  60. } catch (Exception e) {
  61. final InflateException ie = new InflateException(parser.getPositionDescription()
  62. + ": " + e.getMessage(), e);
  63. ie.setStackTrace(EMPTY_STACK_TRACE);
  64. throw ie;
  65. } finally {
  66. // Don"t retain static reference on context.
  67. mConstructorArgs[0] = lastContext;
  68. mConstructorArgs[1] = null;
  69. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  70. }
  71. return result;
  72. }
  73. }

在这个 inflate() 方法中,会先寻找 xml 文件中的起始节点,如果起始节点是 merge,就执行 rInflate() 方法,如果不是 merge,就执行 createViewFromTag() 方法去创建一个新的 View,最后把 View 添加到内容根部局中。

4. createViewFromTag()

现在看 createViewFromTag() 的源码:

</>复制代码

  1. View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
  2. boolean ignoreThemeAttr) {
  3. if (name.equals("view")) {
  4. name = attrs.getAttributeValue(null, "class");
  5. }
  6. // Apply a theme wrapper, if allowed and one is specified.
  7. if (!ignoreThemeAttr) {
  8. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
  9. final int themeResId = ta.getResourceId(0, 0);
  10. if (themeResId != 0) {
  11. context = new ContextThemeWrapper(context, themeResId);
  12. }
  13. ta.recycle();
  14. }
  15. // 如果 name 的值为 blink,返回一个 BlinkLayout
  16. if (name.equals(TAG_1995)) {
  17. // Let"s party like it"s 1995!
  18. return new BlinkLayout(context, attrs);
  19. }
  20. try {
  21. View view;
  22. // 依次寻找合适的 Factory 对象去创建 View
  23. if (mFactory2 != null) {
  24. view = mFactory2.onCreateView(parent, name, context, attrs);
  25. } else if (mFactory != null) {
  26. view = mFactory.onCreateView(name, context, attrs);
  27. } else {
  28. view = null;
  29. }
  30. if (view == null && mPrivateFactory != null) {
  31. view = mPrivateFactory.onCreateView(parent, name, context, attrs);
  32. }
  33. if (view == null) {
  34. final Object lastContext = mConstructorArgs[0];
  35. mConstructorArgs[0] = context;
  36. try {
  37. if (-1 == name.indexOf(".")) {
  38. view = onCreateView(parent, name, attrs);
  39. } else {
  40. view = createView(name, null, attrs);
  41. }
  42. } finally {
  43. mConstructorArgs[0] = lastContext;
  44. }
  45. }
  46. return view;
  47. } catch (InflateException e) {
  48. throw e;
  49. } catch (ClassNotFoundException e) {
  50. final InflateException ie = new InflateException(attrs.getPositionDescription()
  51. + ": Error inflating class " + name, e);
  52. ie.setStackTrace(EMPTY_STACK_TRACE);
  53. throw ie;
  54. } catch (Exception e) {
  55. final InflateException ie = new InflateException(attrs.getPositionDescription()
  56. + ": Error inflating class " + name, e);
  57. ie.setStackTrace(EMPTY_STACK_TRACE);
  58. throw ie;
  59. }
  60. }

我们看一看代码中的 mFactory2mFactorymPrivateFactory 是什么。

在 LayoutInflater.java 的属性中,有如下几个变量:

</>复制代码

  1. private Factory mFactory;
  2. private Factory2 mFactory2;
  3. private Factory2 mPrivateFactory;

Factory 是一个接口,Factory2 是继承了 Factory 的接口,它们都有个一个 onCreateView() 的方法。

5. onCreateView()

我们去看它们在 AppCompatDelegateImpl 中的实现:

</>复制代码

  1. public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
  2. if (this.mAppCompatViewInflater == null) {
  3. TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
  4. String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
  5. if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
  6. try {
  7. Class viewInflaterClass = Class.forName(viewInflaterClassName);
  8. this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
  9. } catch (Throwable var8) {
  10. Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
  11. this.mAppCompatViewInflater = new AppCompatViewInflater();
  12. }
  13. } else {
  14. this.mAppCompatViewInflater = new AppCompatViewInflater();
  15. }
  16. }
  17. boolean inheritContext = false;
  18. if (IS_PRE_LOLLIPOP) {
  19. inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
  20. }
  21. return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
  22. }

方法的最后可以看出创建视图的工作交给了 AppCompatViewInflater 的 createView() 去完成

6. createView()

</>复制代码

  1. final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
  2. Context originalContext = context;
  3. if (inheritContext && parent != null) {
  4. context = parent.getContext();
  5. }
  6. if (readAndroidTheme || readAppTheme) {
  7. context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
  8. }
  9. if (wrapContext) {
  10. context = TintContextWrapper.wrap(context);
  11. }
  12. View view = null;
  13. byte var12 = -1;
  14. switch(name.hashCode()) {
  15. case -1946472170:
  16. if (name.equals("RatingBar")) {
  17. var12 = 11;
  18. }
  19. break;
  20. case -1455429095:
  21. if (name.equals("CheckedTextView")) {
  22. var12 = 8;
  23. }
  24. break;
  25. case -1346021293:
  26. if (name.equals("MultiAutoCompleteTextView")) {
  27. var12 = 10;
  28. }
  29. break;
  30. case -938935918:
  31. if (name.equals("TextView")) {
  32. var12 = 0;
  33. }
  34. break;
  35. case -937446323:
  36. if (name.equals("ImageButton")) {
  37. var12 = 5;
  38. }
  39. break;
  40. case -658531749:
  41. if (name.equals("SeekBar")) {
  42. var12 = 12;
  43. }
  44. break;
  45. case -339785223:
  46. if (name.equals("Spinner")) {
  47. var12 = 4;
  48. }
  49. break;
  50. case 776382189:
  51. if (name.equals("RadioButton")) {
  52. var12 = 7;
  53. }
  54. break;
  55. case 1125864064:
  56. if (name.equals("ImageView")) {
  57. var12 = 1;
  58. }
  59. break;
  60. case 1413872058:
  61. if (name.equals("AutoCompleteTextView")) {
  62. var12 = 9;
  63. }
  64. break;
  65. case 1601505219:
  66. if (name.equals("CheckBox")) {
  67. var12 = 6;
  68. }
  69. break;
  70. case 1666676343:
  71. if (name.equals("EditText")) {
  72. var12 = 3;
  73. }
  74. break;
  75. case 2001146706:
  76. if (name.equals("Button")) {
  77. var12 = 2;
  78. }
  79. }
  80. switch(var12) {
  81. case 0:
  82. view = this.createTextView(context, attrs);
  83. this.verifyNotNull((View)view, name);
  84. break;
  85. case 1:
  86. view = this.createImageView(context, attrs);
  87. this.verifyNotNull((View)view, name);
  88. break;
  89. case 2:
  90. view = this.createButton(context, attrs);
  91. this.verifyNotNull((View)view, name);
  92. break;
  93. case 3:
  94. view = this.createEditText(context, attrs);
  95. this.verifyNotNull((View)view, name);
  96. break;
  97. case 4:
  98. view = this.createSpinner(context, attrs);
  99. this.verifyNotNull((View)view, name);
  100. break;
  101. case 5:
  102. view = this.createImageButton(context, attrs);
  103. this.verifyNotNull((View)view, name);
  104. break;
  105. case 6:
  106. view = this.createCheckBox(context, attrs);
  107. this.verifyNotNull((View)view, name);
  108. break;
  109. case 7:
  110. view = this.createRadioButton(context, attrs);
  111. this.verifyNotNull((View)view, name);
  112. break;
  113. case 8:
  114. view = this.createCheckedTextView(context, attrs);
  115. this.verifyNotNull((View)view, name);
  116. break;
  117. case 9:
  118. view = this.createAutoCompleteTextView(context, attrs);
  119. this.verifyNotNull((View)view, name);
  120. break;
  121. case 10:
  122. view = this.createMultiAutoCompleteTextView(context, attrs);
  123. this.verifyNotNull((View)view, name);
  124. break;
  125. case 11:
  126. view = this.createRatingBar(context, attrs);
  127. this.verifyNotNull((View)view, name);
  128. break;
  129. case 12:
  130. view = this.createSeekBar(context, attrs);
  131. this.verifyNotNull((View)view, name);
  132. break;
  133. default:
  134. view = this.createView(context, name, attrs);
  135. }
  136. if (view == null && originalContext != context) {
  137. view = this.createViewFromTag(context, name, attrs);
  138. }
  139. if (view != null) {
  140. this.checkOnClickListener((View)view, attrs);
  141. }
  142. return (View)view;
  143. }

通过 createTextView() 等源码可以发现,creatView() 方法把常用的组件都变成了 AppCompat 的类型,从而达到了兼容的目的。

至此,setContentView() 的流程就走完了。但是添加好布局文件之后,视图并不会显示到界面上,还需要通过 WindowsManagerService 去渲染界面才能使界面显示。这部分到内容在后面会讲到。

零碎的东西很多,为了方便大家记忆,我把上面的内容做成了思维导图,需要的朋友可以保存下来,偶尔看一下,帮助自己记忆。

欢迎关注本文作者:

扫码关注并回复「干货」,获取我整理的千G Android、iOS、JavaWeb、大数据、人工智能等学习资源。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74405.html

相关文章

  • 程序员,职场上请远离这种人!

    摘要:随着微信和的不断普及,现在微信和留言也已经成为了甩锅证据的一部分,经常邮件里面大量粘贴微信聊天截图,职场上的宫心斗不比电视剧里面的差。 对有些职场人来讲,甩锅就是一种生存手段。 01.从大学打篮球说起 上大学的时候喜欢打篮球,然后我又特别喜欢抢篮板,经常是跳起来的时候没...

    khs1994 评论0 收藏0
  • 刷到就是赚到!八月阿里 Android 高级岗面经(年薪百万)

    摘要:前段时间,前同事跳槽,机缘巧合下面了阿里,本来凭着试一试的态度,却不料好事成双,拿到了,而且薪资也了。面就没啥东西可聊的,基本上就是对此次面试的一个评价定薪等等一些之内的话题。如果是现场面试,记得关注当天的天气,提前查一下路线。 ...

    aisuhua 评论0 收藏0
  • Android 复盘——帮彻底了解消息机制

    摘要:什么是消息机制说到消息机制,作为一名开发者一定先想到的是。但是,在主线程中创建的时候,我们并没有看到的执行,这是因为在线程,即的创建过程中,已经被创建好了。将新消息插入到之前,头消息之后。 1. 什么是消息机制 说到消息机制,作为一名 Android 开发者一定先想到的是 Handler。Handler 就是 Android 消息机制的上层接口,我们可用通过 Handler 轻松的在不...

    Baoyuan 评论0 收藏0

发表评论

0条评论

Alan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<