需求

基于MTK 8.1平台定制导航栏部分,在左边增加音量减,右边增加音量加

思路

需求开始做之前,一定要研读SystemUI Navigation模块的代码流程!!!不要直接去网上copy别人改的需求代码,盲改的话很容易出现问题,然而无从解决。网上有老平台(8.0-)的讲解System UI的导航栏模块的博客,自行搜索。8.0对System UI还是做了不少细节上的改动,代码改动体现上也比较多,但是总体基本流程并没变。

源码阅读可以沿着一条线索去跟代码,不要过分在乎代码细节!例如我客制化这个需求,可以跟着导航栏的返回(back),桌面(home),最近任务(recent)中的一个功能跟代码流程,大体知道比如recen这个view是哪个方法调哪个方法最终加载出来,加载的关键代码在哪,点击事件怎么生成,而不在意里面的具体逻辑判断等等。

代码流程

1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;

从状态栏入口开始看。

  1. protected void makeStatusBarView() {
  2. final Context context = mContext;
  3. updateDisplaySize(); // populates mDisplayMetrics
  4. updateResources();
  5. updateTheme();
  6. ...
  7. ...
  8. try {
  9. boolean showNav = mWindowManagerService.hasNavigationBar();
  10. if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
  11. if (showNav) {
  12. createNavigationBar();//创建导航栏
  13. }
  14. } catch (RemoteException ex) {
  15. }
  16. }

2.进入 createNavigationBar 方法,发现主要是用 NavigationBarFragment 来管理.

  1. protected void createNavigationBar() {
  2. mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
  3. mNavigationBar = (NavigationBarFragment) fragment;
  4. if (mLightBarController != null) {
  5. mNavigationBar.setLightBarController(mLightBarController);
  6. }
  7. mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
  8. });
  9. }

3.看 NavigationBarFragment 的create方法,终于知道,是WindowManager去addView了导航栏的布局,最终add了fragment的onCreateView加载的布局。(其实SystemUI所有的模块都是WindowManager来加载View)

  1. public static View create(Context context, FragmentListener listener) {
  2. WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
  3. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
  4. WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
  5. WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
  6. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  7. | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  8. | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
  9. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
  10. | WindowManager.LayoutParams.FLAG_SLIPPERY,
  11. PixelFormat.TRANSLUCENT);
  12. lp.token = new Binder();
  13. lp.setTitle("NavigationBar");
  14. lp.windowAnimations = 0;
  15. View navigationBarView = LayoutInflater.from(context).inflate(
  16. R.layout.navigation_bar_window, null);
  17. if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
  18. if (navigationBarView == null) return null;
  19. context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
  20. FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
  21. NavigationBarFragment fragment = new NavigationBarFragment();
  22. fragmentHost.getFragmentManager().beginTransaction()
  23. .replace(R.id.navigation_bar_frame, fragment, TAG) //注意!fragment里onCreateView加载的布局是add到这个Window属性的view里的。
  24. .commit();
  25. fragmentHost.addTagListener(TAG, listener);
  26. return navigationBarView;
  27. }
  28. }

4.SystemUI\res\layout\navigation_bar_window.xml

来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame.(其实SystemUI以及其他系统应用如Launcher,都是这种自定义view的方式,好多逻辑处理也都是在自定义view里,不能忽略)

  1. <com.android.systemui.statusbar.phone.NavigationBarFrame
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  4. android:id="@+id/navigation_bar_frame"
  5. android:layout_height="match_parent"
  6. android:layout_width="match_parent">
  7. </com.android.systemui.statusbar.phone.NavigationBarFrame>

5.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFrame.java

我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。

6.再回来看看NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。

  1. @Override
  2. public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
  3. Bundle savedInstanceState) {
  4. return inflater.inflate(R.layout.navigation_bar, container, false);
  5. }

进入导航栏的真正根布局:navigation_bar.xml,好吧又是自定义view,NavigationBarView 和 NavigationBarInflaterView 都要仔细研读。

  1. <com.android.systemui.statusbar.phone.NavigationBarView
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  4. android:layout_height="match_parent"
  5. android:layout_width="match_parent"
  6. android:background="@drawable/system_bar_background">
  7. <com.android.systemui.statusbar.phone.NavigationBarInflaterView
  8. android:id="@+id/navigation_inflater"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent" />
  11. </com.android.systemui.statusbar.phone.NavigationBarView>

7.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java;继承自FrameLayout

先看构造方法,因为加载xml布局首先走的是初始化

  1. public NavigationBarInflaterView(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. createInflaters();//根据屏幕旋转角度创建子view(单个back home or recent)的父布局
  4. Display display = ((WindowManager)
  5. context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  6. Mode displayMode = display.getMode();
  7. isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
  8. }
  9. private void inflateChildren() {
  10. removeAllViews();
  11. mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
  12. mRot0.setId(R.id.rot0);
  13. addView(mRot0);
  14. mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this, false);
  15. mRot90.setId(R.id.rot90);
  16. addView(mRot90);
  17. updateAlternativeOrder();
  18. }

再看onFinishInflate()方法,这是view的生命周期,每个view被inflate之后都会回调。

  1. @Override
  2. protected void onFinishInflate() {
  3. super.onFinishInflate();
  4. inflateChildren();//进去看无关紧要 忽略
  5. clearViews();//进去看无关紧要 忽略
  6. inflateLayout(getDefaultLayout());//关键方法:加载了 back.home.recent三个按钮的layout
  7. }

看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法

  1. protected void inflateLayout(String newLayout) {
  2. mCurrentLayout = newLayout;
  3. if (newLayout == null) {
  4. newLayout = getDefaultLayout();
  5. }
  6. String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根据“;”号分割成长度为3的数组
  7. String[] start = sets[0].split(BUTTON_SEPARATOR);//根据“,”号分割,包含 left[.5W]和back[1WC]
  8. String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home
  9. String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W]
  10. // Inflate these in start to end order or accessibility traversal will be messed up.
  11. inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
  12. inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);
  13. inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
  14. inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);
  15. addGravitySpacer(mRot0.findViewById(R.id.ends_group));
  16. addGravitySpacer(mRot90.findViewById(R.id.ends_group));
  17. inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
  18. inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
  19. }
  20. protected String getDefaultLayout() {
  21. return mContext.getString(R.string.config_navBarLayout);
  22. }

SystemUI\res\values\config.xml

  1. <!-- Nav bar button default ordering/layout -->
  2. <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>

再看inflateButtons()方法,遍历加载inflateButton:

  1. private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
  2. boolean start) {
  3. for (int i = 0; i < buttons.length; i++) {
  4. inflateButton(buttons[i], parent, landscape, start);
  5. }
  6. }
  7. @Nullable
  8. protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
  9. boolean start) {
  10. LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
  11. View v = createView(buttonSpec, parent, inflater);//创建view
  12. if (v == null) return null;
  13. v = applySize(v, buttonSpec, landscape, start);
  14. parent.addView(v);//addView到父布局
  15. addToDispatchers(v);
  16. View lastView = landscape ? mLastLandscape : mLastPortrait;
  17. View accessibilityView = v;
  18. if (v instanceof ReverseFrameLayout) {
  19. accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
  20. }
  21. if (lastView != null) {
  22. accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
  23. }
  24. if (landscape) {
  25. mLastLandscape = accessibilityView;
  26. } else {
  27. mLastPortrait = accessibilityView;
  28. }
  29. return v;
  30. }

我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了 R.layout.home 的layout布局

  1. private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
  2. View v = null;
  3. ...
  4. ...
  5. if (HOME.equals(button)) {
  6. v = inflater.inflate(R.layout.home, parent, false);
  7. } else if (BACK.equals(button)) {
  8. v = inflater.inflate(R.layout.back, parent, false);
  9. } else if (RECENT.equals(button)) {
  10. v = inflater.inflate(R.layout.recent_apps, parent, false);
  11. } else if (MENU_IME.equals(button)) {
  12. v = inflater.inflate(R.layout.menu_ime, parent, false);
  13. } else if (NAVSPACE.equals(button)) {
  14. v = inflater.inflate(R.layout.nav_key_space, parent, false);
  15. } else if (CLIPBOARD.equals(button)) {
  16. v = inflater.inflate(R.layout.clipboard, parent, false);
  17. }
  18. ...
  19. ...
  20. return v;
  21. }
  22. //SystemUI\res\layout\home.xml
  23. //这里布局里没有src显示home的icon,肯定是在代码里设置了
  24. //这里也是自定义view:KeyButtonView
  25. <com.android.systemui.statusbar.policy.KeyButtonView
  26. xmlns:android="http://schemas.android.com/apk/res/android"
  27. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  28. android:id="@+id/home"
  29. android:layout_width="@dimen/navigation_key_width"//引用了dimens.xml里的navigation_key_width
  30. android:layout_height="match_parent"
  31. android:layout_weight="0"
  32. systemui:keyCode="3"//systemui自定义的属性
  33. android:scaleType="fitCenter"
  34. android:contentDescription="@string/accessibility_home"
  35. android:paddingTop="@dimen/home_padding"
  36. android:paddingBottom="@dimen/home_padding"
  37. android:paddingStart="@dimen/navigation_key_padding"
  38. android:paddingEnd="@dimen/navigation_key_padding"/>

8.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java

先来看KeyButtonView的构造方法:我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.

当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。

至此,导航栏按键事件我们梳理完毕。

  1. public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs);
  3. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
  4. defStyle, 0);
  5. mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
  6. mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
  7. mPlaySounds = a.getBoolean(R.styleable.KeyButtonView_playSound, true);
  8. TypedValue value = new TypedValue();
  9. if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
  10. mContentDescriptionRes = value.resourceId;
  11. }
  12. a.recycle();
  13. setClickable(true);
  14. mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  15. mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
  16. mRipple = new KeyButtonRipple(context, this);
  17. setBackground(mRipple);
  18. }
  19. ...
  20. ...
  21. public boolean onTouchEvent(MotionEvent ev) {
  22. ...
  23. switch (action) {
  24. case MotionEvent.ACTION_DOWN:
  25. mDownTime = SystemClock.uptimeMillis();
  26. mLongClicked = false;
  27. setPressed(true);
  28. if (mCode != 0) {
  29. sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);//关键方法
  30. } else {
  31. // Provide the same haptic feedback that the system offers for virtual keys.
  32. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
  33. }
  34. playSoundEffect(SoundEffectConstants.CLICK);
  35. removeCallbacks(mCheckLongPress);
  36. postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
  37. break;
  38. ...
  39. ...
  40. }
  41. return true;
  42. }
  43. void sendEvent(int action, int flags, long when) {
  44. mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
  45. .setType(MetricsEvent.TYPE_ACTION)
  46. .setSubtype(mCode)
  47. .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
  48. .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
  49. final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
  50. //这里根据mCode new了一个KeyEvent事件,通过injectInputEvent使事件生效。
  51. final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
  52. 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
  53. flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
  54. InputDevice.SOURCE_KEYBOARD);
  55. InputManager.getInstance().injectInputEvent(ev,
  56. InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
  57. }

9.还遗留一个问题:设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView.java

SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java

进入NavigationBarView类里,找到构造方法。

  1. public NavigationBarView(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. mDisplay = ((WindowManager) context.getSystemService(
  4. Context.WINDOW_SERVICE)).getDefaultDisplay();
  5. ...
  6. ...
  7. updateIcons(context, Configuration.EMPTY, mConfiguration);//关键方法
  8. mBarTransitions = new NavigationBarTransitions(this);
  9. //mButtonDispatchers 是维护这些home back recent图标view的管理类,会传递到他的child,NavigationBarInflaterView类中
  10. mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
  11. mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
  12. mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
  13. mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
  14. mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
  15. mButtonDispatchers.put(R.id.accessibility_button,new ButtonDispatcher(R.id.accessibility_button));
  16. }
  17. private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
  18. ...
  19. iconLight = mNavBarPlugin.getHomeImage(
  20. ctx.getDrawable(R.drawable.ic_sysbar_home));
  21. iconDark = mNavBarPlugin.getHomeImage(
  22. ctx.getDrawable(R.drawable.ic_sysbar_home_dark));
  23. //mHomeDefaultIcon = getDrawable(ctx,
  24. // R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
  25. mHomeDefaultIcon = getDrawable(iconLight,iconDark);
  26. //亮色的icon资源
  27. iconLight = mNavBarPlugin.getRecentImage(
  28. ctx.getDrawable(R.drawable.ic_sysbar_recent));
  29. //暗色的icon资源
  30. iconDark = mNavBarPlugin.getRecentImage(
  31. ctx.getDrawable(R.drawable.ic_sysbar_recent_dark));
  32. //mRecentIcon = getDrawable(ctx,
  33. // R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
  34. mRecentIcon = getDrawable(iconLight,iconDark);
  35. mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu,
  36. R.drawable.ic_sysbar_menu_dark);
  37. ...
  38. ...
  39. }

10.从第10可以看到,以recent为例,在初始化时得到了mRecentIcon的资源,再看谁调用了了mRecentIcon就可知道,即反推看调用流程。

  1. private void updateRecentsIcon() {
  2. getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
  3. mBarTransitions.reapplyDarkIntensity();
  4. }

updateRecentsIcon这个方法设置了recent图片的资源,再看谁调用了updateRecentsIcon方法:onConfigurationChanged屏幕旋转会重新设置资源图片

  1. @Override
  2. protected void onConfigurationChanged(Configuration newConfig) {
  3. super.onConfigurationChanged(newConfig);
  4. boolean uiCarModeChanged = updateCarMode(newConfig);
  5. updateTaskSwitchHelper();
  6. updateIcons(getContext(), mConfiguration, newConfig);
  7. updateRecentsIcon();
  8. if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
  9. || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
  10. // If car mode or density changes, we need to reset the icons.
  11. setNavigationIconHints(mNavigationIconHints, true);
  12. }
  13. mConfiguration.updateFrom(newConfig);
  14. }
  15. public void setNavigationIconHints(int hints, boolean force) {
  16. ...
  17. ...
  18. mNavigationIconHints = hints;
  19. // We have to replace or restore the back and home button icons when exiting or entering
  20. // carmode, respectively. Recents are not available in CarMode in nav bar so change
  21. // to recent icon is not required.
  22. KeyButtonDrawable backIcon = (backAlt)
  23. ? getBackIconWithAlt(mUseCarModeUi, mVertical)
  24. : getBackIcon(mUseCarModeUi, mVertical);
  25. getBackButton().setImageDrawable(backIcon);
  26. updateRecentsIcon();
  27. ...
  28. ...
  29. }

reorient()也调用了setNavigationIconHints()方法:

  1. public void reorient() {
  2. updateCurrentView();
  3. ...
  4. setNavigationIconHints(mNavigationIconHints, true);
  5. getHomeButton().setVertical(mVertical);
  6. }

再朝上推,最终追溯到NavigationBarFragment的onConfigurationChanged()方法 和 NavigationBarView的onAttachedToWindow()和onSizeChanged()方法。也就是说,在NavigationBarView导航栏这个布局加载的时候就会设置图片资源,和长度改变,屏幕旋转都有可能引起重新设置

至此,SystemUI的虚拟导航栏模块代码流程结束。

总结

  1. 创建一个window属性的父view
  2. 通过读取解析xml里config的配置,addView需要的icon,或者调换顺序
  3. src图片资源通过代码设置亮色和暗色
  4. touch事件以keycode方式交由系统处理

Android 8.1 SystemUI虚拟导航键加载流程解析的更多相关文章

  1. Android Launcher分析和修改4——初始化加载数据

    上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来, 界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Lau ...

  2. 8. Android加载流程(打包与启动)

    移动安全的学习离不开对Android加载流程的分析,包括Android虚拟机,Android打包,启动流程等... 这篇文章  就对Android的一些基本加载进行学习. Android虚拟机 And ...

  3. Android View的加载流程

    什么是Activity? Activity是 用户操作的可视化界面:它为用户提供了一个放置视图和交互操作的窗口.采用setContentView的方法提供.因此,可以理解Activity.Window ...

  4. Android中ViewPager+Fragment取消(禁止)预加载延迟加载(懒加载)问题解决方案

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53205878本文出自[DylanAndroid的博客] Android中Vie ...

  5. Android Volley和Gson实现网络数据加载

    Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...

  6. android源码解析(十七)-->Activity布局加载流程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...

  7. Android中使用WebView, WebChromeClient和WebViewClient加载网页 (能够执行js)

    Android中使用WebView, WebChromeClient和WebViewClient加载网页   在android应用中,有时要加载一个网页,如果能配上一个进度条就更好了,而android ...

  8. 携程Android App的插件化和动态加载框架

    携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...

  9. 我的Android进阶之旅------>Android疯狂连连看游戏的实现之加载界面图片和实现游戏Activity(四)

    正如在<我的Android进阶之旅------>Android疯狂连连看游戏的实现之状态数据模型(三)>一文中看到的,在AbstractBoard的代码中,当程序需要创建N个Piec ...

随机推荐

  1. 卸载&&更新docker(ubuntu)

    卸载docker: apt-get purge lxc-docker apt-get autoremove 更新docker: apt-get update apt-get install lxc-d ...

  2. 【java基础】Thread类之join方法

  3. postman+newman+html测试报告(接口自动化)

    1.安装node.js(Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境) 下载安装node.js,下载地址:https://nodejs.org/en/ 2.安 ...

  4. git log详细使用参数

    1. 可以看到fileName相关的commit记录 git log filename 2. 可以显示每次提交的diff git log -p filename 3. 只看某次提交中的某个文件变化,可 ...

  5. 数据库Oracle组函数和分组函数

    组函数: 组函数操作行集,给出每组的结果.组函数不象单行函数,组函数对行的集合进行操作,对每组给出一个结果.这些集合可能是整个表或者是表分成的组. 组函数与单行函数区别: 单行函数对查询到每个结果集做 ...

  6. HashMap 实现原理解析

    概要 HashMap 最早出现在 JDK 1.2 中,底层基于散列算法实现.HashMap 允许 null 键和 null 值,在计算哈键的哈希值时,null 键哈希值为 0.HashMap 并不保证 ...

  7. Docker系列-(3) Docker-compose使用与负载均衡

    上一篇文章介绍了docker镜像的制作与发布,本文主要介绍实际docker工程部署中经常用到的docker-compose工具,以及docker的网络配置和负载均衡. Docker-compose介绍 ...

  8. 【RabbitMQ】显示耗时处理进度

    [RabbitMQ]显示耗时处理进度 通过网页提交一个耗时的请求,然后启动处理线程,请求返回.处理线程每完成一部分就给前台推送完成的数量,前端显示进度. 依赖jar <?xml version= ...

  9. pythpn爬虫--来一波美女,备好纸巾了!

    关于图片名称的中央乱码问题 import requests from lxml import etree url = 'http://pic.netbian.com/4kmeinv/index_%d. ...

  10. java面试题干货126-170

    这部分主要是开源Java EE框架方面的内容,包括Hibernate.MyBatis.Spring.Spring MVC等,由于Struts 2已经是明日黄花,在这里就不讨论Struts 2的面试题, ...